blob: 46e592c143f783352b1bd1770a5a5b4b504778bc [file] [log] [blame]
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +00001/* Copyright (C) 2004-2007 Constantin Kaplinsky. All Rights Reserved.
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +00002 *
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
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000022#include <stdio.h>
23#include <string.h>
24#include <time.h>
25#include <X11/Xlib.h>
26#include <rfb/LogWriter.h>
27#include <rfb/VNCServer.h>
28#include <rfb/Configuration.h>
29#include <rfb/ServerCore.h>
30
31#include <x0vncserver/PollingManager.h>
32
33static LogWriter vlog("PollingMgr");
34
35BoolParameter PollingManager::pollPointer
36("PollPointer",
37 "DEBUG: Poll area under the pointer with higher priority",
Constantin Kaplinsky344c3fe2007-07-24 08:35:43 +000038 false);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000039
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000040const int PollingManager::m_pollingOrder[32] = {
41 0, 16, 8, 24, 4, 20, 12, 28,
42 10, 26, 18, 2, 22, 6, 30, 14,
43 1, 17, 9, 25, 7, 23, 15, 31,
44 19, 3, 27, 11, 29, 13, 5, 21
45};
46
47//
48// Constructor.
49//
50// Note that dpy and image should remain valid during the object
51// lifetime, while factory is used only in the constructor itself.
52//
53
54PollingManager::PollingManager(Display *dpy, Image *image,
55 ImageFactory *factory,
56 int offsetLeft, int offsetTop)
57 : m_dpy(dpy), m_server(0), m_image(image),
58 m_offsetLeft(offsetLeft), m_offsetTop(offsetTop),
59 m_pointerPosKnown(false), m_pollingStep(0)
60{
61 // Save width and height of the screen (and the image).
62 m_width = m_image->xim->width;
63 m_height = m_image->xim->height;
64
65 // Compute width and height in 32x32 tiles.
66 m_widthTiles = (m_width + 31) / 32;
67 m_heightTiles = (m_height + 31) / 32;
68
69 // Get initial screen image.
70 m_image->get(DefaultRootWindow(m_dpy), m_offsetLeft, m_offsetTop);
71
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +000072 // Create additional images used in polling algorithm, warn if
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000073 // underlying class names are different from the class name of the
74 // primary image.
75 m_rowImage = factory->newImage(m_dpy, m_width, 1);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000076 m_areaImage = factory->newImage(m_dpy, 128, 128);
77 if (strcmp(m_image->className(), m_rowImage->className()) != 0 ||
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000078 strcmp(m_image->className(), m_areaImage->className()) != 0) {
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +000079 vlog.error("Image types do not match (%s, %s)",
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000080 m_rowImage->className(),
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000081 m_areaImage->className());
82 }
83
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000084 int numTiles = m_widthTiles * m_heightTiles;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000085 m_rateMatrix = new char[numTiles];
86 m_videoFlags = new char[numTiles];
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000087 memset(m_rateMatrix, 0, numTiles);
88 memset(m_videoFlags, 0, numTiles);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000089}
90
91PollingManager::~PollingManager()
92{
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000093 delete[] m_videoFlags;
94 delete[] m_rateMatrix;
95
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000096 delete m_areaImage;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000097 delete m_rowImage;
98}
99
100//
101// Register VNCServer object.
102//
103
104void PollingManager::setVNCServer(VNCServer *s)
105{
106 m_server = s;
107}
108
109//
110// Update current pointer position which may be used as a hint for
111// polling algorithms.
112//
113
114void PollingManager::setPointerPos(const Point &pos)
115{
116 m_pointerPosTime = time(NULL);
117 m_pointerPos = pos;
118 m_pointerPosKnown = true;
119}
120
121//
122// Indicate that current pointer position is unknown.
123//
124
125void PollingManager::unsetPointerPos()
126{
127 m_pointerPosKnown = false;
128}
129
130//
131// DEBUG: Measuring time spent in the poll() function,
132// as well as time intervals between poll() calls.
133//
134
135#ifdef DEBUG
136void PollingManager::debugBeforePoll()
137{
138 TimeMillis timeNow;
139 int diff = timeNow.diffFrom(m_timeSaved);
140 fprintf(stderr, "[wait%4dms]\t[step %2d]\t", diff, m_pollingStep % 32);
141 m_timeSaved = timeNow;
142}
143
144void PollingManager::debugAfterPoll()
145{
146 TimeMillis timeNow;
147 int diff = timeNow.diffFrom(m_timeSaved);
148 fprintf(stderr, "[poll%4dms]\n", diff);
149 m_timeSaved = timeNow;
150}
151
152#endif
153
154//
155// Search for changed rectangles on the screen.
156//
157
158void PollingManager::poll()
159{
160#ifdef DEBUG
161 debugBeforePoll();
162#endif
163
164 // First step: full-screen polling.
165
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +0000166 bool changes1 = pollScreen();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000167
168 // Second step: optional thorough polling of the area around the pointer.
169 // We do that only if the pointer position is known and was set recently.
170
171 bool changes2 = false;
172 if (pollPointer) {
173 if (m_pointerPosKnown && time(NULL) - m_pointerPosTime >= 5) {
174 unsetPointerPos();
175 }
176 if (m_pointerPosKnown) {
177 changes2 = pollPointerArea();
178 }
179 }
180
181 // Update if needed.
182
183 if (changes1 || changes2)
184 m_server->tryUpdate();
185
186#ifdef DEBUG
187 debugAfterPoll();
188#endif
189}
190
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +0000191bool PollingManager::pollScreen()
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000192{
193 if (!m_server)
194 return false;
195
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000196 // mxChanged[] array will hold boolean values corresponding to each
197 // 32x32 tile. If a value is true, then we've detected a change in
Constantin Kaplinsky5e2f69f2007-10-07 13:08:18 +0000198 // that tile. Initially, we fill in the array with zero values.
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000199 bool *mxChanged = new bool[m_widthTiles * m_heightTiles];
Constantin Kaplinskyf25307a2007-10-08 13:49:44 +0000200 memset(mxChanged, 0, m_widthTiles * m_heightTiles * sizeof(bool));
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000201
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000202 // First pass over the framebuffer. Here we scan 1/32 part of the
203 // framebuffer -- that is, one line in each (32 * m_width) stripe.
204 // We compare the pixels of that line with previous framebuffer
205 // contents and raise corresponding member values of mxChanged[].
206 int scanOffset = m_pollingOrder[m_pollingStep++ % 32];
207 bool *pmxChanged = mxChanged;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000208 int nTilesChanged = 0;
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000209 for (int y = scanOffset; y < m_height; y += 32) {
210 nTilesChanged += checkRow(0, y, m_width, pmxChanged);
211 pmxChanged += m_widthTiles;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000212 }
213
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000214 // Do the work related to video area detection.
215 bool videoDetected = detectVideo(mxChanged);
216
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000217 // Inform the server about the changes.
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000218 if (nTilesChanged)
219 sendChanges(mxChanged);
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000220
221 // Cleanup.
222 delete[] mxChanged;
223
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000224#ifdef DEBUG
225 if (nTilesChanged != 0) {
226 fprintf(stderr, "#%d# ", nTilesChanged);
227 }
228#endif
229
230 return (nTilesChanged != 0 || videoDetected);
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000231}
232
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000233int PollingManager::checkRow(int x, int y, int w, bool *pmxChanged)
234{
235 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
236 int bytesPerLine = m_image->xim->bytes_per_line;
237
Constantin Kaplinsky29d32052007-10-08 14:16:02 +0000238 if (x == 0 && w == m_width) {
239 getFullRow(y); // use more efficient method if possible
240 } else {
241 getRow(x, y, w);
242 }
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000243
244 char *ptr_old = m_image->xim->data + y * bytesPerLine + x * bytesPerPixel;
245 char *ptr_new = m_rowImage->xim->data;
246
247 int nTilesChanged = 0;
248 for (int i = 0; i < (w + 31) / 32; i++) {
249 int tile_w = (w - i * 32 >= 32) ? 32 : w - i * 32;
250 int nBytes = tile_w * bytesPerPixel;
251 if (memcmp(ptr_old, ptr_new, nBytes)) {
252 *pmxChanged = true;
253 nTilesChanged++;
254 }
255 pmxChanged++;
256 ptr_old += nBytes;
257 ptr_new += nBytes;
258 }
259
260 return nTilesChanged;
261}
262
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000263void PollingManager::sendChanges(bool *pmxChanged)
264{
265 Rect rect;
266 for (int y = 0; y < m_heightTiles; y++) {
267 for (int x = 0; x < m_widthTiles; x++) {
268 if (*pmxChanged++) {
269 // Count successive tiles marked as changed.
270 int count = 1;
271 while (x + count < m_widthTiles && *pmxChanged++) {
272 count++;
273 }
274 // Compute the coordinates and the size of this band.
275 rect.setXYWH(x * 32, y * 32, count * 32, 32);
276 if (rect.br.x > m_width)
277 rect.br.x = m_width;
278 if (rect.br.y > m_height)
279 rect.br.y = m_height;
280 // Add to the changed region maintained by the server.
281 getScreenRect(rect);
282 m_server->add_changed(rect);
283 // Skip processed tiles.
284 x += count;
285 }
286 }
287 }
288}
289
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000290bool PollingManager::detectVideo(bool *pmxChanged)
291{
292 // Configurable parameters.
293 const int VIDEO_THRESHOLD_0 = 3;
294 const int VIDEO_THRESHOLD_1 = 5;
295
296 // Each call: update counters in m_rateMatrix.
297 int numTiles = m_heightTiles * m_widthTiles;
298 for (int i = 0; i < numTiles; i++)
299 m_rateMatrix[i] += (pmxChanged[i] != false);
300
301 // Once per eight calls: detect video region. In other words, mark a
302 // region that consists of continuously changing pixels. Save the
303 // result in m_videoFlags[] and reset counters in m_rateMatrix[].
304 bool isGrandStep = (m_pollingStep % 8 == 0);
305 if (isGrandStep) {
306 for (int i = 0; i < numTiles; i++) {
307 if (m_rateMatrix[i] <= VIDEO_THRESHOLD_0) {
308 m_videoFlags[i] = 0;
309 } else if (m_rateMatrix[i] >= VIDEO_THRESHOLD_1) {
310 m_videoFlags[i] = 1;
311 }
312 m_rateMatrix[i] = 0;
313 }
314 }
315
316 // Choose the biggest rectangle from the region defined by
317 // m_videoFlags[].
318 Rect r;
319 getVideoAreaRect(&r);
320
321 // Exclude video rectangle from pmxChanged[].
322 for (int y = r.tl.y / 32; y < r.br.y / 32; y++) {
323 for (int x = r.tl.x / 32; x < r.br.x / 32; x++) {
324 pmxChanged[y * m_widthTiles + x] = false;
325 }
326 }
327
328 // Inform the server...
329 m_server->set_video_area(r);
330 if (!r.is_empty())
331 getScreenRect(r);
332
333 return (!r.is_empty());
334}
335
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000336//
337// Compute coordinates of the rectangle around the pointer.
338//
339// ASSUMES: (m_pointerPosKnown != false)
340//
341
342void PollingManager::computePointerArea(Rect *r)
343{
344 int x = m_pointerPos.x - 64;
345 int y = m_pointerPos.y - 64;
346 int w = 128;
347 int h = 128;
348 if (x < 0) {
349 w += x; x = 0;
350 }
351 if (x + w > m_width) {
352 w = m_width - x;
353 }
354 if (y < 0) {
355 h += y; y = 0;
356 }
357 if (y + h > m_height) {
358 h = m_height - y;
359 }
360
361 r->setXYWH(x, y, w, h);
362}
363
364//
365// Poll the area under current pointer position. Each pixel of the
366// area should be compared. Using such polling option gives higher
367// priority to screen area under the pointer.
368//
369// ASSUMES: (m_server != NULL && m_pointerPosKnown != false)
370//
371
372bool PollingManager::pollPointerArea()
373{
374 Rect r;
375 computePointerArea(&r);
376
377 // Shortcuts for coordinates.
378 int x = r.tl.x, y = r.tl.y;
379 int w = r.width(), h = r.height();
380
381 // Get new pixels.
382 getArea128(x, y, w, h);
383
384 // Now, try to minimize the rectangle by cutting out unchanged
385 // borders (at top and bottom).
386 //
387 // FIXME: Perhaps we should work on 32x32 tiles (properly aligned)
388 // to produce a region instead of a rectangle. If there would
389 // be just one universal polling algorithm, it could be
390 // better to integrate pointer area polling into that
391 // algorithm, instead of a separate pollPointerArea()
392 // function.
393
394 // Shortcuts.
395 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
396 int oldBytesPerLine = m_image->xim->bytes_per_line;
397 int newBytesPerLine = m_areaImage->xim->bytes_per_line;
398 char *oldPtr = m_image->xim->data + y * oldBytesPerLine + x * bytesPerPixel;
399 char *newPtr = m_areaImage->xim->data;
400
401 // Check and cut out unchanged rows at the top.
402 int ty;
403 for (ty = 0; ty < h; ty++) {
404 if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0)
405 break;
406 oldPtr += oldBytesPerLine;
407 newPtr += newBytesPerLine;
408 }
409 if (ty == h) {
410 return false; // no changes at all
411 }
412 y += ty; h -= ty;
413
414 // Check and cut out unchanged rows at the bottom.
415 oldPtr = m_image->xim->data + (y+h-1) * oldBytesPerLine + x * bytesPerPixel;
416 newPtr = m_areaImage->xim->data + (ty+h-1) * newBytesPerLine;
417 int by;
418 for (by = 0; by < h - 1; by++) {
419 if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0)
420 break;
421 oldPtr -= oldBytesPerLine;
422 newPtr -= newBytesPerLine;
423 }
424 h -= by;
425
426 // Copy pixels.
427 m_image->updateRect(m_areaImage, x, y, 0, ty, w, h);
428
429 // Report updates to the server.
430 Rect rect(x, y, x+w, y+h);
431 m_server->add_changed(rect);
432 return true;
433}
434
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000435void
436PollingManager::getVideoAreaRect(Rect *result)
437{
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000438 int *mx_hlen, *mx_vlen;
439 constructLengthMatrices(&mx_hlen, &mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000440
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000441 int full_h = m_heightTiles;
442 int full_w = m_widthTiles;
443 int x, y;
444 Rect max_rect(0, 0, 0, 0);
445 Rect local_rect;
446
447 for (y = 0; y < full_h; y++) {
448 for (x = 0; x < full_w; x++) {
449 int max_w = mx_hlen[y * full_w + x];
450 int max_h = mx_vlen[y * full_w + x];
451 if (max_w > 2 && max_h > 1 && max_h * max_w > (int)max_rect.area()) {
452 local_rect.tl.x = x;
453 local_rect.tl.y = y;
454 findMaxLocalRect(&local_rect, mx_hlen, mx_vlen);
455 if (local_rect.area() > max_rect.area()) {
456 max_rect = local_rect;
457 }
458 }
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000459 }
460 }
461
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000462 destroyLengthMatrices(mx_hlen, mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000463
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000464 max_rect.tl.x *= 32;
465 max_rect.tl.y *= 32;
466 max_rect.br.x *= 32;
467 max_rect.br.y *= 32;
468 if (max_rect.br.x > m_width)
469 max_rect.br.x = m_width;
470 if (max_rect.br.y > m_height)
471 max_rect.br.y = m_height;
472 *result = max_rect;
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000473
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000474 if (!result->is_empty()) {
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000475 fprintf(stderr, "Video rect %dx%d\tat(%d,%d)\n",
476 result->width(), result->height(), result->tl.x, result->tl.y);
477 }
478}
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000479
480void
481PollingManager::constructLengthMatrices(int **pmx_h, int **pmx_v)
482{
483 // Handy shortcuts.
484 int h = m_heightTiles;
485 int w = m_widthTiles;
486
487 // Allocate memory.
488 int *mx_h = new int[h * w];
489 memset(mx_h, 0, h * w * sizeof(int));
490 int *mx_v = new int[h * w];
491 memset(mx_v, 0, h * w * sizeof(int));
492
493 int x, y, len, i;
494
495 // Fill in horizontal length matrix.
496 for (y = 0; y < h; y++) {
497 for (x = 0; x < w; x++) {
498 len = 0;
499 while (x + len < w && m_videoFlags[y * w + x + len]) {
500 len++;
501 }
502 for (i = 0; i < len; i++) {
503 mx_h[y * w + x + i] = len - i;
504 }
505 x += len;
506 }
507 }
508
509 // Fill in vertical length matrix.
510 for (x = 0; x < w; x++) {
511 for (y = 0; y < h; y++) {
512 len = 0;
513 while (y + len < h && m_videoFlags[(y + len) * w + x]) {
514 len++;
515 }
516 for (i = 0; i < len; i++) {
517 mx_v[(y + i) * w + x] = len - i;
518 }
519 y += len;
520 }
521 }
522
523 *pmx_h = mx_h;
524 *pmx_v = mx_v;
525}
526
527void
528PollingManager::destroyLengthMatrices(int *mx_h, int *mx_v)
529{
530 delete[] mx_h;
531 delete[] mx_v;
532}
533
534// NOTE: This function assumes that current tile has non-zero in mx_h[],
535// otherwise we get division by zero.
536void
537PollingManager::findMaxLocalRect(Rect *r, int mx_h[], int mx_v[])
538{
539 int idx = r->tl.y * m_widthTiles + r->tl.x;
540
541 // NOTE: Rectangle's maximum width and height are 25 and 18
542 // (in tiles, where each tile is usually 32x32 pixels).
543 int max_w = mx_h[idx];
544 if (max_w > 25)
545 max_w = 25;
546 int cur_h = 18;
547
548 int best_w = max_w;
549 int best_area = 1 * best_w;
550
551 for (int i = 0; i < max_w; i++) {
552 int h = mx_v[idx + i];
553 if (h < cur_h) {
554 cur_h = h;
555 if (cur_h * max_w <= best_area)
556 break;
557 }
558 if (cur_h * (i + 1) > best_area) {
559 best_w = i + 1;
560 best_area = cur_h * best_w;
561 }
562 }
563
564 r->br.x = r->tl.x + best_w;
565 r->br.y = r->tl.y + best_area / best_w;
566}
567