/* -- 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;
int xserver_grabbed = 0;

int trap_record_xerror(Display *, XErrorEvent *);

void initialize_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 Display *gdpy_data = NULL;

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;

	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;
	}
	XSetErrorHandler(old_handler);
	XFlush_wr(gdpy_data);
#endif
	if (debug_grabs) {
		fprintf(stderr, "xrecord_grabserver-done: %.5f\n", dnowx());
	}
}

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));
	XSync(dpy, True);
	XSync(rdpy_ctrl, True);
	/* open datalink connection to DISPLAY: */
	rdpy_data = XOpenDisplay_wr(DisplayString(dpy));
	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));
	XSync(dpy, True);
	XSync(gdpy_ctrl, True);
	gdpy_data = XOpenDisplay_wr(DisplayString(dpy));
	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].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, dnow() - x11vnc_start);
 }

	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, dnow() - x11vnc_start);
 }

	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 = dnow() - x11vnc_start;
		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 = dnow() - x11vnc_start;
		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) {
	int last_val, cnt = 0, i, max = 10;
	double d;
#if LIBVNCSERVER_HAVE_RECORD
	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;
	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;
#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) {
	Window focus, wm, c, clast;
	static double create_time = 0.0;
	double now;
	static double last_error = 0.0;
	int rc, db = debug_scroll;
	int do_shutdown = 0;
	int reopen_dpys = 1;
	XErrorHandler old_handler = NULL;
	static Window last_win = None, last_result = None;

if (0) db = 1;

	if (nofb) {
		xrecording = 0;
		return;
	}
	if (use_threads) {
		/* XXX not working */
		use_xrecord = 0;
		xrecording = 0;
		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
}


