/* Copyright (C) 2002-2010 Karl J. Runge 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 . 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 %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(); } }