blob: d8879ebbaf6beec97cadef744a793a41f3562224 [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];
87 m_changedFlags = new char[numTiles];
88 memset(m_rateMatrix, 0, numTiles);
89 memset(m_videoFlags, 0, numTiles);
90 memset(m_changedFlags, 0, numTiles);
91}
92
93PollingManager::~PollingManager()
94{
95 delete[] m_changedFlags;
96 delete[] m_videoFlags;
97 delete[] m_rateMatrix;
98
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000099 delete m_areaImage;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000100 delete m_rowImage;
101}
102
103//
104// Register VNCServer object.
105//
106
107void PollingManager::setVNCServer(VNCServer *s)
108{
109 m_server = s;
110}
111
112//
113// Update current pointer position which may be used as a hint for
114// polling algorithms.
115//
116
117void PollingManager::setPointerPos(const Point &pos)
118{
119 m_pointerPosTime = time(NULL);
120 m_pointerPos = pos;
121 m_pointerPosKnown = true;
122}
123
124//
125// Indicate that current pointer position is unknown.
126//
127
128void PollingManager::unsetPointerPos()
129{
130 m_pointerPosKnown = false;
131}
132
133//
134// DEBUG: Measuring time spent in the poll() function,
135// as well as time intervals between poll() calls.
136//
137
138#ifdef DEBUG
139void PollingManager::debugBeforePoll()
140{
141 TimeMillis timeNow;
142 int diff = timeNow.diffFrom(m_timeSaved);
143 fprintf(stderr, "[wait%4dms]\t[step %2d]\t", diff, m_pollingStep % 32);
144 m_timeSaved = timeNow;
145}
146
147void PollingManager::debugAfterPoll()
148{
149 TimeMillis timeNow;
150 int diff = timeNow.diffFrom(m_timeSaved);
151 fprintf(stderr, "[poll%4dms]\n", diff);
152 m_timeSaved = timeNow;
153}
154
155#endif
156
157//
158// Search for changed rectangles on the screen.
159//
160
161void PollingManager::poll()
162{
163#ifdef DEBUG
164 debugBeforePoll();
165#endif
166
167 // First step: full-screen polling.
168
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +0000169 bool changes1 = pollScreen();
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000170
171 // Second step: optional thorough polling of the area around the pointer.
172 // We do that only if the pointer position is known and was set recently.
173
174 bool changes2 = false;
175 if (pollPointer) {
176 if (m_pointerPosKnown && time(NULL) - m_pointerPosTime >= 5) {
177 unsetPointerPos();
178 }
179 if (m_pointerPosKnown) {
180 changes2 = pollPointerArea();
181 }
182 }
183
184 // Update if needed.
185
186 if (changes1 || changes2)
187 m_server->tryUpdate();
188
189#ifdef DEBUG
190 debugAfterPoll();
191#endif
192}
193
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +0000194bool PollingManager::pollScreen()
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000195{
196 if (!m_server)
197 return false;
198
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000199 // mxChanged[] array will hold boolean values corresponding to each
200 // 32x32 tile. If a value is true, then we've detected a change in
Constantin Kaplinsky5e2f69f2007-10-07 13:08:18 +0000201 // that tile. Initially, we fill in the array with zero values.
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000202 bool *mxChanged = new bool[m_widthTiles * m_heightTiles];
Constantin Kaplinskyf25307a2007-10-08 13:49:44 +0000203 memset(mxChanged, 0, m_widthTiles * m_heightTiles * sizeof(bool));
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000204
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000205 // First pass over the framebuffer. Here we scan 1/32 part of the
206 // framebuffer -- that is, one line in each (32 * m_width) stripe.
207 // We compare the pixels of that line with previous framebuffer
208 // contents and raise corresponding member values of mxChanged[].
209 int scanOffset = m_pollingOrder[m_pollingStep++ % 32];
210 bool *pmxChanged = mxChanged;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000211 int nTilesChanged = 0;
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000212 for (int y = scanOffset; y < m_height; y += 32) {
213 nTilesChanged += checkRow(0, y, m_width, pmxChanged);
214 pmxChanged += m_widthTiles;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000215 }
216
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000217 // Do the work related to video area detection.
218 bool videoDetected = detectVideo(mxChanged);
219
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000220 // Inform the server about the changes.
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000221 if (nTilesChanged)
222 sendChanges(mxChanged);
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000223
224 // Cleanup.
225 delete[] mxChanged;
226
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000227#ifdef DEBUG
228 if (nTilesChanged != 0) {
229 fprintf(stderr, "#%d# ", nTilesChanged);
230 }
231#endif
232
233 return (nTilesChanged != 0 || videoDetected);
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000234}
235
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000236int PollingManager::checkRow(int x, int y, int w, bool *pmxChanged)
237{
238 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
239 int bytesPerLine = m_image->xim->bytes_per_line;
240
Constantin Kaplinsky29d32052007-10-08 14:16:02 +0000241 if (x == 0 && w == m_width) {
242 getFullRow(y); // use more efficient method if possible
243 } else {
244 getRow(x, y, w);
245 }
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000246
247 char *ptr_old = m_image->xim->data + y * bytesPerLine + x * bytesPerPixel;
248 char *ptr_new = m_rowImage->xim->data;
249
250 int nTilesChanged = 0;
251 for (int i = 0; i < (w + 31) / 32; i++) {
252 int tile_w = (w - i * 32 >= 32) ? 32 : w - i * 32;
253 int nBytes = tile_w * bytesPerPixel;
254 if (memcmp(ptr_old, ptr_new, nBytes)) {
255 *pmxChanged = true;
256 nTilesChanged++;
257 }
258 pmxChanged++;
259 ptr_old += nBytes;
260 ptr_new += nBytes;
261 }
262
263 return nTilesChanged;
264}
265
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000266void PollingManager::sendChanges(bool *pmxChanged)
267{
268 Rect rect;
269 for (int y = 0; y < m_heightTiles; y++) {
270 for (int x = 0; x < m_widthTiles; x++) {
271 if (*pmxChanged++) {
272 // Count successive tiles marked as changed.
273 int count = 1;
274 while (x + count < m_widthTiles && *pmxChanged++) {
275 count++;
276 }
277 // Compute the coordinates and the size of this band.
278 rect.setXYWH(x * 32, y * 32, count * 32, 32);
279 if (rect.br.x > m_width)
280 rect.br.x = m_width;
281 if (rect.br.y > m_height)
282 rect.br.y = m_height;
283 // Add to the changed region maintained by the server.
284 getScreenRect(rect);
285 m_server->add_changed(rect);
286 // Skip processed tiles.
287 x += count;
288 }
289 }
290 }
291}
292
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000293bool PollingManager::detectVideo(bool *pmxChanged)
294{
295 // Configurable parameters.
296 const int VIDEO_THRESHOLD_0 = 3;
297 const int VIDEO_THRESHOLD_1 = 5;
298
299 // Each call: update counters in m_rateMatrix.
300 int numTiles = m_heightTiles * m_widthTiles;
301 for (int i = 0; i < numTiles; i++)
302 m_rateMatrix[i] += (pmxChanged[i] != false);
303
304 // Once per eight calls: detect video region. In other words, mark a
305 // region that consists of continuously changing pixels. Save the
306 // result in m_videoFlags[] and reset counters in m_rateMatrix[].
307 bool isGrandStep = (m_pollingStep % 8 == 0);
308 if (isGrandStep) {
309 for (int i = 0; i < numTiles; i++) {
310 if (m_rateMatrix[i] <= VIDEO_THRESHOLD_0) {
311 m_videoFlags[i] = 0;
312 } else if (m_rateMatrix[i] >= VIDEO_THRESHOLD_1) {
313 m_videoFlags[i] = 1;
314 }
315 m_rateMatrix[i] = 0;
316 }
317 }
318
319 // Choose the biggest rectangle from the region defined by
320 // m_videoFlags[].
321 Rect r;
322 getVideoAreaRect(&r);
323
324 // Exclude video rectangle from pmxChanged[].
325 for (int y = r.tl.y / 32; y < r.br.y / 32; y++) {
326 for (int x = r.tl.x / 32; x < r.br.x / 32; x++) {
327 pmxChanged[y * m_widthTiles + x] = false;
328 }
329 }
330
331 // Inform the server...
332 m_server->set_video_area(r);
333 if (!r.is_empty())
334 getScreenRect(r);
335
336 return (!r.is_empty());
337}
338
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000339//
340// Compute coordinates of the rectangle around the pointer.
341//
342// ASSUMES: (m_pointerPosKnown != false)
343//
344
345void PollingManager::computePointerArea(Rect *r)
346{
347 int x = m_pointerPos.x - 64;
348 int y = m_pointerPos.y - 64;
349 int w = 128;
350 int h = 128;
351 if (x < 0) {
352 w += x; x = 0;
353 }
354 if (x + w > m_width) {
355 w = m_width - x;
356 }
357 if (y < 0) {
358 h += y; y = 0;
359 }
360 if (y + h > m_height) {
361 h = m_height - y;
362 }
363
364 r->setXYWH(x, y, w, h);
365}
366
367//
368// Poll the area under current pointer position. Each pixel of the
369// area should be compared. Using such polling option gives higher
370// priority to screen area under the pointer.
371//
372// ASSUMES: (m_server != NULL && m_pointerPosKnown != false)
373//
374
375bool PollingManager::pollPointerArea()
376{
377 Rect r;
378 computePointerArea(&r);
379
380 // Shortcuts for coordinates.
381 int x = r.tl.x, y = r.tl.y;
382 int w = r.width(), h = r.height();
383
384 // Get new pixels.
385 getArea128(x, y, w, h);
386
387 // Now, try to minimize the rectangle by cutting out unchanged
388 // borders (at top and bottom).
389 //
390 // FIXME: Perhaps we should work on 32x32 tiles (properly aligned)
391 // to produce a region instead of a rectangle. If there would
392 // be just one universal polling algorithm, it could be
393 // better to integrate pointer area polling into that
394 // algorithm, instead of a separate pollPointerArea()
395 // function.
396
397 // Shortcuts.
398 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
399 int oldBytesPerLine = m_image->xim->bytes_per_line;
400 int newBytesPerLine = m_areaImage->xim->bytes_per_line;
401 char *oldPtr = m_image->xim->data + y * oldBytesPerLine + x * bytesPerPixel;
402 char *newPtr = m_areaImage->xim->data;
403
404 // Check and cut out unchanged rows at the top.
405 int ty;
406 for (ty = 0; ty < h; ty++) {
407 if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0)
408 break;
409 oldPtr += oldBytesPerLine;
410 newPtr += newBytesPerLine;
411 }
412 if (ty == h) {
413 return false; // no changes at all
414 }
415 y += ty; h -= ty;
416
417 // Check and cut out unchanged rows at the bottom.
418 oldPtr = m_image->xim->data + (y+h-1) * oldBytesPerLine + x * bytesPerPixel;
419 newPtr = m_areaImage->xim->data + (ty+h-1) * newBytesPerLine;
420 int by;
421 for (by = 0; by < h - 1; by++) {
422 if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0)
423 break;
424 oldPtr -= oldBytesPerLine;
425 newPtr -= newBytesPerLine;
426 }
427 h -= by;
428
429 // Copy pixels.
430 m_image->updateRect(m_areaImage, x, y, 0, ty, w, h);
431
432 // Report updates to the server.
433 Rect rect(x, y, x+w, y+h);
434 m_server->add_changed(rect);
435 return true;
436}
437
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000438void
439PollingManager::getVideoAreaRect(Rect *result)
440{
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000441 int *mx_hlen, *mx_vlen;
442 constructLengthMatrices(&mx_hlen, &mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000443
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000444 int full_h = m_heightTiles;
445 int full_w = m_widthTiles;
446 int x, y;
447 Rect max_rect(0, 0, 0, 0);
448 Rect local_rect;
449
450 for (y = 0; y < full_h; y++) {
451 for (x = 0; x < full_w; x++) {
452 int max_w = mx_hlen[y * full_w + x];
453 int max_h = mx_vlen[y * full_w + x];
454 if (max_w > 2 && max_h > 1 && max_h * max_w > (int)max_rect.area()) {
455 local_rect.tl.x = x;
456 local_rect.tl.y = y;
457 findMaxLocalRect(&local_rect, mx_hlen, mx_vlen);
458 if (local_rect.area() > max_rect.area()) {
459 max_rect = local_rect;
460 }
461 }
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000462 }
463 }
464
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000465 destroyLengthMatrices(mx_hlen, mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000466
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000467 max_rect.tl.x *= 32;
468 max_rect.tl.y *= 32;
469 max_rect.br.x *= 32;
470 max_rect.br.y *= 32;
471 if (max_rect.br.x > m_width)
472 max_rect.br.x = m_width;
473 if (max_rect.br.y > m_height)
474 max_rect.br.y = m_height;
475 *result = max_rect;
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000476
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000477 if (!result->is_empty()) {
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000478 fprintf(stderr, "Video rect %dx%d\tat(%d,%d)\n",
479 result->width(), result->height(), result->tl.x, result->tl.y);
480 }
481}
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000482
483void
484PollingManager::constructLengthMatrices(int **pmx_h, int **pmx_v)
485{
486 // Handy shortcuts.
487 int h = m_heightTiles;
488 int w = m_widthTiles;
489
490 // Allocate memory.
491 int *mx_h = new int[h * w];
492 memset(mx_h, 0, h * w * sizeof(int));
493 int *mx_v = new int[h * w];
494 memset(mx_v, 0, h * w * sizeof(int));
495
496 int x, y, len, i;
497
498 // Fill in horizontal length matrix.
499 for (y = 0; y < h; y++) {
500 for (x = 0; x < w; x++) {
501 len = 0;
502 while (x + len < w && m_videoFlags[y * w + x + len]) {
503 len++;
504 }
505 for (i = 0; i < len; i++) {
506 mx_h[y * w + x + i] = len - i;
507 }
508 x += len;
509 }
510 }
511
512 // Fill in vertical length matrix.
513 for (x = 0; x < w; x++) {
514 for (y = 0; y < h; y++) {
515 len = 0;
516 while (y + len < h && m_videoFlags[(y + len) * w + x]) {
517 len++;
518 }
519 for (i = 0; i < len; i++) {
520 mx_v[(y + i) * w + x] = len - i;
521 }
522 y += len;
523 }
524 }
525
526 *pmx_h = mx_h;
527 *pmx_v = mx_v;
528}
529
530void
531PollingManager::destroyLengthMatrices(int *mx_h, int *mx_v)
532{
533 delete[] mx_h;
534 delete[] mx_v;
535}
536
537// NOTE: This function assumes that current tile has non-zero in mx_h[],
538// otherwise we get division by zero.
539void
540PollingManager::findMaxLocalRect(Rect *r, int mx_h[], int mx_v[])
541{
542 int idx = r->tl.y * m_widthTiles + r->tl.x;
543
544 // NOTE: Rectangle's maximum width and height are 25 and 18
545 // (in tiles, where each tile is usually 32x32 pixels).
546 int max_w = mx_h[idx];
547 if (max_w > 25)
548 max_w = 25;
549 int cur_h = 18;
550
551 int best_w = max_w;
552 int best_area = 1 * best_w;
553
554 for (int i = 0; i < max_w; i++) {
555 int h = mx_v[idx + i];
556 if (h < cur_h) {
557 cur_h = h;
558 if (cur_h * max_w <= best_area)
559 break;
560 }
561 if (cur_h * (i + 1) > best_area) {
562 best_w = i + 1;
563 best_area = cur_h * best_w;
564 }
565 }
566
567 r->br.x = r->tl.x + best_w;
568 r->br.y = r->tl.y + best_area / best_w;
569}
570