blob: a823ed4274ef18be57ea4a2bb0f313c25dc79772 [file] [log] [blame]
/* Copyright (C) 2004-2005 Constantin Kaplinsky. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
//
// PollingManager.cxx
//
// FIXME: Don't compare pixels already marked as changed.
// FIXME: Use Image::copyPixels() instead of Image::updateRect()?
// In that case, note the fact that arguments are not checked.
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <X11/Xlib.h>
#include <rfb/LogWriter.h>
#include <rfb/VNCServer.h>
#include <rfb/Configuration.h>
#include <rfb/ServerCore.h>
#include <x0vncserver/PollingManager.h>
static LogWriter vlog("PollingMgr");
BoolParameter PollingManager::pollPointer
("PollPointer",
"DEBUG: Poll area under the pointer with higher priority",
true);
IntParameter PollingManager::pollingType
("PollingType",
"DEBUG: Select particular polling algorithm (0..3)",
3);
const int PollingManager::m_pollingOrder[32] = {
0, 16, 8, 24, 4, 20, 12, 28,
10, 26, 18, 2, 22, 6, 30, 14,
1, 17, 9, 25, 7, 23, 15, 31,
19, 3, 27, 11, 29, 13, 5, 21
};
//
// Constructor.
//
// Note that dpy and image should remain valid during the object
// lifetime, while factory is used only in the constructor itself.
//
PollingManager::PollingManager(Display *dpy, Image *image,
ImageFactory *factory,
int offsetLeft, int offsetTop)
: m_dpy(dpy), m_server(0), m_image(image),
m_offsetLeft(offsetLeft), m_offsetTop(offsetTop),
m_pointerPosKnown(false), m_pollingStep(0)
{
// Save width and height of the screen (and the image).
m_width = m_image->xim->width;
m_height = m_image->xim->height;
// Compute width and height in 32x32 tiles.
m_widthTiles = (m_width + 31) / 32;
m_heightTiles = (m_height + 31) / 32;
// Get initial screen image.
m_image->get(DefaultRootWindow(m_dpy), m_offsetLeft, m_offsetTop);
// Create additional images used in polling algorithms, warn if
// underlying class names are different from the class name of the
// primary image.
m_rowImage = factory->newImage(m_dpy, m_width, 1);
m_tileImage = factory->newImage(m_dpy, 32, 32);
m_areaImage = factory->newImage(m_dpy, 128, 128);
if (strcmp(m_image->className(), m_rowImage->className()) != 0 ||
strcmp(m_image->className(), m_tileImage->className()) != 0 ||
strcmp(m_image->className(), m_areaImage->className()) != 0) {
vlog.error("Image types do not match (%s, %s, %s)",
m_rowImage->className(),
m_tileImage->className(),
m_areaImage->className());
}
// FIXME: Extend the comment.
// Create a matrix with one byte per each 32x32 tile. It will be
// used to limit the rate of updates on continuously-changed screen
// areas (like video).
int numTiles = m_widthTiles * m_heightTiles;
m_statusMatrix = new char[numTiles];
memset(m_statusMatrix, 0, numTiles);
// FIXME: Extend the comment.
// Create a matrix with one byte per each 32x32 tile. It will be
// used to limit the rate of updates on continuously-changed screen
// areas (like video).
m_rateMatrix = new char[numTiles];
m_videoFlags = new char[numTiles];
m_changedFlags = new char[numTiles];
memset(m_rateMatrix, 0, numTiles);
memset(m_videoFlags, 0, numTiles);
memset(m_changedFlags, 0, numTiles);
}
PollingManager::~PollingManager()
{
delete[] m_changedFlags;
delete[] m_videoFlags;
delete[] m_rateMatrix;
delete[] m_statusMatrix;
delete m_areaImage;
delete m_tileImage;
delete m_rowImage;
}
//
// Register VNCServer object.
//
void PollingManager::setVNCServer(VNCServer *s)
{
m_server = s;
}
//
// Update current pointer position which may be used as a hint for
// polling algorithms.
//
void PollingManager::setPointerPos(const Point &pos)
{
m_pointerPosTime = time(NULL);
m_pointerPos = pos;
m_pointerPosKnown = true;
}
//
// Indicate that current pointer position is unknown.
//
void PollingManager::unsetPointerPos()
{
m_pointerPosKnown = false;
}
//
// DEBUG: Measuring time spent in the poll() function,
// as well as time intervals between poll() calls.
//
#ifdef DEBUG
void PollingManager::debugBeforePoll()
{
TimeMillis timeNow;
int diff = timeNow.diffFrom(m_timeSaved);
fprintf(stderr, "[wait%4dms]\t[step %2d]\t", diff, m_pollingStep % 32);
m_timeSaved = timeNow;
}
void PollingManager::debugAfterPoll()
{
TimeMillis timeNow;
int diff = timeNow.diffFrom(m_timeSaved);
fprintf(stderr, "[poll%4dms]\n", diff);
m_timeSaved = timeNow;
}
#endif
//
// Search for changed rectangles on the screen.
//
void PollingManager::poll()
{
#ifdef DEBUG
debugBeforePoll();
#endif
// First step: full-screen polling.
bool changes1 = false;
switch((int)pollingType) {
case 0:
changes1 = poll_Dumb();
break;
case 1:
changes1 = poll_Traditional();
break;
case 2:
changes1 = poll_SkipCycles();
break;
//case 3:
default:
changes1 = poll_DetectVideo();
break;
}
// Second step: optional thorough polling of the area around the pointer.
// We do that only if the pointer position is known and was set recently.
bool changes2 = false;
if (pollPointer) {
if (m_pointerPosKnown && time(NULL) - m_pointerPosTime >= 5) {
unsetPointerPos();
}
if (m_pointerPosKnown) {
changes2 = pollPointerArea();
}
}
// Update if needed.
if (changes1 || changes2)
m_server->tryUpdate();
#ifdef DEBUG
debugAfterPoll();
#endif
}
bool PollingManager::poll_DetectVideo()
{
if (!m_server)
return false;
const int GRAND_STEP_DIVISOR = 8;
const int VIDEO_THRESHOLD_0 = 3;
const int VIDEO_THRESHOLD_1 = 5;
bool grandStep = (m_pollingStep % GRAND_STEP_DIVISOR == 0);
// FIXME: Save shortcuts in member variables?
int scanLine = m_pollingOrder[m_pollingStep++ % 32];
int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
int bytesPerLine = m_image->xim->bytes_per_line;
Rect rect;
int nTilesChanged = 0;
int idx = 0;
for (int y = 0; y * 32 < m_height; y++) {
int tile_h = (m_height - y * 32 >= 32) ? 32 : m_height - y * 32;
if (scanLine >= tile_h)
break;
int scan_y = y * 32 + scanLine;
getRow(scan_y);
char *ptr_old = m_image->xim->data + scan_y * bytesPerLine;
char *ptr_new = m_rowImage->xim->data;
for (int x = 0; x * 32 < m_width; x++) {
int tile_w = (m_width - x * 32 >= 32) ? 32 : m_width - x * 32;
int nBytes = tile_w * bytesPerPixel;
char wasChanged = (memcmp(ptr_old, ptr_new, nBytes) != 0);
m_rateMatrix[idx] += wasChanged;
if (grandStep) {
if (m_rateMatrix[idx] <= VIDEO_THRESHOLD_0) {
m_videoFlags[idx] = 0;
} else if (m_rateMatrix[idx] >= VIDEO_THRESHOLD_1) {
m_videoFlags[idx] = 1;
}
m_rateMatrix[idx] = 0;
}
m_changedFlags[idx] |= wasChanged;
if ( m_changedFlags[idx] && (!m_videoFlags[idx] || grandStep) ) {
getTile32(x, y, tile_w, tile_h);
m_image->updateRect(m_tileImage, x * 32, y * 32);
rect.setXYWH(x * 32, y * 32, tile_w, tile_h);
m_server->add_changed(rect);
nTilesChanged++;
m_changedFlags[idx] = 0;
}
ptr_old += nBytes;
ptr_new += nBytes;
idx++;
}
}
if (grandStep)
adjustVideoArea();
return (nTilesChanged != 0);
}
bool PollingManager::poll_SkipCycles()
{
if (!m_server)
return false;
enum {
NOT_CHANGED, CHANGED_ONCE, CHANGED_AGAIN
};
bool grandStep = (m_pollingStep % 8 == 0);
int nTilesChanged = 0;
int scanLine = m_pollingOrder[m_pollingStep++ % 32];
int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
int bytesPerLine = m_image->xim->bytes_per_line;
char *pstatus = m_statusMatrix;
bool wasChanged;
Rect rect;
for (int y = 0; y * 32 < m_height; y++) {
int tile_h = (m_height - y * 32 >= 32) ? 32 : m_height - y * 32;
if (scanLine >= tile_h)
scanLine %= tile_h;
int scan_y = y * 32 + scanLine;
getRow(scan_y);
char *ptr_old = m_image->xim->data + scan_y * bytesPerLine;
char *ptr_new = m_rowImage->xim->data;
for (int x = 0; x * 32 < m_width; x++) {
int tile_w = (m_width - x * 32 >= 32) ? 32 : m_width - x * 32;
int nBytes = tile_w * bytesPerPixel;
if (grandStep || *pstatus != CHANGED_AGAIN) {
wasChanged = (*pstatus == CHANGED_AGAIN) ?
true : (memcmp(ptr_old, ptr_new, nBytes) != 0);
if (wasChanged) {
if (grandStep || *pstatus == NOT_CHANGED) {
getTile32(x, y, tile_w, tile_h);
m_image->updateRect(m_tileImage, x * 32, y * 32);
rect.setXYWH(x * 32, y * 32, tile_w, tile_h);
m_server->add_changed(rect);
nTilesChanged++;
*pstatus = CHANGED_ONCE;
} else {
*pstatus = CHANGED_AGAIN;
}
} else if (grandStep) {
*pstatus = NOT_CHANGED;
}
}
ptr_old += nBytes;
ptr_new += nBytes;
pstatus++;
}
}
return (nTilesChanged != 0);
}
bool PollingManager::poll_Traditional()
{
if (!m_server)
return false;
int nTilesChanged = 0;
int scanLine = m_pollingOrder[m_pollingStep++ % 32];
int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
int bytesPerLine = m_image->xim->bytes_per_line;
Rect rect;
for (int y = 0; y * 32 < m_height; y++) {
int tile_h = (m_height - y * 32 >= 32) ? 32 : m_height - y * 32;
if (scanLine >= tile_h)
break;
int scan_y = y * 32 + scanLine;
getRow(scan_y);
char *ptr_old = m_image->xim->data + scan_y * bytesPerLine;
char *ptr_new = m_rowImage->xim->data;
for (int x = 0; x * 32 < m_width; x++) {
int tile_w = (m_width - x * 32 >= 32) ? 32 : m_width - x * 32;
int nBytes = tile_w * bytesPerPixel;
if (memcmp(ptr_old, ptr_new, nBytes)) {
getTile32(x, y, tile_w, tile_h);
m_image->updateRect(m_tileImage, x * 32, y * 32);
rect.setXYWH(x * 32, y * 32, tile_w, tile_h);
m_server->add_changed(rect);
nTilesChanged++;
}
ptr_old += nBytes;
ptr_new += nBytes;
}
}
return (nTilesChanged != 0);
}
//
// Simplest polling method, from the original x0vncserver of VNC4.
//
bool PollingManager::poll_Dumb()
{
if (!m_server)
return false;
getScreen();
Rect rect(0, 0, m_width, m_height);
m_server->add_changed(rect);
// Report that some changes have been detected.
return true;
}
//
// Compute coordinates of the rectangle around the pointer.
//
// ASSUMES: (m_pointerPosKnown != false)
//
void PollingManager::computePointerArea(Rect *r)
{
int x = m_pointerPos.x - 64;
int y = m_pointerPos.y - 64;
int w = 128;
int h = 128;
if (x < 0) {
w += x; x = 0;
}
if (x + w > m_width) {
w = m_width - x;
}
if (y < 0) {
h += y; y = 0;
}
if (y + h > m_height) {
h = m_height - y;
}
r->setXYWH(x, y, w, h);
}
//
// Poll the area under current pointer position. Each pixel of the
// area should be compared. Using such polling option gives higher
// priority to screen area under the pointer.
//
// ASSUMES: (m_server != NULL && m_pointerPosKnown != false)
//
bool PollingManager::pollPointerArea()
{
Rect r;
computePointerArea(&r);
// Shortcuts for coordinates.
int x = r.tl.x, y = r.tl.y;
int w = r.width(), h = r.height();
// Get new pixels.
getArea128(x, y, w, h);
// Now, try to minimize the rectangle by cutting out unchanged
// borders (at top and bottom).
//
// FIXME: Perhaps we should work on 32x32 tiles (properly aligned)
// to produce a region instead of a rectangle. If there would
// be just one universal polling algorithm, it could be
// better to integrate pointer area polling into that
// algorithm, instead of a separate pollPointerArea()
// function.
// Shortcuts.
int bytesPerPixel = m_image->xim->bits_per_pixel / 8;
int oldBytesPerLine = m_image->xim->bytes_per_line;
int newBytesPerLine = m_areaImage->xim->bytes_per_line;
char *oldPtr = m_image->xim->data + y * oldBytesPerLine + x * bytesPerPixel;
char *newPtr = m_areaImage->xim->data;
// Check and cut out unchanged rows at the top.
int ty;
for (ty = 0; ty < h; ty++) {
if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0)
break;
oldPtr += oldBytesPerLine;
newPtr += newBytesPerLine;
}
if (ty == h) {
return false; // no changes at all
}
y += ty; h -= ty;
// Check and cut out unchanged rows at the bottom.
oldPtr = m_image->xim->data + (y+h-1) * oldBytesPerLine + x * bytesPerPixel;
newPtr = m_areaImage->xim->data + (ty+h-1) * newBytesPerLine;
int by;
for (by = 0; by < h - 1; by++) {
if (memcmp(oldPtr, newPtr, w * bytesPerPixel) != 0)
break;
oldPtr -= oldBytesPerLine;
newPtr -= newBytesPerLine;
}
h -= by;
// Copy pixels.
m_image->updateRect(m_areaImage, x, y, 0, ty, w, h);
// Report updates to the server.
Rect rect(x, y, x+w, y+h);
m_server->add_changed(rect);
return true;
}
//
// Make video area pattern more regular.
//
// FIXME: Replace the above with a normal comment.
// FIXME: Is the function efficient enough?
//
void PollingManager::adjustVideoArea()
{
char newFlags[m_widthTiles * m_heightTiles];
char *ptr = newFlags;
int x, y;
// DEBUG:
// int nVideoTiles = 0;
for (y = 0; y < m_heightTiles; y++) {
for (x = 0; x < m_widthTiles; x++) {
// DEBUG:
// nVideoTiles += m_videoFlags[y * m_widthTiles + x];
int weightedSum = 0, n;
if (y > 0 && x > 0) {
n = (m_videoFlags[ y * m_widthTiles + (x-1)] +
m_videoFlags[(y-1) * m_widthTiles + (x-1)] +
m_videoFlags[(y-1) * m_widthTiles + x ]);
if (n == 3) {
*ptr++ = 1;
continue;
}
weightedSum += n;
}
if (y > 0 && x < m_widthTiles - 1) {
n = (m_videoFlags[ y * m_widthTiles + (x+1)] +
m_videoFlags[(y-1) * m_widthTiles + (x+1)] +
m_videoFlags[(y-1) * m_widthTiles + x ]);
if (n == 3) {
*ptr++ = 1;
continue;
}
weightedSum += n;
}
if (y < m_heightTiles - 1 && x > 0) {
n = (m_videoFlags[ y * m_widthTiles + (x-1)] +
m_videoFlags[(y+1) * m_widthTiles + (x-1)] +
m_videoFlags[(y+1) * m_widthTiles + x ]);
if (n == 3) {
*ptr++ = 1;
continue;
}
weightedSum += n;
}
if (y < m_heightTiles - 1 && x < m_widthTiles - 1) {
n = (m_videoFlags[ y * m_widthTiles + (x+1)] +
m_videoFlags[(y+1) * m_widthTiles + (x+1)] +
m_videoFlags[(y+1) * m_widthTiles + x ]);
if (n == 3) {
*ptr++ = 1;
continue;
}
weightedSum += n;
}
*ptr++ = (weightedSum <= 3) ? 0 : m_videoFlags[y * m_widthTiles + x];
}
}
/*
/// DEBUG: ------------------------------------------------------
if (nVideoTiles) {
for (y = 0; y < m_heightTiles; y++) {
for (x = 0; x < m_widthTiles; x++) {
printf("%c", m_videoFlags[y * m_widthTiles + x] ? '@' : ':');
}
printf(" ");
for (x = 0; x < m_widthTiles; x++) {
printf("%c", newFlags[y * m_widthTiles + x] ? '@' : ':');
}
printf("\n");
}
printf("\n");
}
/// -------------------------------------------------------------
*/
memcpy(m_videoFlags, newFlags, m_widthTiles * m_heightTiles);
}