blob: f2ea58712711c8b0330cd3c17c9f47b39ceff1a8 [file] [log] [blame]
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00001/* Copyright (C) 2004-2005 Constantin Kaplinsky. All Rights Reserved.
2 *
3 * This is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This software is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this software; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
16 * USA.
17 */
18//
19// PollingManager.cxx
20//
21
22// FIXME: Don't compare pixels already marked as changed.
23// FIXME: Use Image::copyPixels() instead of Image::updateRect()?
24// In that case, note the fact that arguments are not checked.
25
26#include <stdio.h>
27#include <string.h>
28#include <time.h>
29#include <X11/Xlib.h>
30#include <rfb/LogWriter.h>
31#include <rfb/VNCServer.h>
32#include <rfb/Configuration.h>
33#include <rfb/ServerCore.h>
34
35#include <x0vncserver/PollingManager.h>
36
37static LogWriter vlog("PollingMgr");
38
39BoolParameter PollingManager::pollPointer
40("PollPointer",
41 "DEBUG: Poll area under the pointer with higher priority",
Constantin Kaplinsky344c3fe2007-07-24 08:35:43 +000042 false);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000043
44IntParameter PollingManager::pollingType
45("PollingType",
46 "DEBUG: Select particular polling algorithm (0..3)",
47 3);
48
49const int PollingManager::m_pollingOrder[32] = {
50 0, 16, 8, 24, 4, 20, 12, 28,
51 10, 26, 18, 2, 22, 6, 30, 14,
52 1, 17, 9, 25, 7, 23, 15, 31,
53 19, 3, 27, 11, 29, 13, 5, 21
54};
55
56//
57// Constructor.
58//
59// Note that dpy and image should remain valid during the object
60// lifetime, while factory is used only in the constructor itself.
61//
62
63PollingManager::PollingManager(Display *dpy, Image *image,
64 ImageFactory *factory,
65 int offsetLeft, int offsetTop)
66 : m_dpy(dpy), m_server(0), m_image(image),
67 m_offsetLeft(offsetLeft), m_offsetTop(offsetTop),
68 m_pointerPosKnown(false), m_pollingStep(0)
69{
70 // Save width and height of the screen (and the image).
71 m_width = m_image->xim->width;
72 m_height = m_image->xim->height;
73
74 // Compute width and height in 32x32 tiles.
75 m_widthTiles = (m_width + 31) / 32;
76 m_heightTiles = (m_height + 31) / 32;
77
78 // Get initial screen image.
79 m_image->get(DefaultRootWindow(m_dpy), m_offsetLeft, m_offsetTop);
80
81 // Create additional images used in polling algorithms, warn if
82 // underlying class names are different from the class name of the
83 // primary image.
84 m_rowImage = factory->newImage(m_dpy, m_width, 1);
85 m_tileImage = factory->newImage(m_dpy, 32, 32);
86 m_areaImage = factory->newImage(m_dpy, 128, 128);
87 if (strcmp(m_image->className(), m_rowImage->className()) != 0 ||
88 strcmp(m_image->className(), m_tileImage->className()) != 0 ||
89 strcmp(m_image->className(), m_areaImage->className()) != 0) {
90 vlog.error("Image types do not match (%s, %s, %s)",
91 m_rowImage->className(),
92 m_tileImage->className(),
93 m_areaImage->className());
94 }
95
96 // FIXME: Extend the comment.
97 // Create a matrix with one byte per each 32x32 tile. It will be
98 // used to limit the rate of updates on continuously-changed screen
99 // areas (like video).
100 int numTiles = m_widthTiles * m_heightTiles;
101 m_statusMatrix = new char[numTiles];
102 memset(m_statusMatrix, 0, numTiles);
103
104 // FIXME: Extend the comment.
105 // Create a matrix with one byte per each 32x32 tile. It will be
106 // used to limit the rate of updates on continuously-changed screen
107 // areas (like video).
108 m_rateMatrix = new char[numTiles];
109 m_videoFlags = new char[numTiles];
110 m_changedFlags = new char[numTiles];
111 memset(m_rateMatrix, 0, numTiles);
112 memset(m_videoFlags, 0, numTiles);
113 memset(m_changedFlags, 0, numTiles);
114}
115
116PollingManager::~PollingManager()
117{
118 delete[] m_changedFlags;
119 delete[] m_videoFlags;
120 delete[] m_rateMatrix;
121
122 delete[] m_statusMatrix;
123
124 delete m_areaImage;
125 delete m_tileImage;
126 delete m_rowImage;
127}
128
129//
130// Register VNCServer object.
131//
132
133void PollingManager::setVNCServer(VNCServer *s)
134{
135 m_server = s;
136}
137
138//
139// Update current pointer position which may be used as a hint for
140// polling algorithms.
141//
142
143void PollingManager::setPointerPos(const Point &pos)
144{
145 m_pointerPosTime = time(NULL);
146 m_pointerPos = pos;
147 m_pointerPosKnown = true;
148}
149
150//
151// Indicate that current pointer position is unknown.
152//
153
154void PollingManager::unsetPointerPos()
155{
156 m_pointerPosKnown = false;
157}
158
159//
160// DEBUG: Measuring time spent in the poll() function,
161// as well as time intervals between poll() calls.
162//
163
164#ifdef DEBUG
165void PollingManager::debugBeforePoll()
166{
167 TimeMillis timeNow;
168 int diff = timeNow.diffFrom(m_timeSaved);
169 fprintf(stderr, "[wait%4dms]\t[step %2d]\t", diff, m_pollingStep % 32);
170 m_timeSaved = timeNow;
171}
172
173void PollingManager::debugAfterPoll()
174{
175 TimeMillis timeNow;
176 int diff = timeNow.diffFrom(m_timeSaved);
177 fprintf(stderr, "[poll%4dms]\n", diff);
178 m_timeSaved = timeNow;
179}
180
181#endif
182
183//
184// Search for changed rectangles on the screen.
185//
186
187void PollingManager::poll()
188{
189#ifdef DEBUG
190 debugBeforePoll();
191#endif
192
193 // First step: full-screen polling.
194
195 bool changes1 = false;
196
197 switch((int)pollingType) {
198 case 0:
199 changes1 = poll_Dumb();
200 break;
201 case 1:
202 changes1 = poll_Traditional();
203 break;
204 case 2:
205 changes1 = poll_SkipCycles();
206 break;
207//case 3:
208 default:
209 changes1 = poll_DetectVideo();
210 break;
211 }
212
213 // Second step: optional thorough polling of the area around the pointer.
214 // We do that only if the pointer position is known and was set recently.
215
216 bool changes2 = false;
217 if (pollPointer) {
218 if (m_pointerPosKnown && time(NULL) - m_pointerPosTime >= 5) {
219 unsetPointerPos();
220 }
221 if (m_pointerPosKnown) {
222 changes2 = pollPointerArea();
223 }
224 }
225
226 // Update if needed.
227
228 if (changes1 || changes2)
229 m_server->tryUpdate();
230
231#ifdef DEBUG
232 debugAfterPoll();
233#endif
234}
235
236bool PollingManager::poll_DetectVideo()
237{
238 if (!m_server)
239 return false;
240
241 const int GRAND_STEP_DIVISOR = 8;
242 const int VIDEO_THRESHOLD_0 = 3;
243 const int VIDEO_THRESHOLD_1 = 5;
244
245 bool grandStep = (m_pollingStep % GRAND_STEP_DIVISOR == 0);
246
247 // FIXME: Save shortcuts in member variables?
248 int scanLine = m_pollingOrder[m_pollingStep++ % 32];
249 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
250 int bytesPerLine = m_image->xim->bytes_per_line;
251
252 Rect rect;
253 int nTilesChanged = 0;
254 int idx = 0;
255
256 for (int y = 0; y * 32 < m_height; y++) {
257 int tile_h = (m_height - y * 32 >= 32) ? 32 : m_height - y * 32;
258 if (scanLine >= tile_h)
259 break;
260 int scan_y = y * 32 + scanLine;
261 getRow(scan_y);
262 char *ptr_old = m_image->xim->data + scan_y * bytesPerLine;
263 char *ptr_new = m_rowImage->xim->data;
264 for (int x = 0; x * 32 < m_width; x++) {
265 int tile_w = (m_width - x * 32 >= 32) ? 32 : m_width - x * 32;
266 int nBytes = tile_w * bytesPerPixel;
267
268 char wasChanged = (memcmp(ptr_old, ptr_new, nBytes) != 0);
269 m_rateMatrix[idx] += wasChanged;
270
271 if (grandStep) {
272 if (m_rateMatrix[idx] <= VIDEO_THRESHOLD_0) {
273 m_videoFlags[idx] = 0;
274 } else if (m_rateMatrix[idx] >= VIDEO_THRESHOLD_1) {
275 m_videoFlags[idx] = 1;
276 }
277 m_rateMatrix[idx] = 0;
278 }
279
280 m_changedFlags[idx] |= wasChanged;
281 if ( m_changedFlags[idx] && (!m_videoFlags[idx] || grandStep) ) {
282 getTile32(x, y, tile_w, tile_h);
283 m_image->updateRect(m_tileImage, x * 32, y * 32);
284 rect.setXYWH(x * 32, y * 32, tile_w, tile_h);
285 m_server->add_changed(rect);
286 nTilesChanged++;
287 m_changedFlags[idx] = 0;
288 }
289
290 ptr_old += nBytes;
291 ptr_new += nBytes;
292 idx++;
293 }
294 }
295
296 if (grandStep)
297 adjustVideoArea();
298
Constantin Kaplinsky344c3fe2007-07-24 08:35:43 +0000299#ifdef DEBUG
300 if (nTilesChanged != 0) {
301 fprintf(stderr, "#%d# ", nTilesChanged);
302 }
303#endif
304
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000305 return (nTilesChanged != 0);
306}
307
308bool PollingManager::poll_SkipCycles()
309{
310 if (!m_server)
311 return false;
312
313 enum {
314 NOT_CHANGED, CHANGED_ONCE, CHANGED_AGAIN
315 };
316
317 bool grandStep = (m_pollingStep % 8 == 0);
318
319 int nTilesChanged = 0;
320 int scanLine = m_pollingOrder[m_pollingStep++ % 32];
321 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
322 int bytesPerLine = m_image->xim->bytes_per_line;
323 char *pstatus = m_statusMatrix;
324 bool wasChanged;
325 Rect rect;
326
327 for (int y = 0; y * 32 < m_height; y++) {
328 int tile_h = (m_height - y * 32 >= 32) ? 32 : m_height - y * 32;
329 if (scanLine >= tile_h)
330 scanLine %= tile_h;
331 int scan_y = y * 32 + scanLine;
332 getRow(scan_y);
333 char *ptr_old = m_image->xim->data + scan_y * bytesPerLine;
334 char *ptr_new = m_rowImage->xim->data;
335 for (int x = 0; x * 32 < m_width; x++) {
336 int tile_w = (m_width - x * 32 >= 32) ? 32 : m_width - x * 32;
337 int nBytes = tile_w * bytesPerPixel;
338
339 if (grandStep || *pstatus != CHANGED_AGAIN) {
340 wasChanged = (*pstatus == CHANGED_AGAIN) ?
341 true : (memcmp(ptr_old, ptr_new, nBytes) != 0);
342 if (wasChanged) {
343 if (grandStep || *pstatus == NOT_CHANGED) {
344 getTile32(x, y, tile_w, tile_h);
345 m_image->updateRect(m_tileImage, x * 32, y * 32);
346 rect.setXYWH(x * 32, y * 32, tile_w, tile_h);
347 m_server->add_changed(rect);
348 nTilesChanged++;
349 *pstatus = CHANGED_ONCE;
350 } else {
351 *pstatus = CHANGED_AGAIN;
352 }
353 } else if (grandStep) {
354 *pstatus = NOT_CHANGED;
355 }
356 }
357
358 ptr_old += nBytes;
359 ptr_new += nBytes;
360 pstatus++;
361 }
362 }
363
364 return (nTilesChanged != 0);
365}
366
367bool PollingManager::poll_Traditional()
368{
369 if (!m_server)
370 return false;
371
372 int nTilesChanged = 0;
373 int scanLine = m_pollingOrder[m_pollingStep++ % 32];
374 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
375 int bytesPerLine = m_image->xim->bytes_per_line;
376 Rect rect;
377
378 for (int y = 0; y * 32 < m_height; y++) {
379 int tile_h = (m_height - y * 32 >= 32) ? 32 : m_height - y * 32;
380 if (scanLine >= tile_h)
381 break;
382 int scan_y = y * 32 + scanLine;
383 getRow(scan_y);
384 char *ptr_old = m_image->xim->data + scan_y * bytesPerLine;
385 char *ptr_new = m_rowImage->xim->data;
386 for (int x = 0; x * 32 < m_width; x++) {
387 int tile_w = (m_width - x * 32 >= 32) ? 32 : m_width - x * 32;
388 int nBytes = tile_w * bytesPerPixel;
389 if (memcmp(ptr_old, ptr_new, nBytes)) {
390 getTile32(x, y, tile_w, tile_h);
391 m_image->updateRect(m_tileImage, x * 32, y * 32);
392 rect.setXYWH(x * 32, y * 32, tile_w, tile_h);
393 m_server->add_changed(rect);
394 nTilesChanged++;
395 }
396 ptr_old += nBytes;
397 ptr_new += nBytes;
398 }
399 }
400
401 return (nTilesChanged != 0);
402}
403
404//
405// Simplest polling method, from the original x0vncserver of VNC4.
406//
407
408bool PollingManager::poll_Dumb()
409{
410 if (!m_server)
411 return false;
412
413 getScreen();
414 Rect rect(0, 0, m_width, m_height);
415 m_server->add_changed(rect);
416
417 // Report that some changes have been detected.
418 return true;
419}
420
421//
422// Compute coordinates of the rectangle around the pointer.
423//
424// ASSUMES: (m_pointerPosKnown != false)
425//
426
427void PollingManager::computePointerArea(Rect *r)
428{
429 int x = m_pointerPos.x - 64;
430 int y = m_pointerPos.y - 64;
431 int w = 128;
432 int h = 128;
433 if (x < 0) {
434 w += x; x = 0;
435 }
436 if (x + w > m_width) {
437 w = m_width - x;
438 }
439 if (y < 0) {
440 h += y; y = 0;
441 }
442 if (y + h > m_height) {
443 h = m_height - y;
444 }
445
446 r->setXYWH(x, y, w, h);
447}
448
449//
450// Poll the area under current pointer position. Each pixel of the
451// area should be compared. Using such polling option gives higher
452// priority to screen area under the pointer.
453//
454// ASSUMES: (m_server != NULL && m_pointerPosKnown != false)
455//
456
457bool PollingManager::pollPointerArea()
458{
459 Rect r;
460 computePointerArea(&r);
461
462 // Shortcuts for coordinates.
463 int x = r.tl.x, y = r.tl.y;
464 int w = r.width(), h = r.height();
465
466 // Get new pixels.
467 getArea128(x, y, w, h);
468
469 // Now, try to minimize the rectangle by cutting out unchanged
470 // borders (at top and bottom).
471 //
472 // FIXME: Perhaps we should work on 32x32 tiles (properly aligned)
473 // to produce a region instead of a rectangle. If there would
474 // be just one universal polling algorithm, it could be
475 // better to integrate pointer area polling into that
476 // algorithm, instead of a separate pollPointerArea()
477 // function.
478
479 // Shortcuts.
480 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
481 int oldBytesPerLine = m_image->xim->bytes_per_line;
482 int newBytesPerLine = m_areaImage->xim->bytes_per_line;
483 char *oldPtr = m_image->xim->data + y * oldBytesPerLine + x * bytesPerPixel;
484 char *newPtr = m_areaImage->xim->data;
485
486 // Check and cut out unchanged rows at the top.
487 int ty;
488 for (ty = 0; ty < h; ty++) {
489 if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0)
490 break;
491 oldPtr += oldBytesPerLine;
492 newPtr += newBytesPerLine;
493 }
494 if (ty == h) {
495 return false; // no changes at all
496 }
497 y += ty; h -= ty;
498
499 // Check and cut out unchanged rows at the bottom.
500 oldPtr = m_image->xim->data + (y+h-1) * oldBytesPerLine + x * bytesPerPixel;
501 newPtr = m_areaImage->xim->data + (ty+h-1) * newBytesPerLine;
502 int by;
503 for (by = 0; by < h - 1; by++) {
504 if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0)
505 break;
506 oldPtr -= oldBytesPerLine;
507 newPtr -= newBytesPerLine;
508 }
509 h -= by;
510
511 // Copy pixels.
512 m_image->updateRect(m_areaImage, x, y, 0, ty, w, h);
513
514 // Report updates to the server.
515 Rect rect(x, y, x+w, y+h);
516 m_server->add_changed(rect);
517 return true;
518}
519
520//
521// Make video area pattern more regular.
522//
523// FIXME: Replace the above with a normal comment.
524// FIXME: Is the function efficient enough?
525//
526
527void PollingManager::adjustVideoArea()
528{
529 char newFlags[m_widthTiles * m_heightTiles];
530 char *ptr = newFlags;
531 int x, y;
532
533 // DEBUG:
534 // int nVideoTiles = 0;
535
536 for (y = 0; y < m_heightTiles; y++) {
537 for (x = 0; x < m_widthTiles; x++) {
538
539 // DEBUG:
540 // nVideoTiles += m_videoFlags[y * m_widthTiles + x];
541
542 int weightedSum = 0, n;
543 if (y > 0 && x > 0) {
544 n = (m_videoFlags[ y * m_widthTiles + (x-1)] +
545 m_videoFlags[(y-1) * m_widthTiles + (x-1)] +
546 m_videoFlags[(y-1) * m_widthTiles + x ]);
547 if (n == 3) {
548 *ptr++ = 1;
549 continue;
550 }
551 weightedSum += n;
552 }
553 if (y > 0 && x < m_widthTiles - 1) {
554 n = (m_videoFlags[ y * m_widthTiles + (x+1)] +
555 m_videoFlags[(y-1) * m_widthTiles + (x+1)] +
556 m_videoFlags[(y-1) * m_widthTiles + x ]);
557 if (n == 3) {
558 *ptr++ = 1;
559 continue;
560 }
561 weightedSum += n;
562 }
563 if (y < m_heightTiles - 1 && x > 0) {
564 n = (m_videoFlags[ y * m_widthTiles + (x-1)] +
565 m_videoFlags[(y+1) * m_widthTiles + (x-1)] +
566 m_videoFlags[(y+1) * m_widthTiles + x ]);
567 if (n == 3) {
568 *ptr++ = 1;
569 continue;
570 }
571 weightedSum += n;
572 }
573 if (y < m_heightTiles - 1 && x < m_widthTiles - 1) {
574 n = (m_videoFlags[ y * m_widthTiles + (x+1)] +
575 m_videoFlags[(y+1) * m_widthTiles + (x+1)] +
576 m_videoFlags[(y+1) * m_widthTiles + x ]);
577 if (n == 3) {
578 *ptr++ = 1;
579 continue;
580 }
581 weightedSum += n;
582 }
583 *ptr++ = (weightedSum <= 3) ? 0 : m_videoFlags[y * m_widthTiles + x];
584 }
585 }
586
587 /*
588 /// DEBUG: ------------------------------------------------------
589 if (nVideoTiles) {
590 for (y = 0; y < m_heightTiles; y++) {
591 for (x = 0; x < m_widthTiles; x++) {
592 printf("%c", m_videoFlags[y * m_widthTiles + x] ? '@' : ':');
593 }
594 printf(" ");
595 for (x = 0; x < m_widthTiles; x++) {
596 printf("%c", newFlags[y * m_widthTiles + x] ? '@' : ':');
597 }
598 printf("\n");
599 }
600 printf("\n");
601 }
602 /// -------------------------------------------------------------
603 */
604
605 memcpy(m_videoFlags, newFlags, m_widthTiles * m_heightTiles);
606}