blob: b2d4757d3da9671751b068b235441c604d59a91f [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
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000035const int PollingManager::m_pollingOrder[32] = {
36 0, 16, 8, 24, 4, 20, 12, 28,
37 10, 26, 18, 2, 22, 6, 30, 14,
38 1, 17, 9, 25, 7, 23, 15, 31,
39 19, 3, 27, 11, 29, 13, 5, 21
40};
41
42//
43// Constructor.
44//
45// Note that dpy and image should remain valid during the object
46// lifetime, while factory is used only in the constructor itself.
47//
48
49PollingManager::PollingManager(Display *dpy, Image *image,
50 ImageFactory *factory,
51 int offsetLeft, int offsetTop)
52 : m_dpy(dpy), m_server(0), m_image(image),
53 m_offsetLeft(offsetLeft), m_offsetTop(offsetTop),
Constantin Kaplinskyd0b15c62007-10-09 08:15:25 +000054 m_pollingStep(0)
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000055{
56 // Save width and height of the screen (and the image).
57 m_width = m_image->xim->width;
58 m_height = m_image->xim->height;
59
60 // Compute width and height in 32x32 tiles.
61 m_widthTiles = (m_width + 31) / 32;
62 m_heightTiles = (m_height + 31) / 32;
63
64 // Get initial screen image.
65 m_image->get(DefaultRootWindow(m_dpy), m_offsetLeft, m_offsetTop);
66
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +000067 // Create additional images used in polling algorithm, warn if
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000068 // underlying class names are different from the class name of the
69 // primary image.
70 m_rowImage = factory->newImage(m_dpy, m_width, 1);
Constantin Kaplinskyd0b15c62007-10-09 08:15:25 +000071 if (strcmp(m_image->className(), m_rowImage->className()) != 0) {
72 vlog.error("Image types do not match (%s)",
73 m_rowImage->className());
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000074 }
75
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000076 int numTiles = m_widthTiles * m_heightTiles;
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000077 m_rateMatrix = new char[numTiles];
78 m_videoFlags = new char[numTiles];
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000079 memset(m_rateMatrix, 0, numTiles);
80 memset(m_videoFlags, 0, numTiles);
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000081}
82
83PollingManager::~PollingManager()
84{
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000085 delete[] m_videoFlags;
86 delete[] m_rateMatrix;
87
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +000088 delete m_rowImage;
89}
90
91//
92// Register VNCServer object.
93//
94
95void PollingManager::setVNCServer(VNCServer *s)
96{
97 m_server = s;
98}
99
100//
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000101// DEBUG: Measuring time spent in the poll() function,
102// as well as time intervals between poll() calls.
103//
104
105#ifdef DEBUG
106void PollingManager::debugBeforePoll()
107{
108 TimeMillis timeNow;
109 int diff = timeNow.diffFrom(m_timeSaved);
110 fprintf(stderr, "[wait%4dms]\t[step %2d]\t", diff, m_pollingStep % 32);
111 m_timeSaved = timeNow;
112}
113
114void PollingManager::debugAfterPoll()
115{
116 TimeMillis timeNow;
117 int diff = timeNow.diffFrom(m_timeSaved);
118 fprintf(stderr, "[poll%4dms]\n", diff);
119 m_timeSaved = timeNow;
120}
121
122#endif
123
124//
125// Search for changed rectangles on the screen.
126//
127
128void PollingManager::poll()
129{
130#ifdef DEBUG
131 debugBeforePoll();
132#endif
133
Constantin Kaplinskyd0b15c62007-10-09 08:15:25 +0000134 // Perform polling and try update clients if changes were detected.
135 if (pollScreen())
Constantin Kaplinskyb30ae7f2006-05-25 05:04:46 +0000136 m_server->tryUpdate();
137
138#ifdef DEBUG
139 debugAfterPoll();
140#endif
141}
142
Constantin Kaplinsky9ee8dc62007-10-09 07:46:32 +0000143bool PollingManager::pollScreen()
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000144{
145 if (!m_server)
146 return false;
147
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000148 // mxChanged[] array will hold boolean values corresponding to each
149 // 32x32 tile. If a value is true, then we've detected a change in
Constantin Kaplinsky5e2f69f2007-10-07 13:08:18 +0000150 // that tile. Initially, we fill in the array with zero values.
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000151 bool *mxChanged = new bool[m_widthTiles * m_heightTiles];
Constantin Kaplinskyf25307a2007-10-08 13:49:44 +0000152 memset(mxChanged, 0, m_widthTiles * m_heightTiles * sizeof(bool));
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000153
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000154 // First pass over the framebuffer. Here we scan 1/32 part of the
155 // framebuffer -- that is, one line in each (32 * m_width) stripe.
156 // We compare the pixels of that line with previous framebuffer
157 // contents and raise corresponding member values of mxChanged[].
158 int scanOffset = m_pollingOrder[m_pollingStep++ % 32];
159 bool *pmxChanged = mxChanged;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000160 int nTilesChanged = 0;
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000161 for (int y = scanOffset; y < m_height; y += 32) {
162 nTilesChanged += checkRow(0, y, m_width, pmxChanged);
163 pmxChanged += m_widthTiles;
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000164 }
165
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000166 // Do the work related to video area detection.
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000167 bool haveVideoRect = handleVideo(mxChanged);
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000168
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000169 // Inform the server about the changes.
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000170 // FIXME: It's possible that (nTilesChanged != 0) but mxChanged[]
171 // array is empty. That's possible because handleVideo()
172 // modifies mxChanged[].
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000173 if (nTilesChanged)
174 sendChanges(mxChanged);
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000175
176 // Cleanup.
177 delete[] mxChanged;
178
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000179#ifdef DEBUG
180 if (nTilesChanged != 0) {
181 fprintf(stderr, "#%d# ", nTilesChanged);
182 }
183#endif
184
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000185 return (nTilesChanged != 0 || haveVideoRect);
Constantin Kaplinskya119b482007-10-04 06:02:02 +0000186}
187
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000188int PollingManager::checkRow(int x, int y, int w, bool *pmxChanged)
189{
190 int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
191 int bytesPerLine = m_image->xim->bytes_per_line;
192
Constantin Kaplinsky29d32052007-10-08 14:16:02 +0000193 if (x == 0 && w == m_width) {
194 getFullRow(y); // use more efficient method if possible
195 } else {
196 getRow(x, y, w);
197 }
Constantin Kaplinskybc6b9e22007-10-04 11:43:41 +0000198
199 char *ptr_old = m_image->xim->data + y * bytesPerLine + x * bytesPerPixel;
200 char *ptr_new = m_rowImage->xim->data;
201
202 int nTilesChanged = 0;
203 for (int i = 0; i < (w + 31) / 32; i++) {
204 int tile_w = (w - i * 32 >= 32) ? 32 : w - i * 32;
205 int nBytes = tile_w * bytesPerPixel;
206 if (memcmp(ptr_old, ptr_new, nBytes)) {
207 *pmxChanged = true;
208 nTilesChanged++;
209 }
210 pmxChanged++;
211 ptr_old += nBytes;
212 ptr_new += nBytes;
213 }
214
215 return nTilesChanged;
216}
217
Constantin Kaplinskya79255b2007-10-07 13:03:55 +0000218void PollingManager::sendChanges(bool *pmxChanged)
219{
220 Rect rect;
221 for (int y = 0; y < m_heightTiles; y++) {
222 for (int x = 0; x < m_widthTiles; x++) {
223 if (*pmxChanged++) {
224 // Count successive tiles marked as changed.
225 int count = 1;
226 while (x + count < m_widthTiles && *pmxChanged++) {
227 count++;
228 }
229 // Compute the coordinates and the size of this band.
230 rect.setXYWH(x * 32, y * 32, count * 32, 32);
231 if (rect.br.x > m_width)
232 rect.br.x = m_width;
233 if (rect.br.y > m_height)
234 rect.br.y = m_height;
235 // Add to the changed region maintained by the server.
236 getScreenRect(rect);
237 m_server->add_changed(rect);
238 // Skip processed tiles.
239 x += count;
240 }
241 }
242 }
243}
244
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000245bool PollingManager::handleVideo(bool *pmxChanged)
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000246{
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000247 // Update counters in m_rateMatrix.
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000248 int numTiles = m_heightTiles * m_widthTiles;
249 for (int i = 0; i < numTiles; i++)
250 m_rateMatrix[i] += (pmxChanged[i] != false);
251
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000252 // Once per eight calls: detect video rectangle by examining
253 // m_rateMatrix[], then reset counters in m_rateMatrix[].
254 if (m_pollingStep % 8 == 0) {
255 detectVideo();
256 memset(m_rateMatrix, 0, numTiles);
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000257 }
258
Constantin Kaplinsky646998a2007-10-09 09:31:41 +0000259 // Grab the pixels of video area. Also, exclude video rectangle from
260 // pmxChanged[], to prevent grabbing the same pixels twice.
261 if (!m_videoRect.is_empty()) {
262 Rect r(m_videoRect.tl.x / 32, m_videoRect.tl.y / 32,
263 m_videoRect.br.x / 32, m_videoRect.br.y / 32);
264 for (int y = r.tl.y; y < r.br.y; y++) {
265 for (int x = r.tl.x; x < r.br.x; x++) {
266 pmxChanged[y * m_widthTiles + x] = false;
267 }
268 }
269 getScreenRect(m_videoRect);
270 return true; // we've got a video rectangle
271 }
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000272
Constantin Kaplinsky646998a2007-10-09 09:31:41 +0000273 return false; // video rectangle is empty
Constantin Kaplinskyc1984e02007-10-08 14:54:18 +0000274}
275
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000276void
Constantin Kaplinsky6bb4bf12007-10-09 12:03:15 +0000277PollingManager::detectVideo()
278{
279 // Configurable parameters.
280 const int VIDEO_THRESHOLD_0 = 3;
281 const int VIDEO_THRESHOLD_1 = 5;
282
283 // First, detect candidate region that looks like video. In other
284 // words, find a region that consists of continuously changing
285 // pixels. Save the result in m_videoFlags[].
286 int numTiles = m_heightTiles * m_widthTiles;
287 for (int i = 0; i < numTiles; i++) {
288 if (m_rateMatrix[i] <= VIDEO_THRESHOLD_0) {
289 m_videoFlags[i] = 0;
290 } else if (m_rateMatrix[i] >= VIDEO_THRESHOLD_1) {
291 m_videoFlags[i] = 1;
292 }
293 }
294
295 // Now, choose the biggest rectangle from that candidate region.
296 Rect newRect;
297 getVideoAreaRect(&newRect);
298
299 // Does new rectangle differ from the previously detected one?
300 // If it does, save new rectangle and inform the server.
301 if (!newRect.equals(m_videoRect)) {
302 if (newRect.is_empty()) {
303 fprintf(stderr, "No video detected\n");
304 } else {
305 fprintf(stderr, "Video rect %dx%d\tat(%d,%d)\n",
306 newRect.width(), newRect.height(), newRect.tl.x, newRect.tl.y);
307 }
308 m_videoRect = newRect;
309 m_server->set_video_area(newRect);
310 }
311}
312
313void
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000314PollingManager::getVideoAreaRect(Rect *result)
315{
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000316 int *mx_hlen, *mx_vlen;
317 constructLengthMatrices(&mx_hlen, &mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000318
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000319 int full_h = m_heightTiles;
320 int full_w = m_widthTiles;
321 int x, y;
322 Rect max_rect(0, 0, 0, 0);
323 Rect local_rect;
324
325 for (y = 0; y < full_h; y++) {
326 for (x = 0; x < full_w; x++) {
327 int max_w = mx_hlen[y * full_w + x];
328 int max_h = mx_vlen[y * full_w + x];
329 if (max_w > 2 && max_h > 1 && max_h * max_w > (int)max_rect.area()) {
330 local_rect.tl.x = x;
331 local_rect.tl.y = y;
332 findMaxLocalRect(&local_rect, mx_hlen, mx_vlen);
333 if (local_rect.area() > max_rect.area()) {
334 max_rect = local_rect;
335 }
336 }
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000337 }
338 }
339
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000340 destroyLengthMatrices(mx_hlen, mx_vlen);
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000341
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000342 max_rect.tl.x *= 32;
343 max_rect.tl.y *= 32;
344 max_rect.br.x *= 32;
345 max_rect.br.y *= 32;
346 if (max_rect.br.x > m_width)
347 max_rect.br.x = m_width;
348 if (max_rect.br.y > m_height)
349 max_rect.br.y = m_height;
350 *result = max_rect;
Constantin Kaplinsky56649982007-09-04 09:15:35 +0000351}
Constantin Kaplinsky0fc9f172007-09-29 04:00:02 +0000352
353void
354PollingManager::constructLengthMatrices(int **pmx_h, int **pmx_v)
355{
356 // Handy shortcuts.
357 int h = m_heightTiles;
358 int w = m_widthTiles;
359
360 // Allocate memory.
361 int *mx_h = new int[h * w];
362 memset(mx_h, 0, h * w * sizeof(int));
363 int *mx_v = new int[h * w];
364 memset(mx_v, 0, h * w * sizeof(int));
365
366 int x, y, len, i;
367
368 // Fill in horizontal length matrix.
369 for (y = 0; y < h; y++) {
370 for (x = 0; x < w; x++) {
371 len = 0;
372 while (x + len < w && m_videoFlags[y * w + x + len]) {
373 len++;
374 }
375 for (i = 0; i < len; i++) {
376 mx_h[y * w + x + i] = len - i;
377 }
378 x += len;
379 }
380 }
381
382 // Fill in vertical length matrix.
383 for (x = 0; x < w; x++) {
384 for (y = 0; y < h; y++) {
385 len = 0;
386 while (y + len < h && m_videoFlags[(y + len) * w + x]) {
387 len++;
388 }
389 for (i = 0; i < len; i++) {
390 mx_v[(y + i) * w + x] = len - i;
391 }
392 y += len;
393 }
394 }
395
396 *pmx_h = mx_h;
397 *pmx_v = mx_v;
398}
399
400void
401PollingManager::destroyLengthMatrices(int *mx_h, int *mx_v)
402{
403 delete[] mx_h;
404 delete[] mx_v;
405}
406
407// NOTE: This function assumes that current tile has non-zero in mx_h[],
408// otherwise we get division by zero.
409void
410PollingManager::findMaxLocalRect(Rect *r, int mx_h[], int mx_v[])
411{
412 int idx = r->tl.y * m_widthTiles + r->tl.x;
413
414 // NOTE: Rectangle's maximum width and height are 25 and 18
415 // (in tiles, where each tile is usually 32x32 pixels).
416 int max_w = mx_h[idx];
417 if (max_w > 25)
418 max_w = 25;
419 int cur_h = 18;
420
421 int best_w = max_w;
422 int best_area = 1 * best_w;
423
424 for (int i = 0; i < max_w; i++) {
425 int h = mx_v[idx + i];
426 if (h < cur_h) {
427 cur_h = h;
428 if (cur_h * max_w <= best_area)
429 break;
430 }
431 if (cur_h * (i + 1) > best_area) {
432 best_w = i + 1;
433 best_area = cur_h * best_w;
434 }
435 }
436
437 r->br.x = r->tl.x + best_w;
438 r->br.y = r->tl.y + best_area / best_w;
439}
440