/*
   Copyright (C) 2002-2009 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.
*/

/* -- xrecord.c -- */

#include "x11vnc.h"
#include "xwrappers.h"
#include "win_utils.h"
#include "cleanup.h"
#include "userinput.h"
#include "winattr_t.h"
#include "scrollevent_t.h"
#include "unixpw.h"

#define SCR_EV_MAX 128
scroll_event_t scr_ev[SCR_EV_MAX];
int scr_ev_cnt;

int xrecording = 0;
int xrecord_set_by_keys = 0;
int xrecord_set_by_mouse = 0;
Window xrecord_focus_window = None;
Window xrecord_wm_window = None;
Window xrecord_ptr_window = None;
KeySym xrecord_keysym = NoSymbol;

#define NAMEINFO 2048
char xrecord_name_info[NAMEINFO];

#define SCR_ATTR_CACHE 8
winattr_t scr_attr_cache[SCR_ATTR_CACHE];
static double attr_cache_max_age = 1.5;

Display *rdpy_data = NULL;		/* Data connection for RECORD */
Display *rdpy_ctrl = NULL;		/* Control connection for RECORD */

Display *gdpy_ctrl = NULL;
Display *gdpy_data = NULL;
int xserver_grabbed = 0;

int trap_record_xerror(Display *, XErrorEvent *);

void initialize_xrecord(void);
void zerodisp_xrecord(void);
void shutdown_xrecord(void);
int xrecord_skip_keysym(rfbKeySym keysym);
int xrecord_skip_button(int new, int old);
int xrecord_scroll_keysym(rfbKeySym keysym);
void check_xrecord_reset(int force);
void xrecord_watch(int start, int setby);


#if LIBVNCSERVER_HAVE_RECORD
static XRecordRange *rr_CA = NULL;
static XRecordRange *rr_CW = NULL;
static XRecordRange *rr_GS = NULL;
static XRecordRange *rr_scroll[10];
static XRecordContext rc_scroll;
static XRecordClientSpec rcs_scroll;
static XRecordRange *rr_grab[10];
static XRecordContext rc_grab;
static XRecordClientSpec rcs_grab;
#endif
static XErrorEvent *trapped_record_xerror_event;

static void xrecord_grabserver(int start);
static int xrecord_vi_scroll_keysym(rfbKeySym keysym);
static int xrecord_emacs_scroll_keysym(rfbKeySym keysym);
static int lookup_attr_cache(Window win, int *cache_index, int *next_index);
#if LIBVNCSERVER_HAVE_RECORD
static void record_CA(XPointer ptr, XRecordInterceptData *rec_data);
static void record_CW(XPointer ptr, XRecordInterceptData *rec_data);
static void record_switch(XPointer ptr, XRecordInterceptData *rec_data);
static void record_grab(XPointer ptr, XRecordInterceptData *rec_data);
static void shutdown_record_context(XRecordContext rc, int bequiet, int reopen);
#endif
static void check_xrecord_grabserver(void);


int trap_record_xerror(Display *d, XErrorEvent *error) {
	trapped_record_xerror = 1;
	trapped_record_xerror_event = error;

	if (d) {} /* unused vars warning: */

	return 0;
}

static void xrecord_grabserver(int start) {
	XErrorHandler old_handler = NULL;
	int rc = 0;

	if (debug_grabs) {
		fprintf(stderr, "xrecord_grabserver%d/%d %.5f\n",
			xserver_grabbed, start, dnowx());
	}

	if (! gdpy_ctrl || ! gdpy_data) {
		return;
	}
#if LIBVNCSERVER_HAVE_RECORD
	if (!start) {
		if (! rc_grab) {
			return;
		}
		XRecordDisableContext(gdpy_ctrl, rc_grab);
		XRecordFreeContext(gdpy_ctrl, rc_grab);
		XFlush_wr(gdpy_ctrl);
		rc_grab = 0;
		return;
	}

	xserver_grabbed = 0;

	rr_grab[0] = rr_GS;
	rcs_grab = XRecordAllClients;

	rc_grab = XRecordCreateContext(gdpy_ctrl, 0, &rcs_grab, 1, rr_grab, 1);
	trapped_record_xerror = 0;
	old_handler = XSetErrorHandler(trap_record_xerror);

	XSync(gdpy_ctrl, True);

	if (! rc_grab || trapped_record_xerror) {
		XCloseDisplay_wr(gdpy_ctrl);
		XCloseDisplay_wr(gdpy_data);
		gdpy_ctrl = NULL;
		gdpy_data = NULL;
		XSetErrorHandler(old_handler);
		return;
	}
	rc = XRecordEnableContextAsync(gdpy_data, rc_grab, record_grab, NULL);
	if (!rc || trapped_record_xerror) {
		XCloseDisplay_wr(gdpy_ctrl);
		XCloseDisplay_wr(gdpy_data);
		gdpy_ctrl = NULL;
		gdpy_data = NULL;
		XSetErrorHandler(old_handler);
		return;
	}
	XFlush_wr(gdpy_data);
	XSetErrorHandler(old_handler);
#else
	if (!rc || !old_handler) {}
#endif
	if (debug_grabs) {
		fprintf(stderr, "xrecord_grabserver-done: %.5f\n", dnowx());
	}
}

void zerodisp_xrecord(void) {
	rdpy_data = NULL;
	rdpy_ctrl = NULL;
	gdpy_data = NULL;
	gdpy_ctrl = NULL;
}

void initialize_xrecord(void) {
	use_xrecord = 0;
	if (! xrecord_present) {
		return;
	}
	if (nofb) {
		return;
	}
	if (noxrecord) {
		return;
	}
	RAWFB_RET_VOID
#if LIBVNCSERVER_HAVE_RECORD

	if (rr_CA) XFree_wr(rr_CA);
	if (rr_CW) XFree_wr(rr_CW);
	if (rr_GS) XFree_wr(rr_GS);

	rr_CA = XRecordAllocRange();
	rr_CW = XRecordAllocRange();
	rr_GS = XRecordAllocRange();
	if (!rr_CA || !rr_CW || !rr_GS) {
		return;
	}
	/* protocol request ranges: */
	rr_CA->core_requests.first = X_CopyArea;
	rr_CA->core_requests.last  = X_CopyArea;
	
	rr_CW->core_requests.first = X_ConfigureWindow;
	rr_CW->core_requests.last  = X_ConfigureWindow;

	rr_GS->core_requests.first = X_GrabServer;
	rr_GS->core_requests.last  = X_UngrabServer;

	X_LOCK;
	/* open a 2nd control connection to DISPLAY: */
	if (rdpy_data) {
		XCloseDisplay_wr(rdpy_data);
		rdpy_data = NULL;
	}
	if (rdpy_ctrl) {
		XCloseDisplay_wr(rdpy_ctrl);
		rdpy_ctrl = NULL;
	}
	rdpy_ctrl = XOpenDisplay_wr(DisplayString(dpy));
	if (!rdpy_ctrl) {
		fprintf(stderr, "rdpy_ctrl open failed: %s / %s / %s / %s\n", getenv("DISPLAY"), DisplayString(dpy), getenv("XAUTHORITY"), getenv("XAUTHORIT_"));
	}
	XSync(dpy, True);
	XSync(rdpy_ctrl, True);
	/* open datalink connection to DISPLAY: */
	rdpy_data = XOpenDisplay_wr(DisplayString(dpy));
	if (!rdpy_data) {
		fprintf(stderr, "rdpy_data open failed\n");
	}
	if (!rdpy_ctrl || ! rdpy_data) {
		X_UNLOCK;
		return;
	}
	disable_grabserver(rdpy_ctrl, 0);
	disable_grabserver(rdpy_data, 0);

	use_xrecord = 1;

	/*
	 * now set up the GrabServer watcher.  We get GrabServer
	 * deadlock in XRecordCreateContext() even with XTestGrabServer
	 * in place, why?  Not sure, so we manually watch for grabs...
	 */
	if (gdpy_data) {
		XCloseDisplay_wr(gdpy_data);
		gdpy_data = NULL;
	}
	if (gdpy_ctrl) {
		XCloseDisplay_wr(gdpy_ctrl);
		gdpy_ctrl = NULL;
	}
	xserver_grabbed = 0;

	gdpy_ctrl = XOpenDisplay_wr(DisplayString(dpy));
	if (!gdpy_ctrl) {
		fprintf(stderr, "gdpy_ctrl open failed\n");
	}
	XSync(dpy, True);
	XSync(gdpy_ctrl, True);
	gdpy_data = XOpenDisplay_wr(DisplayString(dpy));
	if (!gdpy_data) {
		fprintf(stderr, "gdpy_data open failed\n");
	}
	if (gdpy_ctrl && gdpy_data) {
		disable_grabserver(gdpy_ctrl, 0);
		disable_grabserver(gdpy_data, 0);
		xrecord_grabserver(1);
	}
	X_UNLOCK;
#endif
}

void shutdown_xrecord(void) {
#if LIBVNCSERVER_HAVE_RECORD

	if (debug_grabs) {
		fprintf(stderr, "shutdown_xrecord%d %.5f\n",
			xserver_grabbed, dnowx());
	}

	if (rr_CA) XFree_wr(rr_CA);
	if (rr_CW) XFree_wr(rr_CW);
	if (rr_GS) XFree_wr(rr_GS);

	rr_CA = NULL;
	rr_CW = NULL;
	rr_GS = NULL;

	X_LOCK;
	if (rdpy_ctrl && rc_scroll) {
		XRecordDisableContext(rdpy_ctrl, rc_scroll);
		XRecordFreeContext(rdpy_ctrl, rc_scroll);
		XSync(rdpy_ctrl, False);
		rc_scroll = 0;
	}
		
	if (gdpy_ctrl && rc_grab) {
		XRecordDisableContext(gdpy_ctrl, rc_grab);
		XRecordFreeContext(gdpy_ctrl, rc_grab);
		XSync(gdpy_ctrl, False);
		rc_grab = 0;
	}
		
	if (rdpy_data) {
		XCloseDisplay_wr(rdpy_data);
		rdpy_data = NULL;
	}
	if (rdpy_ctrl) {
		XCloseDisplay_wr(rdpy_ctrl);
		rdpy_ctrl = NULL;
	}
	if (gdpy_data) {
		XCloseDisplay_wr(gdpy_data);
		gdpy_data = NULL;
	}
	if (gdpy_ctrl) {
		XCloseDisplay_wr(gdpy_ctrl);
		gdpy_ctrl = NULL;
	}
	xserver_grabbed = 0;
	X_UNLOCK;
#endif
	use_xrecord = 0;

	if (debug_grabs) {
		fprintf(stderr, "shutdown_xrecord-done: %.5f\n", dnowx());
	}
}

int xrecord_skip_keysym(rfbKeySym keysym) {
	KeySym sym = (KeySym) keysym;
	int ok = -1, matched = 0;

	if (scroll_key_list) {
		int k, exclude = 0;
		if (scroll_key_list[0]) {
			exclude = 1;
		}
		k = 1;
		while (scroll_key_list[k] != NoSymbol) {
			if (scroll_key_list[k++] == sym) {
				matched = 1;
				break;
			}
		}
		if (exclude) {
			if (matched) {
				return 1;
			} else {
				ok = 1;
			}
		} else {
			if (matched) {
				ok = 1;
			} else {
				ok = 0;
			}
		}
	}
	if (ok == 1) {
		return 0;
	} else if (ok == 0) {
		return 1;
	}

	/* apply various heuristics: */

	if (IsModifierKey(sym)) {
		/* Shift, Control, etc, usu. generate no scrolls */
		return 1;
	}
	if (sym == XK_space && scroll_term) {
		/* space in a terminal is usu. full page... */
		Window win;
		static Window prev_top = None;
		int size = 256;
		static char name[256];

		X_LOCK;
		win = query_pointer(rootwin);
		X_UNLOCK;
		if (win != None && win != rootwin) {
			if (prev_top != None && win == prev_top) {
				;	/* use cached result */
			} else {
				prev_top = win;
				X_LOCK;
				win = descend_pointer(6, win, name, size);
				X_UNLOCK;
			}
			if (match_str_list(name, scroll_term)) {
				return 1;
			}
		}
	}

	/* TBD use typing_rate() so */
	return 0;
}

int xrecord_skip_button(int new, int old) {
	/* unused vars warning: */
	if (new || old) {}

	return 0;
}

static int xrecord_vi_scroll_keysym(rfbKeySym keysym) {
	KeySym sym = (KeySym) keysym;
	if (sym == XK_J || sym == XK_j || sym == XK_K || sym == XK_k) {
		return 1;	/* vi */
	}
	if (sym == XK_D || sym == XK_d || sym == XK_U || sym == XK_u) {
		return 1;	/* Ctrl-d/u */
	}
	if (sym == XK_Z || sym == XK_z) {
		return 1;	/* zz, zt, zb .. */
	}
	return 0;
}

static int xrecord_emacs_scroll_keysym(rfbKeySym keysym) {
	KeySym sym = (KeySym) keysym;
	if (sym == XK_N || sym == XK_n || sym == XK_P || sym == XK_p) {
		return 1;	/* emacs */
	}
	/* Must be some more ... */
	return 0;
}

int xrecord_scroll_keysym(rfbKeySym keysym) {
	KeySym sym = (KeySym) keysym;
	/* X11/keysymdef.h */

	if (sym == XK_Return || sym == XK_KP_Enter || sym == XK_Linefeed) {
		return 1;	/* Enter */
	}
	if (sym==XK_Up || sym==XK_KP_Up || sym==XK_Down || sym==XK_KP_Down) {
		return 1;	/* U/D arrows */
	}
	if (sym == XK_Left || sym == XK_KP_Left || sym == XK_Right ||
	    sym == XK_KP_Right) {
		return 1;	/* L/R arrows */
	}
	if (xrecord_vi_scroll_keysym(keysym)) {
		return 1;
	}
	if (xrecord_emacs_scroll_keysym(keysym)) {
		return 1;
	}
	return 0;
}

static int lookup_attr_cache(Window win, int *cache_index, int *next_index) {
	double now, t, oldest = 0.0;
	int i, old_index = -1, count = 0;
	Window cwin;

	*cache_index = -1;
	*next_index  = -1;
	
	if (win == None) {
		return 0;
	}
	if (attr_cache_max_age == 0.0) {
		return 0;
	}

	dtime0(&now);
	for (i=0; i < SCR_ATTR_CACHE; i++) {

		cwin = scr_attr_cache[i].win;
		t = scr_attr_cache[i].time;

		if (now > t + attr_cache_max_age) {
			/* expire it even if it is the one we want */
			scr_attr_cache[i].win = cwin = None;
			scr_attr_cache[i].fetched = 0;
			scr_attr_cache[i].valid = 0;
		}

		if (*next_index == -1 && cwin == None) {
			*next_index = i;
		}
		if (*next_index == -1) {
			/* record oldest */
			if (old_index == -1 || t < oldest) {
				oldest = t;
				old_index = i;
			}
		}
		if (cwin != None) {
			count++;
		}
		if (cwin == win) {
			if (*cache_index == -1) {
				*cache_index = i;
			} else {
				/* remove dups */
				scr_attr_cache[i].win = None;
				scr_attr_cache[i].fetched = 0;
				scr_attr_cache[i].valid = 0;
			}
		}
	}
	if (*next_index == -1) {
		*next_index = old_index;
	}

if (0) fprintf(stderr, "lookup_attr_cache count: %d\n", count);
	if (*cache_index != -1) {
		return 1;
	} else {
		return 0;
	}
}


static XID xrecord_seq = 0;
static double xrecord_start = 0.0;

#if LIBVNCSERVER_HAVE_RECORD
static void record_CA(XPointer ptr, XRecordInterceptData *rec_data) {
	xCopyAreaReq *req;
	Window src = None, dst = None, c;
	XWindowAttributes attr;
	int src_x, src_y, dst_x, dst_y, rx, ry;
	int good = 1, dx, dy, k=0, i;
	unsigned int w, h;
	int dba = 0, db = debug_scroll;
	int cache_index, next_index, valid;

	if (dba || db) {
		if (rec_data->category == XRecordFromClient) {
			req = (xCopyAreaReq *) rec_data->data;
			if (req->reqType == X_CopyArea) {
				src = req->srcDrawable;
				dst = req->dstDrawable;
			}
		}
	}

if (dba || db > 1) fprintf(stderr, "record_CA-%d id_base: 0x%lx  ptr: 0x%lx "
	"seq: 0x%lx rc: 0x%lx  cat: %d  swapped: %d 0x%lx/0x%lx\n", k++,
	rec_data->id_base, (unsigned long) ptr, xrecord_seq, rc_scroll,
	rec_data->category, rec_data->client_swapped, src, dst);

	if (! xrecording) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CA-%d\n", k++);

	if (rec_data->id_base == 0) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CA-%d\n", k++);

	if ((XID) ptr != xrecord_seq) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CA-%d\n", k++);

	if (rec_data->category != XRecordFromClient) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CA-%d\n", k++);

	req = (xCopyAreaReq *) rec_data->data;

	if (req->reqType != X_CopyArea) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CA-%d\n", k++);

/*

xterm, gnome-terminal, others.

Note we miss the X_ImageText8 that clears the block cursor.  So there is a
short period of time with a painting error: two cursors, one above the other.

 X_ImageText8 
    draw: 0x8c00017 nChars: 1, gc: 0x8c00013, x: 101, y: 585, chars=' '
 X_ClearArea 
    window: 0x8c00018, x:   2, y: 217, w:  10, h:   5
 X_FillPoly 
    draw: 0x8c00018 gc: 0x8c0000a, shape: 0, coordMode: 0,
 X_FillPoly 
    draw: 0x8c00018 gc: 0x8c0000b, shape: 0, coordMode: 0,
 X_CopyArea 
    src: 0x8c00017, dst: 0x8c00017, gc: 0x8c00013, srcX:  17, srcY:  15, dstX:  17, dstY:   2, w: 480, h: 572
 X_ChangeWindowAttributes 
 X_ClearArea 
    window: 0x8c00017, x:  17, y: 574, w: 480, h:  13
 X_ChangeWindowAttributes 

 */

	src = req->srcDrawable;
	dst = req->dstDrawable;
	src_x = req->srcX;
	src_y = req->srcY;
	dst_x = req->dstX;
	dst_y = req->dstY;
	w = req->width;
	h = req->height;

	if (w*h < (unsigned int) scrollcopyrect_min_area) {
		good = 0;
	} else if (!src || !dst) {
		good = 0;
	} else if (src != dst) {
		good = 0;
	} else if (scr_ev_cnt >= SCR_EV_MAX) {
		good = 0;
	}

	dx = dst_x - src_x;
	dy = dst_y - src_y;

	if (dx != 0 && dy != 0) {
		good = 0;
	}

	if (! good) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CA-%d\n", k++);

	/*
	 * after all of the above succeeds, now contact X server.
	 * we try to get away with some caching here.
	 */
	if (lookup_attr_cache(src, &cache_index, &next_index)) {
		i = cache_index;
		attr.x = scr_attr_cache[i].x;
		attr.y = scr_attr_cache[i].y;
		attr.width = scr_attr_cache[i].width;
		attr.height = scr_attr_cache[i].height;
		attr.map_state = scr_attr_cache[i].map_state;
		rx = scr_attr_cache[i].rx;
		ry = scr_attr_cache[i].ry;
		valid = scr_attr_cache[i].valid;

	} else {
		valid = valid_window(src, &attr, 1);

		if (valid) {
			if (!xtranslate(src, rootwin, 0, 0, &rx, &ry, &c, 1)) {
				valid = 0;
			}
		}
		if (next_index >= 0) {
			i = next_index;
			scr_attr_cache[i].win = src;
			scr_attr_cache[i].fetched = 1;
			scr_attr_cache[i].valid = valid;
			scr_attr_cache[i].time = dnow();
			if (valid) {
				scr_attr_cache[i].x = attr.x;
				scr_attr_cache[i].y = attr.y;
				scr_attr_cache[i].width = attr.width;
				scr_attr_cache[i].height = attr.height;
				scr_attr_cache[i].border_width = attr.border_width;
				scr_attr_cache[i].depth = attr.depth;
				scr_attr_cache[i].class = attr.class;
				scr_attr_cache[i].backing_store =
				    attr.backing_store;
				scr_attr_cache[i].map_state = attr.map_state;

				scr_attr_cache[i].rx = rx;
				scr_attr_cache[i].ry = ry;
			}
		}
	}

	if (! valid) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CA-%d\n", k++);

	if (attr.map_state != IsViewable) {
		return;
	}


 if (0 || dba || db) {
	double st, dt;
	st = (double) rec_data->server_time/1000.0;
	dt = (dnow() - servertime_diff) - st;
	fprintf(stderr, "record_CA-%d *FOUND_SCROLL: src: 0x%lx dx: %d dy: %d "
	"x: %d y: %d w: %d h: %d st: %.4f %.4f  %.4f\n", k++, src, dx, dy,
	src_x, src_y, w, h, st, dt, dnowx());
 }

	i = scr_ev_cnt;

	scr_ev[i].win = src;
	scr_ev[i].frame = None;
	scr_ev[i].dx = dx;
	scr_ev[i].dy = dy;
	scr_ev[i].x = rx + dst_x;
	scr_ev[i].y = ry + dst_y;
	scr_ev[i].w = w;
	scr_ev[i].h = h;
	scr_ev[i].t = ((double) rec_data->server_time)/1000.0;
	scr_ev[i].win_x = rx;
	scr_ev[i].win_y = ry;
	scr_ev[i].win_w = attr.width;
	scr_ev[i].win_h = attr.height;
	scr_ev[i].new_x = 0;
	scr_ev[i].new_y = 0;
	scr_ev[i].new_w = 0;
	scr_ev[i].new_h = 0;

	if (dx == 0) {
		if (dy > 0) {
			scr_ev[i].new_x = rx + src_x;
			scr_ev[i].new_y = ry + src_y;
			scr_ev[i].new_w = w;
			scr_ev[i].new_h = dy;
		} else {
			scr_ev[i].new_x = rx + src_x;
			scr_ev[i].new_y = ry + dst_y + h;
			scr_ev[i].new_w = w;
			scr_ev[i].new_h = -dy;
		}
	} else if (dy == 0) {
		if (dx > 0) {
			scr_ev[i].new_x = rx + src_x;
			scr_ev[i].new_y = rx + src_y;
			scr_ev[i].new_w = dx;
			scr_ev[i].new_h = h;
		} else {
			scr_ev[i].new_x = rx + dst_x + w;
			scr_ev[i].new_y = ry + src_y;
			scr_ev[i].new_w = -dx;
			scr_ev[i].new_h = h;
		}
	}

	scr_ev_cnt++;
}

typedef struct cw_event {
	Window win;
	int x, y, w, h;
} cw_event_t;

#define MAX_CW 128
static cw_event_t cw_events[MAX_CW];

static void record_CW(XPointer ptr, XRecordInterceptData *rec_data) {
	xConfigureWindowReq *req;
	Window win = None, c;
	Window src = None, dst = None;
	XWindowAttributes attr;
	int absent = 0x100000;
	int src_x, src_y, dst_x, dst_y, rx, ry;
	int good = 1, dx, dy, k=0, i, j, match, list[3];
	int f_x, f_y, f_w, f_h;
	int x, y, w, h;
	int x0, y0, w0, h0, x1, y1, w1, h1, x2, y2, w2, h2;
	static int index = 0;
	unsigned int vals[4];
	unsigned tmask;
	char *data;
	int dba = 0, db = debug_scroll;
	int cache_index, next_index, valid;

	if (db) {
		if (rec_data->category == XRecordFromClient) {
			req = (xConfigureWindowReq *) rec_data->data;
			if (req->reqType == X_ConfigureWindow) {
				src = req->window;
			}
		}
	}

if (dba || db > 1) fprintf(stderr, "record_CW-%d id_base: 0x%lx  ptr: 0x%lx "
	"seq: 0x%lx rc: 0x%lx  cat: %d  swapped: %d 0x%lx/0x%lx\n", k++,
	rec_data->id_base, (unsigned long) ptr, xrecord_seq, rc_scroll,
	rec_data->category, rec_data->client_swapped, src, dst);


	if (! xrecording) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	if ((XID) ptr != xrecord_seq) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	if (rec_data->id_base == 0) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	if (rec_data->category == XRecordStartOfData) {
		index = 0;
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	if (rec_data->category != XRecordFromClient) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	if (rec_data->client_swapped) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	req = (xConfigureWindowReq *) rec_data->data;

	if (req->reqType != X_ConfigureWindow) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	tmask = req->mask;

	tmask &= ~CWX;
	tmask &= ~CWY;
	tmask &= ~CWWidth;
	tmask &= ~CWHeight;

	if (tmask) {
		/* require no more than these 4 flags */
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	f_x = req->mask & CWX;
	f_y = req->mask & CWY;
	f_w = req->mask & CWWidth;
	f_h = req->mask & CWHeight;

	if (! f_x || ! f_y)  {
		if (f_w && f_h) {
			;	/* netscape 4.x style */
		} else {
			return;
		}
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	if ( (f_w && !f_h) || (!f_w && f_h) ) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);
		
	for (i=0; i<4; i++) {
		vals[i] = 0;
	}

	data = (char *)req;
	data += sz_xConfigureWindowReq;

	for (i=0; i<req->length; i++) {
		unsigned int v;
		/*
		 * We use unsigned int for the values.  There were
		 * some crashes on 64bit machines with unsigned longs.
		 * Need to check that X protocol sends 32bit values.
		 */
		v = *( (unsigned int *) data);
if (db > 1) fprintf(stderr, "  vals[%d]  0x%x/%d\n", i, v, v);
		vals[i] = v;
		data += sizeof(unsigned int);
	}

	if (index >= MAX_CW) {
		int i, j;

		/* FIXME, circular, etc. */
		for (i=0; i<2; i++) {
			j = MAX_CW - 2 + i;
			cw_events[i].win = cw_events[j].win;
			cw_events[i].x = cw_events[j].x;
			cw_events[i].y = cw_events[j].y;
			cw_events[i].w = cw_events[j].w;
			cw_events[i].h = cw_events[j].h;
		}
		index = 2;
	}

	if (! f_x && ! f_y) {
		/* netscape 4.x style  CWWidth,CWHeight */
		vals[2] = vals[0];
		vals[3] = vals[1];
		vals[0] = 0;
		vals[1] = 0;
	}

	cw_events[index].win = req->window;

	if (! f_x) {
		cw_events[index].x = absent;
	} else {
		cw_events[index].x = (int) vals[0];
	}
	if (! f_y) {
		cw_events[index].y = absent;
	} else {
		cw_events[index].y = (int) vals[1];
	}

	if (! f_w) {
		cw_events[index].w = absent;
	} else {
		cw_events[index].w = (int) vals[2];
	}
	if (! f_h) {
		cw_events[index].h = absent;
	} else {
		cw_events[index].h = (int) vals[3];
	}

	x = cw_events[index].x;
	y = cw_events[index].y;
	w = cw_events[index].w;
	h = cw_events[index].h;
	win = cw_events[index].win;

if (dba || db) fprintf(stderr, "  record_CW ind: %d win: 0x%lx x: %d y: %d w: %d h: %d\n",
	index, win, x, y, w, h);

	index++;

	if (index < 3) {
		good = 0;
	} else if (w != absent && h != absent &&
	    w*h < scrollcopyrect_min_area) {
		good = 0;
	}

	if (! good) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	match = 0;
	for (j=index - 1; j >= 0; j--) {
		if (cw_events[j].win == win) {
			list[match++] = j;
		}
		if (match >= 3) {
			break;
		}
	}

	if (match != 3) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

/*

Mozilla:

Up arrow: window moves down a bit (dy > 0):

 X_ConfigureWindow 
    length: 7, window: 0x2e000cd, mask: 0xf, v0 0,  v1 -18,  v2 760,  v3 906,  v4 327692,  v5 48234701,  v6 3, 
        CW-mask: CWX,CWY,CWWidth,CWHeight,
 X_ConfigureWindow 
    length: 5, window: 0x2e000cd, mask: 0x3, v0 0,  v1 0,  v2 506636,  v3 48234701,  v4 48234511, 
        CW-mask: CWX,CWY,
 X_ConfigureWindow 
    length: 7, window: 0x2e000cd, mask: 0xf, v0 0,  v1 0,  v2 760,  v3 888,  v4 65579,  v5 0,  v6 108009, 
        CW-mask: CWX,CWY,CWWidth,CWHeight,

Down arrow: window moves up a bit (dy < 0):

 X_ConfigureWindow 
    length: 7, window: 0x2e000cd, mask: 0xf, v0 0,  v1 0,  v2 760,  v3 906,  v4 327692,  v5 48234701,  v6 262147, 
        CW-mask: CWX,CWY,CWWidth,CWHeight,
 X_ConfigureWindow 
    length: 5, window: 0x2e000cd, mask: 0x3, v0 0,  v1 -18,  v2 506636,  v3 48234701,  v4 48234511, 
        CW-mask: CWX,CWY,
 X_ConfigureWindow 
    length: 7, window: 0x2e000cd, mask: 0xf, v0 0,  v1 0,  v2 760,  v3 888,  v4 96555,  v5 48265642,  v6 48265262, 
        CW-mask: CWX,CWY,CWWidth,CWHeight,


Netscape 4.x

Up arrow:
71.76142   0.01984 X_ConfigureWindow
    length: 7, window: 0x9800488, mask: 0xf, v0 0,  v1 -15,  v2 785,  v3 882,  v4 327692,  v5 159384712,  v6 1769484,
        CW-mask: CWX,CWY,CWWidth,CWHeight,
71.76153   0.00011 X_ConfigureWindow
    length: 5, window: 0x9800488, mask: 0xc, v0 785,  v1 867,  v2 329228,  v3 159384712,  v4 159383555,
        CW-mask:       CWWidth,CWHeight,
                XXX,XXX
71.76157   0.00003 X_ConfigureWindow
    length: 5, window: 0x9800488, mask: 0x3, v0 0,  v1 0,  v2 131132,  v3 159385313,  v4 328759,
        CW-mask: CWX,CWY,
                         XXX,XXX

Down arrow:
72.93147   0.01990 X_ConfigureWindow
    length: 5, window: 0x9800488, mask: 0xc, v0 785,  v1 882,  v2 328972,  v3 159384712,  v4 159383555,
        CW-mask:       CWWidth,CWHeight,
                XXX,XXX
72.93156   0.00009 X_ConfigureWindow
    length: 5, window: 0x9800488, mask: 0x3, v0 0,  v1 -15,  v2 458764,  v3 159384712,  v4 159383567,
        CW-mask: CWX,CWY,
72.93160   0.00004 X_ConfigureWindow
    length: 7, window: 0x9800488, mask: 0xf, v0 0,  v1 0,  v2 785,  v3 867,  v4 131132,  v5 159385335,  v6 328759,
        CW-mask: CWX,CWY,CWWidth,CWHeight,


sadly, probably need to handle some more...

 */
	x0 = cw_events[list[2]].x;
	y0 = cw_events[list[2]].y;
	w0 = cw_events[list[2]].w;
	h0 = cw_events[list[2]].h;

	x1 = cw_events[list[1]].x;
	y1 = cw_events[list[1]].y;
	w1 = cw_events[list[1]].w;
	h1 = cw_events[list[1]].h;

	x2 = cw_events[list[0]].x;
	y2 = cw_events[list[0]].y;
	w2 = cw_events[list[0]].w;
	h2 = cw_events[list[0]].h;

	/* see NS4 XXX's above: */
	if (w2 == absent || h2 == absent) {
		/* up arrow */
		if (w2 == absent) {
			w2 = w1;
		}
		if (h2 == absent) {
			h2 = h1;
		}
	}
	if (x1 == absent || y1 == absent) {
		/* up arrow */
		if (x1 == absent) {
			x1 = x2;
		}
		if (y1 == absent) {
			y1 = y2;
		}
	}
	if (x0 == absent || y0 == absent) {
		/* down arrow */
		if (x0 == absent) {
			/* hmmm... what to do */
			x0 = x2;
		}
		if (y0 == absent) {
			y0 = y2;
		}
	}

if (dba) fprintf(stderr, "%d/%d/%d/%d  %d/%d/%d/%d  %d/%d/%d/%d\n", x0, y0, w0, h0, x1, y1, w1, h1, x2, y2, w2, h2);

	dy = y1 - y0;
	dx = x1 - x0;

	src_x = x2;
	src_y = y2;
	w = w2;
	h = h2;

	/* check w and h before we modify them */
	if (w <= 0 || h <= 0) {
		good = 0;
	} else if (w == absent || h == absent) {
		good = 0;
	}
	if (! good) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	if (dy > 0) {
		h -= dy;	
	} else {
		h += dy;	
		src_y -= dy;
	}
	if (dx > 0) {
		w -= dx;	
	} else {
		w += dx;	
		src_x -= dx;
	}

	dst_x = src_x + dx;
	dst_y = src_y + dy;

	if (x0 == absent || x1 == absent || x2 == absent) {
		good = 0;
	} else if (y0 == absent || y1 == absent || y2 == absent) {
		good = 0;
	} else if (dx != 0 && dy != 0) {
		good = 0;
	} else if (w0 - w2 != nabs(dx)) {
		good = 0;
	} else if (h0 - h2 != nabs(dy)) {
		good = 0;
	} else if (scr_ev_cnt >= SCR_EV_MAX) {
		good = 0;
	}

	if (! good) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	/*
	 * geometry OK.
	 * after all of the above succeeds, now contact X server.
	 */
	if (lookup_attr_cache(win, &cache_index, &next_index)) {
		i = cache_index;
		attr.x = scr_attr_cache[i].x;
		attr.y = scr_attr_cache[i].y;
		attr.width = scr_attr_cache[i].width;
		attr.height = scr_attr_cache[i].height;
		attr.map_state = scr_attr_cache[i].map_state;
		rx = scr_attr_cache[i].rx;
		ry = scr_attr_cache[i].ry;
		valid = scr_attr_cache[i].valid;

if (0) fprintf(stderr, "lookup_attr_cache hit:  %2d %2d 0x%lx %d\n",
    cache_index, next_index, win, valid);

	} else {
		valid = valid_window(win, &attr, 1);

if (0) fprintf(stderr, "lookup_attr_cache MISS: %2d %2d 0x%lx %d\n",
    cache_index, next_index, win, valid);

		if (valid) {
			if (!xtranslate(win, rootwin, 0, 0, &rx, &ry, &c, 1)) {
				valid = 0;
			}
		}
		if (next_index >= 0) {
			i = next_index;
			scr_attr_cache[i].win = win;
			scr_attr_cache[i].fetched = 1;
			scr_attr_cache[i].valid = valid;
			scr_attr_cache[i].time = dnow();
			if (valid) {
				scr_attr_cache[i].x = attr.x;
				scr_attr_cache[i].y = attr.y;
				scr_attr_cache[i].width = attr.width;
				scr_attr_cache[i].height = attr.height;
				scr_attr_cache[i].depth = attr.depth;
				scr_attr_cache[i].class = attr.class;
				scr_attr_cache[i].backing_store =
				    attr.backing_store;
				scr_attr_cache[i].map_state = attr.map_state;

				scr_attr_cache[i].rx = rx;
				scr_attr_cache[i].ry = ry;
			}
		}
	}

	if (! valid) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

	if (attr.map_state != IsViewable) {
		return;
	}
if (db > 1) fprintf(stderr, "record_CW-%d\n", k++);

 if (0 || dba || db) {
	double st, dt;
	st = (double) rec_data->server_time/1000.0;
	dt = (dnow() - servertime_diff) - st;
	fprintf(stderr, "record_CW-%d *FOUND_SCROLL: win: 0x%lx dx: %d dy: %d "
	"x: %d y: %d w: %d h: %d  st: %.4f  dt: %.4f  %.4f\n", k++, win,
	dx, dy, src_x, src_y, w, h, st, dt, dnowx());
 }

	i = scr_ev_cnt;

	scr_ev[i].win = win;
	scr_ev[i].frame = None;
	scr_ev[i].dx = dx;
	scr_ev[i].dy = dy;
	scr_ev[i].x = rx + dst_x;
	scr_ev[i].y = ry + dst_y;
	scr_ev[i].w = w;
	scr_ev[i].h = h;
	scr_ev[i].t = ((double) rec_data->server_time)/1000.0;
	scr_ev[i].win_x = rx;
	scr_ev[i].win_y = ry;
	scr_ev[i].win_w = attr.width;
	scr_ev[i].win_h = attr.height;
	scr_ev[i].new_x = 0;
	scr_ev[i].new_y = 0;
	scr_ev[i].new_w = 0;
	scr_ev[i].new_h = 0;

	if (dx == 0) {
		if (dy > 0) {
			scr_ev[i].new_x = rx + src_x;
			scr_ev[i].new_y = ry + src_y;
			scr_ev[i].new_w = w;
			scr_ev[i].new_h = dy;
		} else {
			scr_ev[i].new_x = rx + src_x;
			scr_ev[i].new_y = ry + dst_y + h;
			scr_ev[i].new_w = w;
			scr_ev[i].new_h = -dy;
		}
	} else if (dy == 0) {
		if (dx > 0) {
			scr_ev[i].new_x = rx + src_x;
			scr_ev[i].new_y = rx + src_y;
			scr_ev[i].new_w = dx;
			scr_ev[i].new_h = h;
		} else {
			scr_ev[i].new_x = rx + dst_x + w;
			scr_ev[i].new_y = ry + src_y;
			scr_ev[i].new_w = -dx;
			scr_ev[i].new_h = h;
		}
	}

	/* indicate we have a new one */
	scr_ev_cnt++;

	index = 0;
}

static void record_switch(XPointer ptr, XRecordInterceptData *rec_data) {
	static int first = 1;
	xReq *req;

	if (first) {
		int i;
		for (i=0; i<SCR_ATTR_CACHE; i++) {
			scr_attr_cache[i].win = None;
			scr_attr_cache[i].fetched = 0;
			scr_attr_cache[i].valid = 0;
			scr_attr_cache[i].time = 0.0;
		}
		first = 0;
	}

	/* should handle control msgs, start/stop/etc */
	if (rec_data->category == XRecordStartOfData) {
		record_CW(ptr, rec_data);
	} else if (rec_data->category == XRecordEndOfData) {
		;
	} else if (rec_data->category == XRecordClientStarted) {
		;
	} else if (rec_data->category == XRecordClientDied) {
		;
	} else if (rec_data->category == XRecordFromServer) {
		;
	}

	if (rec_data->category != XRecordFromClient) {
		XRecordFreeData(rec_data);
		return;
	}

	req = (xReq *) rec_data->data;

	if (req->reqType == X_CopyArea) {
		record_CA(ptr, rec_data);
	} else if (req->reqType == X_ConfigureWindow) {
		record_CW(ptr, rec_data);
	} else {
		;
	}
	XRecordFreeData(rec_data);
}

static void record_grab(XPointer ptr, XRecordInterceptData *rec_data) {
	xReq *req;
	int db = 0;

	if (debug_grabs) db = 1;

	/* should handle control msgs, start/stop/etc */
	if (rec_data->category == XRecordStartOfData) {
		;
	} else if (rec_data->category == XRecordEndOfData) {
		;
	} else if (rec_data->category == XRecordClientStarted) {
		;
	} else if (rec_data->category == XRecordClientDied) {
		;
	} else if (rec_data->category == XRecordFromServer) {
		;
	}

	if (rec_data->category != XRecordFromClient) {
		XRecordFreeData(rec_data);
		return;
	}

	req = (xReq *) rec_data->data;

	if (req->reqType == X_GrabServer) {
		double now = dnowx();
		xserver_grabbed++;
		if (db) rfbLog("X server Grabbed:    %d %.5f\n", xserver_grabbed, now);
		if (xserver_grabbed > 1) {
			/* 
			 * some apps do multiple grabs... very unlikely
			 * two apps will be doing it at same time.
			 */
			xserver_grabbed = 1;
		}
	} else if (req->reqType == X_UngrabServer) {
		double now = dnowx();
		xserver_grabbed--;
		if (xserver_grabbed < 0) {
			xserver_grabbed = 0;
		}
		if (db) rfbLog("X server Un-Grabbed: %d %.5f\n", xserver_grabbed, now);
	} else {
		;
	}
	XRecordFreeData(rec_data);

	/* unused vars warning: */
	if (ptr) {}
}
#endif

static void check_xrecord_grabserver(void) {
#if LIBVNCSERVER_HAVE_RECORD
	int last_val, cnt = 0, i, max = 10;
	double d;
	if (!gdpy_ctrl || !gdpy_data) {
		return;
	}
	if (unixpw_in_progress) return;

	dtime0(&d);
	XFlush_wr(gdpy_ctrl);
	for (i=0; i<max; i++) {
		last_val = xserver_grabbed;
		XRecordProcessReplies(gdpy_data);
		if (xserver_grabbed != last_val) {
			cnt++;
		} else if (i > 2) {
			break;
		}
	}
	if (cnt) {
		XFlush_wr(gdpy_ctrl);
	}
 if (debug_grabs && cnt > 0) {
	d = dtime(&d);
fprintf(stderr, "check_xrecord_grabserver: cnt=%d i=%d %.4f\n", cnt, i, d);
 }
#endif
}

#if LIBVNCSERVER_HAVE_RECORD
static void shutdown_record_context(XRecordContext rc, int bequiet, int reopen) {
	int ret1, ret2;
	int verb = (!bequiet && !quiet);

	RAWFB_RET_VOID
	if (0 || debug_scroll) {
		rfbLog("shutdown_record_context(0x%lx, %d, %d)\n", rc,
		    bequiet, reopen);
		verb = 1;
	}

	ret1 = XRecordDisableContext(rdpy_ctrl, rc);
	if (!ret1 && verb) {
		rfbLog("XRecordDisableContext(0x%lx) failed.\n", rc);	
	}
	ret2 = XRecordFreeContext(rdpy_ctrl, rc);
	if (!ret2 && verb) {
		rfbLog("XRecordFreeContext(0x%lx) failed.\n", rc);	
	}
	XFlush_wr(rdpy_ctrl);

	if (reopen == 2 && ret1 && ret2) {
		reopen = 0;	/* 2 means reopen only on failure  */
	}
	if (reopen && gdpy_ctrl) {
		check_xrecord_grabserver();
		if (xserver_grabbed) {
			rfbLog("shutdown_record_context: skip reopen,"
			    " server grabbed\n");	
			reopen = 0;
		}
	}
	if (reopen) {
		char *dpystr = DisplayString(dpy);

		if (debug_scroll) {
			rfbLog("closing RECORD data connection.\n");
		}
		XCloseDisplay_wr(rdpy_data);
		rdpy_data = NULL;

		if (debug_scroll) {
			rfbLog("closing RECORD control connection.\n");
		}
		XCloseDisplay_wr(rdpy_ctrl);
		rdpy_ctrl = NULL;

		rdpy_ctrl = XOpenDisplay_wr(dpystr);

		if (! rdpy_ctrl) {
			rfbLog("Failed to reopen RECORD control connection:"
			    "%s\n", dpystr);
			rfbLog("  disabling RECORD scroll detection.\n");
			use_xrecord = 0;
			return;
		}
		XSync(dpy, False);

		disable_grabserver(rdpy_ctrl, 0);
		XSync(rdpy_ctrl, True);

		rdpy_data = XOpenDisplay_wr(dpystr);

		if (! rdpy_data) {
			rfbLog("Failed to reopen RECORD data connection:"
			    "%s\n", dpystr);
			rfbLog("  disabling RECORD scroll detection.\n");
			XCloseDisplay_wr(rdpy_ctrl);
			rdpy_ctrl = NULL;
			use_xrecord = 0;
			return;
		}
		disable_grabserver(rdpy_data, 0);

		if (debug_scroll || (! bequiet && reopen == 2)) {
			rfbLog("reopened RECORD data and control display"
			    " connections: %s\n", dpystr);
		}
	}
}
#endif

void check_xrecord_reset(int force) {
	static double last_reset = 0.0;
	int reset_time  = 60, require_idle  = 10;
	int reset_time2 = 600, require_idle2 = 40;
	double now = 0.0;
	XErrorHandler old_handler = NULL;

	if (gdpy_ctrl) {
		X_LOCK;
		check_xrecord_grabserver();
		X_UNLOCK;
	} else {
		/* more dicey if not watching grabserver */
		reset_time = reset_time2;
		require_idle = require_idle2;
	}

	if (!use_xrecord) {
		return;
	}
	if (xrecording) {
		return;
	}
	if (button_mask) {
		return;
	}
	if (xserver_grabbed) {
		return;
	}

	if (unixpw_in_progress) return;

#if LIBVNCSERVER_HAVE_RECORD
	if (! rc_scroll) {
		return;
	}
	now = dnow();
	if (last_reset == 0.0) {
		last_reset = now;
		return;
	}
	/*
	 * try to wait for a break in input to reopen the displays
	 * this is only to avoid XGrabServer deadlock on the repopens.
	 */
	if (force) {
		;
	} else if (now < last_reset + reset_time) {
		return;
	} else if (now < last_pointer_click_time + require_idle)  {
		return;
	} else if (now < last_keyboard_time + require_idle)  {
		return;
	}
	X_LOCK;
	trapped_record_xerror = 0;
	old_handler = XSetErrorHandler(trap_record_xerror);

	/* unlikely, but check again since we will definitely be doing it. */
	if (gdpy_ctrl) {
		check_xrecord_grabserver();
		if (xserver_grabbed) {
			XSetErrorHandler(old_handler);
			X_UNLOCK;
			return;
		}
	}
	
	shutdown_record_context(rc_scroll, 0, 1);
	rc_scroll = 0;

	XSetErrorHandler(old_handler);
	X_UNLOCK;

	last_reset = now;
#else
	if (!old_handler || now == 0.0 || !last_reset || !force) {}
#endif
}

#define RECORD_ERROR_MSG \
	if (! quiet) { \
		rfbLog("trapped RECORD XError: %s %d/%d/%d (0x%lx)\n", \
		    xerror_string(trapped_record_xerror_event), \
		    (int) trapped_record_xerror_event->error_code, \
		    (int) trapped_record_xerror_event->request_code, \
		    (int) trapped_record_xerror_event->minor_code, \
		    (int) trapped_record_xerror_event->resourceid); \
	}

void xrecord_watch(int start, int setby) {
#if LIBVNCSERVER_HAVE_RECORD
	Window focus, wm, c, clast;
	static double create_time = 0.0;
	int rc;
	int do_shutdown = 0;
	int reopen_dpys = 1;
	XErrorHandler old_handler = NULL;
	static Window last_win = None, last_result = None;
#endif
	int db = debug_scroll;
	double now;
	static double last_error = 0.0;

if (0) db = 1;

	if (nofb) {
		xrecording = 0;
		return;
	}
	if (use_threads) {
		/* XXX not working.  Still?  Painting errors. */
		static int first = 1;
		if (first) {
			if (use_xrecord && !getenv("XRECORD_THREADS")) {
				rfbLog("xrecord_watch: disabling scroll detection in -threads mode.\n");
				rfbLog("xrecord_watch: Set -env XRECORD_THREADS=1 to enable it.\n");
				use_xrecord = 0;
				xrecording = 0;
			}
			first = 0;
		}
		if (!use_xrecord && !xrecording) {
			return;
		}
	}

	dtime0(&now);
	if (now < last_error + 0.5) {
		return;
	}

	if (gdpy_ctrl) {
		X_LOCK;
		check_xrecord_grabserver();
		X_UNLOCK;
		if (xserver_grabbed) {
if (db || debug_grabs) fprintf(stderr, "xrecord_watch: %d/%d  out xserver_grabbed\n", start, setby);
			return;
		}
	}

#if LIBVNCSERVER_HAVE_RECORD
	if (! start) {
		int shut_reopen = 2, shut_time = 25;
if (db || debug_grabs) fprintf(stderr, "XRECORD OFF: %d/%d  %.4f\n", xrecording, setby, now - x11vnc_start);
		xrecording = 0;
		if (! rc_scroll) {
			xrecord_focus_window = None;
			xrecord_wm_window = None;
			xrecord_ptr_window = None;
			xrecord_keysym = NoSymbol;
			rcs_scroll = 0;
			return;
		}

		if (! do_shutdown && now > create_time + shut_time) {
			/* XXX unstable if we keep a RECORD going forever */
			do_shutdown = 1;
		}

		SCR_LOCK;
		
		if (do_shutdown) {
if (db > 1) fprintf(stderr, "=== shutdown-scroll 0x%lx\n", rc_scroll);
			X_LOCK;
			trapped_record_xerror = 0;
			old_handler = XSetErrorHandler(trap_record_xerror);

			shutdown_record_context(rc_scroll, 0, shut_reopen);
			rc_scroll = 0;

			/*
			 * n.b. there is a grabserver issue wrt
			 * XRecordCreateContext() even though rdpy_ctrl
			 * is set imprevious to grabs.  Perhaps a bug
			 * in the X server or library...
			 *
			 * If there are further problems, a thought
			 * to recreate rc_scroll right after the
			 * reopen.
			 */

			if (! use_xrecord) {
				XSetErrorHandler(old_handler);
				X_UNLOCK;
				SCR_UNLOCK;
				return;
			}

			XRecordProcessReplies(rdpy_data);

			if (trapped_record_xerror) {
				RECORD_ERROR_MSG;
				last_error = now;
			}

			XSetErrorHandler(old_handler);
			X_UNLOCK;
			SCR_UNLOCK;

		} else {
			if (rcs_scroll) {
if (db > 1) fprintf(stderr, "=== disab-scroll 0x%lx 0x%lx\n", rc_scroll, rcs_scroll);
				X_LOCK;
				trapped_record_xerror = 0;
				old_handler =
				    XSetErrorHandler(trap_record_xerror);

				rcs_scroll = XRecordCurrentClients;
				XRecordUnregisterClients(rdpy_ctrl, rc_scroll,
				    &rcs_scroll, 1);
				XRecordDisableContext(rdpy_ctrl, rc_scroll);
				XFlush_wr(rdpy_ctrl);
				XRecordProcessReplies(rdpy_data);

				if (trapped_record_xerror) {
					RECORD_ERROR_MSG;

					shutdown_record_context(rc_scroll,
					    0, reopen_dpys);
					rc_scroll = 0;

					last_error = now;

					if (! use_xrecord) {
						XSetErrorHandler(old_handler);
						X_UNLOCK;
						SCR_UNLOCK;
						return;
					}
				}
				XSetErrorHandler(old_handler);
				X_UNLOCK;
			}
		}

		SCR_UNLOCK;
		/*
		 * XXX if we do a XFlush_wr(rdpy_ctrl) here we get:
		 *

		X Error of failed request:  XRecordBadContext
		  Major opcode of failed request:  145 (RECORD)
		  Minor opcode of failed request:  5 (XRecordEnableContext)
		  Context in failed request:  0x2200013
		  Serial number of failed request:  29
		  Current serial number in output stream:  29

		 *
		 * need to figure out what is going on... since it may lead
		 * infrequent failures.
		 */
		xrecord_focus_window = None;
		xrecord_wm_window = None;
		xrecord_ptr_window = None;
		xrecord_keysym = NoSymbol;
		rcs_scroll = 0;
		return;
	}
if (db || debug_grabs) fprintf(stderr, "XRECORD ON:  %d/%d  %.4f\n", xrecording, setby, now - x11vnc_start);

	if (xrecording) {
		return;
	}

	if (do_shutdown && rc_scroll) {
		static int didmsg = 0;
		/* should not happen... */
		if (0 || !didmsg) {
			rfbLog("warning: do_shutdown && rc_scroll\n");
			didmsg = 1;
		}
		xrecord_watch(0, SCR_NONE);
	}

	xrecording = 0;
	xrecord_focus_window = None;
	xrecord_wm_window = None;
	xrecord_ptr_window = None;
	xrecord_keysym = NoSymbol;
	xrecord_set_by_keys  = 0;
	xrecord_set_by_mouse = 0;

	/* get the window with focus and mouse pointer: */
	clast = None;
	focus = None;
	wm = None;

	X_LOCK;
	SCR_LOCK;
#if 0
	/*
	 * xrecord_focus_window / focus not currently used... save a
	 * round trip to the X server for now.
	 * N.B. our heuristic is inaccurate: if he is scrolling and
	 * drifts off of the scrollbar onto another application we
	 * will catch that application, not the starting ones.
	 * check_xrecord_{keys,mouse} mitigates this somewhat by
	 * delaying calls to xrecord_watch as much as possible.
	 */
	XGetInputFocus(dpy, &focus, &i);
#endif

	wm = query_pointer(rootwin);
	if (wm) {
		c = wm;
	} else {
		c = rootwin;
	}

	/* descend a bit to avoid wm frames: */
	if (c != rootwin && c == last_win) {
		/* use cached results to avoid roundtrips: */
		clast = last_result;
	} else if (scroll_good_all == NULL && scroll_skip_all == NULL) {
		/* more efficient if name info not needed. */
		xrecord_name_info[0] = '\0';
		clast = descend_pointer(6, c, NULL, 0);
	} else {
		char *nm;
		int matched_good = 0, matched_skip = 0;

		clast = descend_pointer(6, c, xrecord_name_info, NAMEINFO);
if (db) fprintf(stderr, "name_info: %s\n", xrecord_name_info);

		nm = xrecord_name_info;

		if (scroll_good_all) {
			matched_good += match_str_list(nm, scroll_good_all);
		}
		if (setby == SCR_KEY && scroll_good_key) {
			matched_good += match_str_list(nm, scroll_good_key);
		}
		if (setby == SCR_MOUSE && scroll_good_mouse) {
			matched_good += match_str_list(nm, scroll_good_mouse);
		}
		if (scroll_skip_all) {
			matched_skip += match_str_list(nm, scroll_skip_all);
		}
		if (setby == SCR_KEY && scroll_skip_key) {
			matched_skip += match_str_list(nm, scroll_skip_key);
		}
		if (setby == SCR_MOUSE && scroll_skip_mouse) {
			matched_skip += match_str_list(nm, scroll_skip_mouse);
		}

		if (!matched_good && matched_skip) {
			clast = None;
		}
	}
	if (c != rootwin) {
		/* cache results for possible use next call */
		last_win = c;
		last_result = clast;
	}

	if (!clast || clast == rootwin) {
if (db) fprintf(stderr, "--- xrecord_watch: SKIP.\n");
		X_UNLOCK;
		SCR_UNLOCK;
		return;
	}

	/* set protocol request ranges: */
	rr_scroll[0] = rr_CA;
	rr_scroll[1] = rr_CW;

	/*
	 * start trapping... there still are some occasional failures
	 * not yet understood, likely some race condition WRT the 
	 * context being setup.
	 */
	trapped_record_xerror = 0;
	old_handler = XSetErrorHandler(trap_record_xerror);

	if (! rc_scroll) {
		/* do_shutdown case or first time in */

		if (gdpy_ctrl) {
			/*
			 * Even though rdpy_ctrl is impervious to grabs
			 * at this point, we still get deadlock, why?
			 * It blocks in the library find_display() call.
			 */
			check_xrecord_grabserver();
			if (xserver_grabbed) {
				XSetErrorHandler(old_handler);
				X_UNLOCK;
				SCR_UNLOCK;
				return;
			}
		}
		rcs_scroll = (XRecordClientSpec) clast;
		rc_scroll = XRecordCreateContext(rdpy_ctrl, 0, &rcs_scroll, 1,
		    rr_scroll, 2);

		if (! do_shutdown) {
			XSync(rdpy_ctrl, False);
		}
if (db) fprintf(stderr, "NEW rc:    0x%lx\n", rc_scroll);
		if (rc_scroll) {
			dtime0(&create_time);
		} else {
			rcs_scroll = 0;
		}

	} else if (! do_shutdown) {
		if (rcs_scroll) {
			/*
			 * should have been unregistered in xrecord_watch(0)...
			 */
			rcs_scroll = XRecordCurrentClients;
			XRecordUnregisterClients(rdpy_ctrl, rc_scroll,
			    &rcs_scroll, 1);

if (db > 1) fprintf(stderr, "=2= unreg-scroll 0x%lx 0x%lx\n", rc_scroll, rcs_scroll);

		}
		
		rcs_scroll = (XRecordClientSpec) clast;

if (db > 1) fprintf(stderr, "=-=   reg-scroll 0x%lx 0x%lx\n", rc_scroll, rcs_scroll);

		if (!XRecordRegisterClients(rdpy_ctrl, rc_scroll, 0,
		    &rcs_scroll, 1, rr_scroll, 2)) {
			if (1 || now > last_error + 60) {
				rfbLog("failed to register client 0x%lx with"
				    " X RECORD context rc_scroll.\n", clast);
			}
			last_error = now;
			rcs_scroll = 0;
			/* continue on for now... */
		}
	}

	XFlush_wr(rdpy_ctrl);

if (db) fprintf(stderr, "rc_scroll: 0x%lx\n", rc_scroll);
	if (trapped_record_xerror) {
		RECORD_ERROR_MSG;
	}

	if (! rc_scroll) {
		XSetErrorHandler(old_handler);
		X_UNLOCK;
		SCR_UNLOCK;
		use_xrecord = 0;
		rfbLog("failed to create X RECORD context rc_scroll.\n");
		rfbLog("  switching to -noscrollcopyrect mode.\n");
		return;
	} else if (! rcs_scroll || trapped_record_xerror) {
		/* try again later */
		shutdown_record_context(rc_scroll, 0, reopen_dpys);
		rc_scroll = 0;
		last_error = now;

		XSetErrorHandler(old_handler);
		X_UNLOCK;
		SCR_UNLOCK;
		return;
	}

	xrecord_focus_window = focus;
#if 0
	/* xrecord_focus_window currently unused. */
	if (! xrecord_focus_window) {
		xrecord_focus_window = clast;
	}
#endif
	xrecord_wm_window = wm;
	if (! xrecord_wm_window) {
		xrecord_wm_window = clast;
	}

	xrecord_ptr_window = clast;

	xrecording = 1;
	xrecord_seq++;
	dtime0(&xrecord_start);

	rc = XRecordEnableContextAsync(rdpy_data, rc_scroll, record_switch,
	    (XPointer) xrecord_seq);

	if (!rc || trapped_record_xerror) {
		if (1 || now > last_error + 60) {
			rfbLog("failed to enable RECORD context "
			    "rc_scroll: 0x%lx rc: %d\n", rc_scroll, rc);
			if (trapped_record_xerror) {
				RECORD_ERROR_MSG;
			}
		}
		shutdown_record_context(rc_scroll, 0, reopen_dpys);
		rc_scroll = 0;
		last_error = now;
		xrecording = 0;
		/* continue on for now... */
	}
	XSetErrorHandler(old_handler);

	/* XXX this may cause more problems than it solves... */
	if (use_xrecord) {
		XFlush_wr(rdpy_data);
	}

	X_UNLOCK;
	SCR_UNLOCK;
#endif
}


