/*
Copyright (C) 2002-2010 Karl J. Runge <runge@karlrunge.com>
All rights reserved.
This file is part of x11vnc.
x11vnc 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.
x11vnc 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 x11vnc; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
or see <http://www.gnu.org/licenses/>.
In addition, as a special exception, Karl J. Runge
gives permission to link the code of its release of x11vnc with the
OpenSSL project's "OpenSSL" library (or with modified versions of it
that use the same license as the "OpenSSL" library), and distribute
the linked executables. You must obey the GNU General Public License
in all respects for all of the code used other than "OpenSSL". If you
modify this file, you may extend this exception to your version of the
file, but you are not obligated to do so. If you do not wish to do
so, delete this exception statement from your version.
*/
/* -- xdamage.c -- */
#include "x11vnc.h"
#include "xwrappers.h"
#include "userinput.h"
#include "unixpw.h"
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
Damage xdamage = 0;
#endif
#ifndef XDAMAGE
#define XDAMAGE 1
#endif
int use_xdamage = XDAMAGE; /* use the xdamage rects for scanline hints */
int xdamage_present = 0;
#ifdef MACOSX
int xdamage_max_area = 50000;
#else
int xdamage_max_area = 20000; /* pixels */
#endif
double xdamage_memory = 1.0; /* in units of NSCAN */
int xdamage_tile_count = 0, xdamage_direct_count = 0;
double xdamage_scheduled_mark = 0.0;
double xdamage_crazy_time = 0.0;
double xdamage_crazy_delay = 300.0;
sraRegionPtr xdamage_scheduled_mark_region = NULL;
sraRegionPtr *xdamage_regions = NULL;
int xdamage_ticker = 0;
int XD_skip = 0, XD_tot = 0, XD_des = 0; /* for stats */
void add_region_xdamage(sraRegionPtr new_region);
void clear_xdamage_mark_region(sraRegionPtr markregion, int flush);
int collect_non_X_xdamage(int x_in, int y_in, int w_in, int h_in, int call);
int collect_xdamage(int scancnt, int call);
int xdamage_hint_skip(int y);
void initialize_xdamage(void);
void create_xdamage_if_needed(int force);
void destroy_xdamage_if_needed(void);
void check_xdamage_state(void);
static void record_desired_xdamage_rect(int x, int y, int w, int h);
static void record_desired_xdamage_rect(int x, int y, int w, int h) {
/*
* Unfortunately we currently can't trust an xdamage event
* to correspond to real screen damage. E.g. focus-in for
* mozilla (depending on wm) will mark the whole toplevel
* area as damaged, when only the border has changed.
* Similar things for terminal windows.
*
* This routine uses some heuristics to detect small enough
* damage regions that we will not have a performance problem
* if we believe them even though they are wrong. We record
* the corresponding tiles the damage regions touch.
*/
int dt_x, dt_y, nt_x1, nt_y1, nt_x2, nt_y2, nt;
int ix, iy, cnt = 0;
int area = w*h, always_accept = 0;
/*
* XXX: not working yet, slow and overlaps with scan_display()
* probably slow because tall skinny rectangles very inefficient
* in general and in direct_fb_copy() (100X slower then horizontal).
*/
int use_direct_fb_copy = 0;
int wh_min, wh_max;
static int first = 1, udfb = 0;
/* compiler warning: */
nt_x1 = 0; nt_y1 = 0; nt_x2 = 0; nt_y2 = 0;
if (first) {
if (getenv("XD_DFC")) {
udfb = 1;
}
first = 0;
}
if (udfb) {
use_direct_fb_copy = 1;
}
if (xdamage_max_area <= 0) {
always_accept = 1;
}
if (!always_accept && area > xdamage_max_area) {
return;
}
dt_x = w / tile_x;
dt_y = h / tile_y;
if (w < h) {
wh_min = w;
wh_max = h;
} else {
wh_min = h;
wh_max = w;
}
if (!always_accept && dt_y >= 3 && area > 4000) {
/*
* if it is real it should be caught by a normal scanline
* poll, but we might as well keep if small (tall line?).
*/
return;
}
if (use_direct_fb_copy) {
X_UNLOCK;
direct_fb_copy(x, y, x + w, y + h, 1);
xdamage_direct_count++;
X_LOCK;
} else if (0 && wh_min < tile_x/4 && wh_max > 30 * wh_min) {
/* try it for long, skinny rects, XXX still no good */
X_UNLOCK;
direct_fb_copy(x, y, x + w, y + h, 1);
xdamage_direct_count++;
X_LOCK;
} else {
if (ntiles_x == 0 || ntiles_y == 0) {
/* too early. */
return;
}
nt_x1 = nfix( (x)/tile_x, ntiles_x);
nt_x2 = nfix((x+w)/tile_x, ntiles_x);
nt_y1 = nfix( (y)/tile_y, ntiles_y);
nt_y2 = nfix((y+h)/tile_y, ntiles_y);
/*
* loop over the rectangle of tiles (1 tile for a small
* input rect).
*/
for (ix = nt_x1; ix <= nt_x2; ix++) {
for (iy = nt_y1; iy <= nt_y2; iy++) {
nt = ix + iy * ntiles_x;
cnt++;
if (! tile_has_xdamage_diff[nt]) {
XD_des++;
tile_has_xdamage_diff[nt] = 1;
}
/* not used: */
tile_row_has_xdamage_diff[iy] = 1;
xdamage_tile_count++;
}
}
}
if (debug_xdamage > 1) {
fprintf(stderr, "xdamage: desired: %dx%d+%d+%d\tA: %6d tiles="
"%02d-%02d/%02d-%02d tilecnt: %d\n", w, h, x, y,
w * h, nt_x1, nt_x2, nt_y1, nt_y2, cnt);
}
}
void add_region_xdamage(sraRegionPtr new_region) {
sraRegionPtr reg;
int prev_tick, nreg;
if (! xdamage_regions) {
return;
}
nreg = (xdamage_memory * NSCAN) + 1;
prev_tick = xdamage_ticker - 1;
if (prev_tick < 0) {
prev_tick = nreg - 1;
}
reg = xdamage_regions[prev_tick];
if (reg != NULL && new_region != NULL) {
if (debug_xdamage > 1) fprintf(stderr, "add_region_xdamage: prev_tick: %d reg %p new_region %p\n", prev_tick, (void *)reg, (void *)new_region);
sraRgnOr(reg, new_region);
}
}
void clear_xdamage_mark_region(sraRegionPtr markregion, int flush) {
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
XEvent ev;
sraRegionPtr tmpregion;
int count = 0;
RAWFB_RET_VOID
if (! xdamage_present || ! use_xdamage) {
return;
}
if (! xdamage) {
return;
}
if (! xdamage_base_event_type) {
return;
}
if (unixpw_in_progress) return;
X_LOCK;
if (flush) {
XFlush_wr(dpy);
}
while (XCheckTypedEvent(dpy, xdamage_base_event_type+XDamageNotify, &ev)) {
count++;
}
/* clear the whole damage region */
XDamageSubtract(dpy, xdamage, None, None);
X_UNLOCK;
if (debug_tiles || debug_xdamage) {
fprintf(stderr, "clear_xdamage_mark_region: %d\n", count);
}
if (! markregion) {
/* NULL means mark the whole display */
tmpregion = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
add_region_xdamage(tmpregion);
sraRgnDestroy(tmpregion);
} else {
add_region_xdamage(markregion);
}
#else
if (0) flush++; /* compiler warnings */
if (0) markregion = NULL;
#endif
}
int collect_non_X_xdamage(int x_in, int y_in, int w_in, int h_in, int call) {
sraRegionPtr tmpregion;
sraRegionPtr reg;
static int rect_count = 0;
int nreg, ccount = 0, dcount = 0, ecount = 0;
static time_t last_rpt = 0;
time_t now;
double tm, dt;
int x, y, w, h, x2, y2;
if (call && debug_xdamage > 1) fprintf(stderr, "collect_non_X_xdamage: %d %d %d %d - %d / %d\n", x_in, y_in, w_in, h_in, call, use_xdamage);
if (! use_xdamage) {
return 0;
}
if (! xdamage_regions) {
return 0;
}
dtime0(&tm);
nreg = (xdamage_memory * NSCAN) + 1;
if (call == 0) {
xdamage_ticker = (xdamage_ticker+1) % nreg;
xdamage_direct_count = 0;
reg = xdamage_regions[xdamage_ticker];
if (reg != NULL) {
sraRgnMakeEmpty(reg);
}
} else {
if (xdamage_ticker < 0) {
xdamage_ticker = 0;
}
reg = xdamage_regions[xdamage_ticker];
}
if (reg == NULL) {
return 0;
}
if (x_in < 0) {
return 0;
}
x = x_in;
y = y_in;
w = w_in;
h = h_in;
/* translate if needed */
if (clipshift) {
/* set coords relative to fb origin */
if (0 && rootshift) {
/*
* Note: not needed because damage is
* relative to subwin, not rootwin.
*/
x = x - off_x;
y = y - off_y;
}
if (clipshift) {
x = x - coff_x;
y = y - coff_y;
}
x2 = x + w; /* upper point */
x = nfix(x, dpy_x); /* place both in fb area */
x2 = nfix(x2, dpy_x+1);
w = x2 - x; /* recompute w */
y2 = y + h;
y = nfix(y, dpy_y);
y2 = nfix(y2, dpy_y+1);
h = y2 - y;
if (w <= 0 || h <= 0) {
return 0;
}
}
if (debug_xdamage > 2) {
fprintf(stderr, "xdamage: -> event %dx%d+%d+%d area:"
" %d dups: %d %s reg: %p\n", w, h, x, y, w*h, dcount,
(w*h > xdamage_max_area) ? "TOO_BIG" : "", (void *)reg);
}
record_desired_xdamage_rect(x, y, w, h);
tmpregion = sraRgnCreateRect(x, y, x + w, y + h);
sraRgnOr(reg, tmpregion);
sraRgnDestroy(tmpregion);
rect_count++;
ccount++;
if (0 && xdamage_direct_count) {
fb_push();
}
dt = dtime(&tm);
if ((debug_tiles > 1 && ecount) || (debug_tiles && ecount > 200)
|| debug_xdamage > 1) {
fprintf(stderr, "collect_non_X_xdamage(%d): %.4f t: %.4f ev/dup/accept"
"/direct %d/%d/%d/%d\n", call, dt, tm - x11vnc_start, ecount,
dcount, ccount, xdamage_direct_count);
}
now = time(NULL);
if (! last_rpt) {
last_rpt = now;
}
if (now > last_rpt + 15) {
double rat = -1.0;
if (XD_tot) {
rat = ((double) XD_skip)/XD_tot;
}
if (debug_tiles || debug_xdamage) {
fprintf(stderr, "xdamage: == scanline skip/tot: "
"%04d/%04d =%.3f rects: %d desired: %d\n",
XD_skip, XD_tot, rat, rect_count, XD_des);
}
XD_skip = 0;
XD_tot = 0;
XD_des = 0;
rect_count = 0;
last_rpt = now;
}
return 0;
}
int collect_xdamage(int scancnt, int call) {
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
XDamageNotifyEvent *dev;
XEvent ev;
sraRegionPtr tmpregion;
sraRegionPtr reg;
static int rect_count = 0;
int nreg, ccount = 0, dcount = 0, ecount = 0;
static time_t last_rpt = 0;
time_t now;
int x, y, w, h, x2, y2;
int i, dup, next = 0, dup_max = 0;
#define DUPSZ 32
int dup_x[DUPSZ], dup_y[DUPSZ], dup_w[DUPSZ], dup_h[DUPSZ];
double tm, dt;
int mark_all = 0, retries = 0, too_many = 1000, tot_ev = 0;
RAWFB_RET(0)
if (scancnt) {} /* unused vars warning: */
if (! xdamage_present || ! use_xdamage) {
return 0;
}
if (! xdamage) {
return 0;
}
if (! xdamage_base_event_type) {
return 0;
}
if (! xdamage_regions) {
return 0;
}
dtime0(&tm);
nreg = (xdamage_memory * NSCAN) + 1;
if (call == 0) {
xdamage_ticker = (xdamage_ticker+1) % nreg;
xdamage_direct_count = 0;
reg = xdamage_regions[xdamage_ticker];
if (reg != NULL) {
sraRgnMakeEmpty(reg);
}
} else {
if (xdamage_ticker < 0) {
xdamage_ticker = 0;
}
reg = xdamage_regions[xdamage_ticker];
}
if (reg == NULL) {
return 0;
}
X_LOCK;
if (0) XFlush_wr(dpy);
if (0) XEventsQueued(dpy, QueuedAfterFlush);
come_back_for_more:
while (XCheckTypedEvent(dpy, xdamage_base_event_type+XDamageNotify, &ev)) {
/*
* TODO max cut off time in this loop?
* Could check QLength and if huge just mark the whole
* screen.
*/
ecount++;
tot_ev++;
if (mark_all) {
continue;
}
if (ecount == too_many) {
int nqa = XEventsQueued(dpy, QueuedAlready);
if (nqa >= too_many) {
static double last_msg = 0.0;
tmpregion = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
sraRgnOr(reg, tmpregion);
sraRgnDestroy(tmpregion);
if (dnow() > last_msg + xdamage_crazy_delay) {
rfbLog("collect_xdamage: too many xdamage events %d+%d\n", ecount, nqa);
last_msg = dnow();
}
mark_all = 1;
}
}
if (ev.type != xdamage_base_event_type + XDamageNotify) {
break;
}
dev = (XDamageNotifyEvent *) &ev;
if (dev->damage != xdamage) {
continue; /* not ours! */
}
x = dev->area.x;
y = dev->area.y;
w = dev->area.width;
h = dev->area.height;
/*
* we try to manually remove some duplicates because
* certain activities can lead to many 10's of dups
* in a row. The region work can be costly and reg is
* later used in xdamage_hint_skip loops, so it is good
* to skip them if possible.
*/
dup = 0;
for (i=0; i < dup_max; i++) {
if (dup_x[i] == x && dup_y[i] == y && dup_w[i] == w &&
dup_h[i] == h) {
dup = 1;
break;
}
}
if (dup) {
dcount++;
continue;
}
if (dup_max < DUPSZ) {
next = dup_max;
dup_max++;
} else {
next = (next+1) % DUPSZ;
}
dup_x[next] = x;
dup_y[next] = y;
dup_w[next] = w;
dup_h[next] = h;
/* translate if needed */
if (clipshift) {
/* set coords relative to fb origin */
if (0 && rootshift) {
/*
* Note: not needed because damage is
* relative to subwin, not rootwin.
*/
x = x - off_x;
y = y - off_y;
}
if (clipshift) {
x = x - coff_x;
y = y - coff_y;
}
x2 = x + w; /* upper point */
x = nfix(x, dpy_x); /* place both in fb area */
x2 = nfix(x2, dpy_x+1);
w = x2 - x; /* recompute w */
y2 = y + h;
y = nfix(y, dpy_y);
y2 = nfix(y2, dpy_y+1);
h = y2 - y;
if (w <= 0 || h <= 0) {
continue;
}
}
if (debug_xdamage > 2) {
fprintf(stderr, "xdamage: -> event %dx%d+%d+%d area:"
" %d dups: %d %s\n", w, h, x, y, w*h, dcount,
(w*h > xdamage_max_area) ? "TOO_BIG" : "");
}
record_desired_xdamage_rect(x, y, w, h);
tmpregion = sraRgnCreateRect(x, y, x + w, y + h);
sraRgnOr(reg, tmpregion);
sraRgnDestroy(tmpregion);
rect_count++;
ccount++;
}
if (mark_all) {
if (ecount + XEventsQueued(dpy, QueuedAlready) >= 3 * too_many && retries < 3) {
retries++;
XFlush_wr(dpy);
usleep(20 * 1000);
XFlush_wr(dpy);
ecount = 0;
goto come_back_for_more;
}
}
/* clear the whole damage region for next time. XXX check */
if (call == 1) {
XDamageSubtract(dpy, xdamage, None, None);
}
X_UNLOCK;
if (tot_ev > 20 * too_many) {
rfbLog("collect_xdamage: xdamage has gone crazy (screensaver or game?) ev: %d ret: %d\n", tot_ev, retries);
rfbLog("collect_xdamage: disabling xdamage for %d seconds.\n", (int) xdamage_crazy_delay);
destroy_xdamage_if_needed();
X_LOCK;
XSync(dpy, False);
while (XCheckTypedEvent(dpy, xdamage_base_event_type+XDamageNotify, &ev)) {
;
}
X_UNLOCK;
xdamage_crazy_time = dnow();
}
if (0 && xdamage_direct_count) {
fb_push();
}
dt = dtime(&tm);
if ((debug_tiles > 1 && ecount) || (debug_tiles && ecount > 200)
|| debug_xdamage > 1) {
fprintf(stderr, "collect_xdamage(%d): %.4f t: %.4f ev/dup/accept"
"/direct %d/%d/%d/%d\n", call, dt, tm - x11vnc_start, ecount,
dcount, ccount, xdamage_direct_count);
}
now = time(NULL);
if (! last_rpt) {
last_rpt = now;
}
if (now > last_rpt + 15) {
double rat = -1.0;
if (XD_tot) {
rat = ((double) XD_skip)/XD_tot;
}
if (debug_tiles || debug_xdamage) {
fprintf(stderr, "xdamage: == scanline skip/tot: "
"%04d/%04d =%.3f rects: %d desired: %d\n",
XD_skip, XD_tot, rat, rect_count, XD_des);
}
XD_skip = 0;
XD_tot = 0;
XD_des = 0;
rect_count = 0;
last_rpt = now;
}
#else
if (0) scancnt++; /* compiler warnings */
if (0) call++;
if (0) record_desired_xdamage_rect(0, 0, 0, 0);
#endif
return 0;
}
int xdamage_hint_skip(int y) {
static sraRegionPtr scanline = NULL;
static sraRegionPtr tmpl_y = NULL;
int fast_tmpl = 1;
sraRegionPtr reg, tmpl;
int ret, i, n, nreg;
#ifndef NO_NCACHE
static int ncache_no_skip = 0;
static double last_ncache_no_skip = 0.0;
static double last_ncache_no_skip_long = 0.0, ncache_fac = 0.25;
#endif
if (! xdamage_present || ! use_xdamage) {
return 0; /* cannot skip */
}
if (! xdamage_regions) {
return 0; /* cannot skip */
}
if (! scanline) {
/* keep it around to avoid malloc etc, recreate */
scanline = sraRgnCreate();
}
if (! tmpl_y) {
tmpl_y = sraRgnCreateRect(0, 0, dpy_x, 1);
}
nreg = (xdamage_memory * NSCAN) + 1;
#ifndef NO_NCACHE
if (ncache > 0) {
if (ncache_no_skip == 0) {
double now = g_now;
if (now > last_ncache_no_skip + 8.0) {
ncache_no_skip = 1;
} else if (now < last_bs_restore + 0.5) {
ncache_no_skip = 1;
} else if (now < last_su_restore + 0.5) {
ncache_no_skip = 1;
} else if (now < last_copyrect + 0.5) {
ncache_no_skip = 1;
}
if (ncache_no_skip) {
last_ncache_no_skip = dnow();
if (now > last_ncache_no_skip_long + 60.0) {
ncache_fac = 2.0;
last_ncache_no_skip_long = now;
} else {
ncache_fac = 0.25;
}
return 0;
}
} else {
if (ncache_no_skip++ >= ncache_fac*nreg + 4) {
ncache_no_skip = 0;
} else {
return 0;
}
}
}
#endif
if (fast_tmpl) {
sraRgnOffset(tmpl_y, 0, y);
tmpl = tmpl_y;
} else {
tmpl = sraRgnCreateRect(0, y, dpy_x, y+1);
}
ret = 1;
for (i=0; i<nreg; i++) {
/* go back thru the history starting at most recent */
n = (xdamage_ticker + nreg - i) % nreg;
reg = xdamage_regions[n];
if (reg == NULL) {
continue;
}
if (sraRgnEmpty(reg)) {
/* checking for emptiness is very fast */
continue;
}
sraRgnMakeEmpty(scanline);
sraRgnOr(scanline, tmpl);
if (sraRgnAnd(scanline, reg)) {
ret = 0;
break;
}
}
if (fast_tmpl) {
sraRgnOffset(tmpl_y, 0, -y);
} else {
sraRgnDestroy(tmpl);
}
if (0) fprintf(stderr, "xdamage_hint_skip: %d -> %d\n", y, ret);
return ret;
}
void initialize_xdamage(void) {
sraRegionPtr *ptr;
int i, nreg;
if (! xdamage_present) {
use_xdamage = 0;
}
if (xdamage_regions) {
ptr = xdamage_regions;
while (*ptr != NULL) {
sraRgnDestroy(*ptr);
ptr++;
}
free(xdamage_regions);
xdamage_regions = NULL;
}
if (use_xdamage) {
nreg = (xdamage_memory * NSCAN) + 2;
xdamage_regions = (sraRegionPtr *)
malloc(nreg * sizeof(sraRegionPtr));
for (i = 0; i < nreg; i++) {
ptr = xdamage_regions+i;
if (i == nreg - 1) {
*ptr = NULL;
} else {
*ptr = sraRgnCreate();
sraRgnMakeEmpty(*ptr);
}
}
/* set so will be 0 in first collect_xdamage call */
xdamage_ticker = -1;
}
}
void create_xdamage_if_needed(int force) {
RAWFB_RET_VOID
if (force) {}
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
if (! xdamage || force) {
X_LOCK;
xdamage = XDamageCreate(dpy, window, XDamageReportRawRectangles);
XDamageSubtract(dpy, xdamage, None, None);
X_UNLOCK;
rfbLog("created xdamage object: 0x%lx\n", xdamage);
}
#endif
}
void destroy_xdamage_if_needed(void) {
RAWFB_RET_VOID
#if LIBVNCSERVER_HAVE_LIBXDAMAGE
if (xdamage) {
XEvent ev;
X_LOCK;
XDamageDestroy(dpy, xdamage);
XFlush_wr(dpy);
if (xdamage_base_event_type) {
while (XCheckTypedEvent(dpy,
xdamage_base_event_type+XDamageNotify, &ev)) {
;
}
}
X_UNLOCK;
rfbLog("destroyed xdamage object: 0x%lx\n", xdamage);
xdamage = 0;
}
#endif
}
void check_xdamage_state(void) {
if (! xdamage_present) {
return;
}
/*
* Create or destroy the Damage object as needed, we don't want
* one if no clients are connected.
*/
if (xdamage_crazy_time > 0.0 && dnow() < xdamage_crazy_time + xdamage_crazy_delay) {
return;
}
if (client_count && use_xdamage) {
create_xdamage_if_needed(0);
if (xdamage_scheduled_mark > 0.0 && dnow() >
xdamage_scheduled_mark) {
if (xdamage_scheduled_mark_region) {
mark_region_for_xdamage(
xdamage_scheduled_mark_region);
sraRgnDestroy(xdamage_scheduled_mark_region);
xdamage_scheduled_mark_region = NULL;
} else {
mark_for_xdamage(0, 0, dpy_x, dpy_y);
}
xdamage_scheduled_mark = 0.0;
}
} else {
destroy_xdamage_if_needed();
}
}