/* -- userinput.c -- */

#include "x11vnc.h"
#include "xwrappers.h"
#include "xdamage.h"
#include "xrecord.h"
#include "xinerama.h"
#include "win_utils.h"
#include "xevents.h"
#include "user.h"
#include "scan.h"
#include "cleanup.h"
#include "pointer.h"
#include "rates.h"
#include "keyboard.h"
#include "solid.h"
#include "xrandr.h"
#include "8to24.h"
#include "unixpw.h"
#include "macosx.h"
#include "macosxCGS.h"
#include "cursor.h"
#include "screen.h"
#include "connections.h"

/*
 * user input handling heuristics
 */
int defer_update_nofb = 6;	/* defer a shorter time under -nofb */
int last_scroll_type = SCR_NONE;


int get_wm_frame_pos(int *px, int *py, int *x, int *y, int *w, int *h,
    Window *frame, Window *win);
void parse_scroll_copyrect(void);
void parse_fixscreen(void);
void parse_wireframe(void);

void set_wirecopyrect_mode(char *str);
void set_scrollcopyrect_mode(char *str);
void initialize_scroll_keys(void);
void initialize_scroll_matches(void);
void initialize_scroll_term(void);
void initialize_max_keyrepeat(void);

int direct_fb_copy(int x1, int y1, int x2, int y2, int mark);
void fb_push(void);
int fb_push_wait(double max_wait, int flags);
void eat_viewonly_input(int max_eat, int keep);

void mark_for_xdamage(int x, int y, int w, int h);
void mark_region_for_xdamage(sraRegionPtr region);
void set_xdamage_mark(int x, int y, int w, int h);
int near_wm_edge(int x, int y, int w, int h, int px, int py);
int near_scrollbar_edge(int x, int y, int w, int h, int px, int py);

void check_fixscreen(void);
int check_xrecord(void);
int check_wireframe(void);
int fb_update_sent(int *count);
int check_user_input(double dt, double dtr, int tile_diffs, int *cnt);
void do_copyregion(sraRegionPtr region, int dx, int dy, int mode);

int check_ncache(int reset, int mode);
int find_rect(int idx, int x, int y, int w, int h);
int bs_restore(int idx, int *nbatch, sraRegionPtr rmask, XWindowAttributes *attr, int clip, int nopad, int *valid, int verb);
int try_to_fix_su(Window win, int idx, Window above, int *nbatch, char *mode);
int try_to_fix_resize_su(Window orig_frame, int orig_x, int orig_y, int orig_w, int orig_h,
    int x, int y, int w, int h, int try_batch);
int lookup_win_index(Window);
void set_ncache_xrootpmap(void);

static void get_client_regions(int *req, int *mod, int *cpy, int *num) ;
static void parse_scroll_copyrect_str(char *scr);
static void parse_wireframe_str(char *wf);
static void destroy_str_list(char **list);
static void draw_box(int x, int y, int w, int h, int restore);
static int do_bdpush(Window wm_win, int x0, int y0, int w0, int h0, int bdx,
    int bdy, int bdskinny);
static int set_ypad(void);
static void scale_mark(int x1, int y1, int x2, int y2, int mark);
static int push_scr_ev(double *age, int type, int bdpush, int bdx, int bdy,
    int bdskinny, int first_push);
static int crfix(int x, int dx, int Lx);
static int scrollability(Window win, int set);
static int eat_pointer(int max_ptr_eat, int keep);
static void set_bdpush(int type, double *last_bdpush, int *pushit);
static int repeat_check(double last_key_scroll);
static int check_xrecord_keys(void);
static int check_xrecord_mouse(void);
static int try_copyrect(Window orig_frame, Window frame, int x, int y, int w, int h,
    int dx, int dy, int *obscured, sraRegionPtr extra_clip, double max_wait, int *nbatch);
static int wireframe_mod_state();
static void check_user_input2(double dt);
static void check_user_input3(double dt, double dtr, int tile_diffs);
static void check_user_input4(double dt, double dtr, int tile_diffs);

winattr_t *cache_list;

/*
 * For -wireframe: find the direct child of rootwin that has the
 * pointer, assume that is the WM frame that contains the application
 * (i.e. wm reparents the app toplevel) return frame position and size
 * if successful.
 */
int get_wm_frame_pos(int *px, int *py, int *x, int *y, int *w, int *h,
    Window *frame, Window *win) {
#if !NO_X11
	Window r, c;
	XWindowAttributes attr;
	Bool ret;
	int rootx, rooty, wx, wy;
	unsigned int mask;
#endif

#ifdef MACOSX
	if (macosx_console) {
		return macosx_get_wm_frame_pos(px, py, x, y, w, h, frame, win);
	}
#endif

	RAWFB_RET(0)

#if NO_X11
	if (!px || !py || !x || !y || !w || !h || !frame || !win) {}
	return 0;
#else


	ret = XQueryPointer_wr(dpy, rootwin, &r, &c, &rootx, &rooty, &wx, &wy,
	    &mask);

	*frame = c;

	/* current pointer position is returned too */
	*px = rootx;
	*py = rooty;

	if (!ret || ! c || c == rootwin) {
		/* no immediate child */
		return 0;
	}

	/* child window position and size */
	if (! valid_window(c, &attr, 1)) {
		return 0;
	}

	*x = attr.x;
	*y = attr.y;
	*w = attr.width;
	*h = attr.height;

	if (win != NULL) {
		*win = descend_pointer(5, c, NULL, 0);
	}

	return 1;
#endif	/* NO_X11 */
}

static int scrollcopyrect_top, scrollcopyrect_bot;
static int scrollcopyrect_left, scrollcopyrect_right;
static double scr_key_time, scr_key_persist;
static double scr_mouse_time, scr_mouse_persist, scr_mouse_maxtime;
static double scr_mouse_pointer_delay;
static double scr_key_bdpush_time, scr_mouse_bdpush_time;

static void parse_scroll_copyrect_str(char *scr) {
	char *p, *str;
	int i;
	char *part[10];

	for (i=0; i<10; i++) {
		part[i] = NULL;
	}

	if (scr == NULL || *scr == '\0') {
		return;
	}

	str = strdup(scr);

	p = strtok(str, ",");
	i = 0;
	while (p) {
		part[i++] = strdup(p);
		p = strtok(NULL, ",");
	}
	free(str);


	/*
	 * Top, Bottom, Left, Right tolerances for scrollbar locations.
	 */
	if ((str = part[0]) != NULL) {
		int t, b, l, r;
		if (sscanf(str, "%d+%d+%d+%d", &t, &b, &l, &r) == 4) {
			scrollcopyrect_top   = t;
			scrollcopyrect_bot   = b;
			scrollcopyrect_left  = l;
			scrollcopyrect_right = r;
		}
		free(str);
	}

	/* key scrolling timing heuristics. */
	if ((str = part[1]) != NULL) {
		double t1, t2, t3;
		if (sscanf(str, "%lf+%lf+%lf", &t1, &t2, &t3) == 3) {
			scr_key_time = t1;
			scr_key_persist = t2;
			scr_key_bdpush_time = t3;
		}
		free(str);
	}

	/* mouse scrolling timing heuristics. */
	if ((str = part[2]) != NULL) {
		double t1, t2, t3, t4, t5;
		if (sscanf(str, "%lf+%lf+%lf+%lf+%lf", &t1, &t2, &t3, &t4,
		    &t5) == 5) {
			scr_mouse_time = t1;
			scr_mouse_persist = t2;
			scr_mouse_bdpush_time = t3;
			scr_mouse_pointer_delay = t4;
			scr_mouse_maxtime = t5;
		}
		free(str);
	}
}

void parse_scroll_copyrect(void) {
	parse_scroll_copyrect_str(SCROLL_COPYRECT_PARMS);
	if (! scroll_copyrect_str) {
		scroll_copyrect_str = strdup(SCROLL_COPYRECT_PARMS);
	}
	parse_scroll_copyrect_str(scroll_copyrect_str);
}

void parse_fixscreen(void) {
	char *str, *p;

	screen_fixup_V = 0.0;
	screen_fixup_C = 0.0;
	screen_fixup_X = 0.0;
	screen_fixup_8 = 0.0;

	if (! screen_fixup_str) {
		return;
	}

	str = strdup(screen_fixup_str);

	p = strtok(str, ",");
	while (p) {
		double t;
		if (*p == 'V' && sscanf(p, "V=%lf", &t) == 1) {
			screen_fixup_V = t;
		} else if (*p == 'C' && sscanf(p, "C=%lf", &t) == 1) {
			screen_fixup_C = t;
		} else if (*p == 'X' && sscanf(p, "X=%lf", &t) == 1) {
			screen_fixup_X = t;
		} else if (*p == 'X' && sscanf(p, "8=%lf", &t) == 1) {
			screen_fixup_8 = t;
		}
		p = strtok(NULL, ",");
	}
	free(str);

	if (screen_fixup_V < 0.0) screen_fixup_V = 0.0;
	if (screen_fixup_C < 0.0) screen_fixup_C = 0.0;
	if (screen_fixup_X < 0.0) screen_fixup_X = 0.0;
	if (screen_fixup_8 < 0.0) screen_fixup_8 = 0.0;
}

/*
WIREFRAME_PARMS "0xff,2,0,30+6+6+6,Alt,0.05+0.3+2.0,8"
                 0xff,2,0,32+8+8+8,all,0.15+0.30+5.0+0.125
shade,linewidth,percent,T+B+L+R,mods,t1+t2+t3+t4
 */
#define LW_MAX 8
static unsigned long wireframe_shade = 0xff;
static int wireframe_lw;
static double wireframe_frac;
static int wireframe_top, wireframe_bot, wireframe_left, wireframe_right;
static double wireframe_t1, wireframe_t2, wireframe_t3, wireframe_t4;
static char *wireframe_mods = NULL;

/*
 * Parse the gory -wireframe string for parameters.
 */
static void parse_wireframe_str(char *wf) {
	char *p, *str;
	int i;
	char *part[10];

	for (i=0; i<10; i++) {
		part[i] = NULL;
	}

	if (wf == NULL || *wf == '\0') {
		return;
	}

	str = strdup(wf);

	/* leading ",", make it start with ignorable string "z" */
	if (*str == ',') {
		char *tmp = (char *) malloc(strlen(str)+2);	
		strcpy(tmp, "z");
		strcat(tmp, str);
		free(str);
		str = tmp;
	}

	p = strtok(str, ",");
	i = 0;
	while (p) {
		part[i++] = strdup(p);
		p = strtok(NULL, ",");
	}
	free(str);


	/* Wireframe shade, color, RGB: */
	if ((str = part[0]) != NULL) {
		unsigned long n;
		int r, g, b, ok = 0;
		XColor cdef;
		Colormap cmap;
		if (dpy && (bpp == 32 || bpp == 16)) {
#if !NO_X11
		 	cmap = DefaultColormap (dpy, scr);
			if (XParseColor(dpy, cmap, str, &cdef) &&
			    XAllocColor(dpy, cmap, &cdef)) {
				r = cdef.red   >> 8;
				g = cdef.green >> 8;
				b = cdef.blue  >> 8;
				if (r == 0 && g == 0) {
					g = 1;	/* need to be > 255 */
				}
				n = 0;
				n |= (r << main_red_shift);
				n |= (g << main_green_shift);
				n |= (b << main_blue_shift);
				wireframe_shade = n;
				ok = 1;
			}
#else
			r = g = b = 0;
			cmap = 0;
			cdef.pixel = 0;
#endif
		}
		if (ok) {
			;
		} else if (sscanf(str, "0x%lx", &n) == 1) {
			wireframe_shade = n;	
		} else if (sscanf(str, "%lu", &n) == 1) {
			wireframe_shade = n;	
		} else if (sscanf(str, "%lx", &n) == 1) {
			wireframe_shade = n;	
		}
		free(str);
	}

	/* linewidth: # of pixels wide for the wireframe lines */
	if ((str = part[1]) != NULL) {
		int n;
		if (sscanf(str, "%d", &n) == 1) {
			wireframe_lw = n;	
			if (wireframe_lw < 1) {
				wireframe_lw = 1; 
			}
			if (wireframe_lw > LW_MAX) {
				wireframe_lw = LW_MAX; 
			}
		}
		free(str);
	}

	/* percentage cutoff for opaque move/resize (like WM's) */
	if ((str = part[2]) != NULL) {
		if (*str == '\0') {
			;
		} else if (strchr(str, '.')) {
			wireframe_frac = atof(str);
		} else {
			wireframe_frac = ((double) atoi(str))/100.0;
		}
		free(str);
	}

	/*
	 * Top, Bottom, Left, Right tolerances to guess the wm frame is
	 * being grabbed (Top is traditionally bigger, i.e. titlebar):
	 */
	if ((str = part[3]) != NULL) {
		int t, b, l, r;
		if (sscanf(str, "%d+%d+%d+%d", &t, &b, &l, &r) == 4) {
			wireframe_top   = t;
			wireframe_bot   = b;
			wireframe_left  = l;
			wireframe_right = r;
		}
		free(str);
	}

	/*
	 * wireframe in interior with Modifier down.
	 * 0 => no mods
	 * 1 => all mods
	 * Shift,Alt,Control,Meta,Super,Hyper
	 */
	if (wireframe_mods) {
		free(wireframe_mods);
	}
	wireframe_mods = NULL;
	if ((str = part[4]) != NULL) {
		if (*str == '0' || !strcmp(str, "none")) {
			;
		} else if (*str == '1' || !strcmp(str, "all")) {
			wireframe_mods = strdup("all");	
		} else if (!strcmp(str, "Alt") || !strcmp(str, "Shift")
		    || !strcmp(str, "Control") || !strcmp(str, "Meta")
		    || !strcmp(str, "Super") || !strcmp(str, "Hyper")) {
			wireframe_mods = strdup(str);
		}
	}

	/* check_wireframe() timing heuristics. */
	if ((str = part[5]) != NULL) {
		double t1, t2, t3, t4;
		if (sscanf(str, "%lf+%lf+%lf+%lf", &t1, &t2, &t3, &t4) == 4) {
			wireframe_t1 = t1;
			wireframe_t2 = t2;
			wireframe_t3 = t3;
			wireframe_t4 = t4;
		}
		free(str);
	}
}

/*
 * First parse the defaults and apply any user supplied ones (may be a subset)
 */
void parse_wireframe(void) {
	parse_wireframe_str(WIREFRAME_PARMS);
	if (! wireframe_str) {
		wireframe_str = strdup(WIREFRAME_PARMS);
	}
	parse_wireframe_str(wireframe_str);
}

/*
 * Set wireframe_copyrect based on desired mode.
 */
void set_wirecopyrect_mode(char *str) {
	char *orig = wireframe_copyrect;
	if (str == NULL || *str == '\0') {
		wireframe_copyrect = strdup(wireframe_copyrect_default);
	} else if (!strcmp(str, "always") || !strcmp(str, "all")) {
		wireframe_copyrect = strdup("always");
	} else if (!strcmp(str, "top")) {
		wireframe_copyrect = strdup("top");
	} else if (!strcmp(str, "never") || !strcmp(str, "none")) {
		wireframe_copyrect = strdup("never");
	} else {
		if (! wireframe_copyrect) {
			wireframe_copyrect = strdup(wireframe_copyrect_default);
		} else {
			orig = NULL;
		}
		rfbLog("unknown -wirecopyrect mode: %s, using: %s\n", str,
		    wireframe_copyrect);
	}
	if (orig) {
		free(orig);
	}
}

/*
 * Set scroll_copyrect based on desired mode.
 */
void set_scrollcopyrect_mode(char *str) {
	char *orig = scroll_copyrect;
	if (str == NULL || *str == '\0') {
		scroll_copyrect = strdup(scroll_copyrect_default);
	} else if (!strcmp(str, "always") || !strcmp(str, "all") ||
		    !strcmp(str, "both")) {
		scroll_copyrect = strdup("always");
	} else if (!strcmp(str, "keys") || !strcmp(str, "keyboard")) {
		scroll_copyrect = strdup("keys");
	} else if (!strcmp(str, "mouse") || !strcmp(str, "pointer")) {
		scroll_copyrect = strdup("mouse");
	} else if (!strcmp(str, "never") || !strcmp(str, "none")) {
		scroll_copyrect = strdup("never");
	} else {
		if (! scroll_copyrect) {
			scroll_copyrect = strdup(scroll_copyrect_default);
		} else {
			orig = NULL;
		}
		rfbLog("unknown -scrollcopyrect mode: %s, using: %s\n", str,
		    scroll_copyrect);
	}
	if (orig) {
		free(orig);
	}
}

void initialize_scroll_keys(void) {
	char *str, *p;
	int i, nkeys = 0, saw_builtin = 0;
	int ks_max = 2 * 0xFFFF;

	if (scroll_key_list) {
		free(scroll_key_list);
		scroll_key_list = NULL;
	}
	if (! scroll_key_list_str || *scroll_key_list_str == '\0') {
		return;
	}

	if (strstr(scroll_key_list_str, "builtin")) {
		int k;
		/* add in number of keysyms builtin gives */
		for (k=1; k<ks_max; k++)  {
			if (xrecord_scroll_keysym((rfbKeySym) k)) {
				nkeys++;
			}
		}
	}

	nkeys++;	/* first key, i.e. no commas. */
	p = str = strdup(scroll_key_list_str);
	while(*p) {
		if (*p == ',') {
			nkeys++;	/* additional key. */
		}
		p++;
	}
	
	nkeys++;	/* exclude/include 0 element */
	nkeys++;	/* trailing NoSymbol */

	scroll_key_list = (KeySym *) malloc(nkeys*sizeof(KeySym)); 
	for (i=0; i<nkeys; i++) {
		scroll_key_list[i] = NoSymbol;
	}
	if (*str == '-') {
		scroll_key_list[0] = 1;
		p = strtok(str+1, ",");
	} else {
		p = strtok(str, ",");
	}
	i = 1;
	while (p) {
		if (!strcmp(p, "builtin")) {
			int k;
			if (saw_builtin) {
				p = strtok(NULL, ",");
				continue;
			}
			saw_builtin = 1;
			for (k=1; k<ks_max; k++)  {
				if (xrecord_scroll_keysym((rfbKeySym) k)) {
					scroll_key_list[i++] = (rfbKeySym) k;
				}
			}
		} else {
			unsigned int in;
			if (sscanf(p, "%u", &in) == 1) {
				scroll_key_list[i++] = (rfbKeySym) in;
			} else if (sscanf(p, "0x%x", &in) == 1) {
				scroll_key_list[i++] = (rfbKeySym) in;
			} else if (XStringToKeysym(p) != NoSymbol) { 
				scroll_key_list[i++] = XStringToKeysym(p);
			} else {
				rfbLog("initialize_scroll_keys: skip unknown "
				    "keysym: %s\n", p);
			}
		}
		p = strtok(NULL, ",");
	}
	free(str);
}

static void destroy_str_list(char **list) {
	int i = 0;
	if (! list) {
		return;
	}
	while (list[i] != NULL) {
		free(list[i++]);
	}
	free(list);
}

void initialize_scroll_matches(void) {
	char *str, *imp = "__IMPOSSIBLE_STR__";
	int i, n, nkey, nmouse;

	destroy_str_list(scroll_good_all);
	scroll_good_all = NULL;
	destroy_str_list(scroll_good_key);
	scroll_good_key = NULL;
	destroy_str_list(scroll_good_mouse);
	scroll_good_mouse = NULL;

	destroy_str_list(scroll_skip_all);
	scroll_skip_all = NULL;
	destroy_str_list(scroll_skip_key);
	scroll_skip_key = NULL;
	destroy_str_list(scroll_skip_mouse);
	scroll_skip_mouse = NULL;

	/* scroll_good: */
	if (scroll_good_str != NULL && *scroll_good_str != '\0') {
		str = scroll_good_str;
	} else {
		str = scroll_good_str0;
	}
	scroll_good_all = create_str_list(str);

	nkey = 0;
	nmouse = 0;
	n = 0;
	while (scroll_good_all[n] != NULL) {
		char *s = scroll_good_all[n++];
		if (strstr(s, "KEY:") == s) nkey++;
		if (strstr(s, "MOUSE:") == s) nmouse++;
	}
	if (nkey++) {
		scroll_good_key = (char **) malloc(nkey*sizeof(char *));
		for (i=0; i<nkey; i++) scroll_good_key[i] = NULL;
	}
	if (nmouse++) {
		scroll_good_mouse = (char **) malloc(nmouse*sizeof(char *));
		for (i=0; i<nmouse; i++) scroll_good_mouse[i] = NULL;
	}
	nkey = 0;
	nmouse = 0;
	for (i=0; i<n; i++) {
		char *s = scroll_good_all[i];
		if (strstr(s, "KEY:") == s) {
			scroll_good_key[nkey++] = strdup(s+strlen("KEY:"));
			free(s);
			scroll_good_all[i] = strdup(imp);
		} else if (strstr(s, "MOUSE:") == s) {
			scroll_good_mouse[nmouse++]=strdup(s+strlen("MOUSE:"));
			free(s);
			scroll_good_all[i] = strdup(imp);
		}
	}

	/* scroll_skip: */
	if (scroll_skip_str != NULL && *scroll_skip_str != '\0') {
		str = scroll_skip_str;
	} else {
		str = scroll_skip_str0;
	}
	scroll_skip_all = create_str_list(str);

	nkey = 0;
	nmouse = 0;
	n = 0;
	while (scroll_skip_all[n] != NULL) {
		char *s = scroll_skip_all[n++];
		if (strstr(s, "KEY:") == s) nkey++;
		if (strstr(s, "MOUSE:") == s) nmouse++;
	}
	if (nkey++) {
		scroll_skip_key = (char **) malloc(nkey*sizeof(char *));
		for (i=0; i<nkey; i++) scroll_skip_key[i] = NULL;
	}
	if (nmouse++) {
		scroll_skip_mouse = (char **) malloc(nmouse*sizeof(char *));
		for (i=0; i<nmouse; i++) scroll_skip_mouse[i] = NULL;
	}
	nkey = 0;
	nmouse = 0;
	for (i=0; i<n; i++) {
		char *s = scroll_skip_all[i];
		if (strstr(s, "KEY:") == s) {
			scroll_skip_key[nkey++] = strdup(s+strlen("KEY:"));
			free(s);
			scroll_skip_all[i] = strdup(imp);
		} else if (strstr(s, "MOUSE:") == s) {
			scroll_skip_mouse[nmouse++]=strdup(s+strlen("MOUSE:"));
			free(s);
			scroll_skip_all[i] = strdup(imp);
		}
	}
}

void initialize_scroll_term(void) {
	char *str;
	int n;

	destroy_str_list(scroll_term);
	scroll_term = NULL;

	if (scroll_term_str != NULL && *scroll_term_str != '\0') {
		str = scroll_term_str;
	} else {
		str = scroll_term_str0;
	}
	if (!strcmp(str, "none")) {
		return;
	}
	scroll_term = create_str_list(str);

	n = 0;
	while (scroll_term[n] != NULL) {
		char *s = scroll_good_all[n++];
		/* pull parameters out at some point */
		s = NULL;
	}
}

void initialize_max_keyrepeat(void) {
	char *str;
	int lo, hi;

	if (max_keyrepeat_str != NULL && *max_keyrepeat_str != '\0') {
		str = max_keyrepeat_str;
	} else {
		str = max_keyrepeat_str0;
	}

	if (sscanf(str, "%d-%d", &lo, &hi) != 2) {
		rfbLog("skipping invalid -scr_keyrepeat string: %s\n", str);
		sscanf(max_keyrepeat_str0, "%d-%d", &lo, &hi);
	}
	max_keyrepeat_lo = lo;
	max_keyrepeat_hi = hi;
	if (max_keyrepeat_lo < 1) {
		max_keyrepeat_lo = 1;
	}
	if (max_keyrepeat_hi > 40) {
		max_keyrepeat_hi = 40;
	}
}

typedef struct saveline {
	int x0, y0, x1, y1;
	int shift;
	int vert;
	int saved;
	char *data;
} saveline_t;

/*
 * Draw the wireframe box onto the framebuffer.  Saves the real
 * framebuffer data to some storage lines.  Restores previous lines.
 * use restore = 1 to clean up (done with animation).
 * This works with -scale.
 */
static void draw_box(int x, int y, int w, int h, int restore) {
	int x0, y0, x1, y1, i, pixelsize = bpp/8;
	char *dst, *src, *use_fb;
	static saveline_t *save[4];
	static int first = 1, len = 0;
	int max = dpy_x > dpy_y ? dpy_x : dpy_y;
	int use_Bpl, lw = wireframe_lw;
	unsigned long shade = wireframe_shade;
	int color = 0;
	unsigned short us = 0;
	unsigned long ul = 0;

	if (clipshift) {
		x -= coff_x;
		y -= coff_y;
	}

	/* handle -8to24 mode: use 2nd fb only */
	use_fb  = main_fb;
	use_Bpl = main_bytes_per_line; 
	
	if (cmap8to24 && cmap8to24_fb) {
		use_fb = cmap8to24_fb;
		pixelsize = 4;
		if (depth == 8) {
			use_Bpl *= 4;
		}
	}

	if (max > len) {
		/* create/resize storage lines: */
		for (i=0; i<4; i++) {
			len = max;
			if (! first && save[i]) {
				if (save[i]->data) {
					free(save[i]->data);
					save[i]->data = NULL;
				}
				free(save[i]);
			}
			save[i] = (saveline_t *) malloc(sizeof(saveline_t));
			save[i]->saved = 0;
			save[i]->data = (char *) malloc( (LW_MAX+1)*len*4 );

			/* 
			 * Four types of lines:
			 *	0) top horizontal
			 *	1) bottom horizontal
			 *	2) left vertical
			 *	3) right vertical
			 *
			 * shift means shifted by width or height.
			 */
			if (i == 0) {
				save[i]->vert  = 0;
				save[i]->shift = 0;
			} else if (i == 1) {
				save[i]->vert  = 0;
				save[i]->shift = 1;
			} else if (i == 2) {
				save[i]->vert  = 1;
				save[i]->shift = 0;
			} else if (i == 3) {
				save[i]->vert  = 1;
				save[i]->shift = 1;
			}
		}
	}
	first = 0;

	/*
	 * restore any saved lines. see below for algorithm and
	 * how x0, etc. are used.  we just reverse those steps.
	 */
	for (i=0; i<4; i++) {
		int s = save[i]->shift;
		int yu, y_min = -1, y_max = -1;
		int y_start, y_stop, y_step;

		if (! save[i]->saved) {
			continue;
		}
		x0 = save[i]->x0;
		y0 = save[i]->y0;
		x1 = save[i]->x1;
		y1 = save[i]->y1;
		if (save[i]->vert) {
			y_start = y0+lw;
			y_stop  = y1-lw;
			y_step  = lw*pixelsize;
		} else {
			y_start = y0 - s*lw;
			y_stop  = y_start + lw;
			y_step  = max*pixelsize;
		}
		for (yu = y_start; yu < y_stop; yu++) {
			if (x0 == x1) {
				continue;
			}
			if (yu < 0 || yu >= dpy_y) {
				continue;
			}
			if (y_min < 0 || yu < y_min) {
				y_min = yu;
			}
			if (y_max < 0 || yu > y_max) {
				y_max = yu;
			}
			src = save[i]->data + (yu-y_start)*y_step;
			dst = use_fb + yu*use_Bpl + x0*pixelsize;
			memcpy(dst, src, (x1-x0)*pixelsize);
		}
		if (y_min >= 0) {
			mark_rect_as_modified(x0, y_min, x1, y_max+1, 0);
		}
		save[i]->saved = 0;
	}

if (0) fprintf(stderr, "  DrawBox: %04dx%04d+%04d+%04d B=%d rest=%d lw=%d %.4f\n", w, h, x, y, 2*(w+h)*(2-restore)*pixelsize*lw, restore, lw, dnowx());

	if (restore) {
		return;
	}


	/*
	 * work out shade/color for the wireframe line, could be a color
	 * for 16bpp or 24bpp.
	 */
	if (shade > 255) {
		if (pixelsize == 2) {
			us = (unsigned short) (shade & 0xffff);
			color = 1;
		} else if (pixelsize == 4) {
			ul = (unsigned long) shade;
			color = 1;
		} else {
			shade = shade % 256;
		}
	}

	for (i=0; i<4; i++)  {
		int s = save[i]->shift;
		int yu, y_min = -1, y_max = -1;
		int yblack = -1, xblack1 = -1, xblack2 = -1;
		int y_start, y_stop, y_step;

		if (save[i]->vert) {
			/*
			 * make the narrow x's be on the screen, let
			 * the y's hang off (not drawn).
			 */
			save[i]->x0 = x0 = nfix(x + s*w - s*lw, dpy_x);
			save[i]->y0 = y0 = y;
			save[i]->x1 = x1 = nfix(x + s*w - s*lw + lw, dpy_x);
			save[i]->y1 = y1 = y + h;

			/*
			 * start and stop a linewidth away from true edge,
			 * to avoid interfering with horizontal lines.
			 */
			y_start = y0+lw;
			y_stop  = y1-lw;
			y_step  = lw*pixelsize;

			/* draw a black pixel for the border if lw > 1 */
			if (s) {
				xblack1 = x1-1;
			} else {
				xblack1 = x0;
			}
		} else {
			/*
			 * make the wide x's be on the screen, let the y's
			 * hang off (not drawn).
			 */
			save[i]->x0 = x0 = nfix(x,     dpy_x);
			save[i]->y0 = y0 = y + s*h;
			save[i]->x1 = x1 = nfix(x + w, dpy_x);
			save[i]->y1 = y1 = y0 + lw;
			y_start = y0 - s*lw;
			y_stop  = y_start + lw;
			y_step  = max*pixelsize;

			/* draw a black pixels for the border if lw > 1 */
			if (s) {
				yblack = y_stop - 1;
			} else {
				yblack = y_start;
			}
			xblack1 = x0;
			xblack2 = x1-1;
		}

		/* now loop over the allowed y's for either case */
		for (yu = y_start; yu < y_stop; yu++) {
			if (x0 == x1) {
				continue;
			}
			if (yu < 0 || yu >= dpy_y) {
				continue;
			}

			/* record min and max y's for marking rectangle: */
			if (y_min < 0 || yu < y_min) {
				y_min = yu;
			}
			if (y_max < 0 || yu > y_max) {
				y_max = yu;
			}

			/* save fb data for this line: */
			save[i]->saved = 1;
			src = use_fb + yu*use_Bpl + x0*pixelsize;
			dst = save[i]->data + (yu-y_start)*y_step;
			memcpy(dst, src, (x1-x0)*pixelsize);

			/* apply the shade/color to make the wireframe line: */
			if (! color) {
				memset(src, shade, (x1-x0)*pixelsize);
			} else {
				char *csrc = src;
				unsigned short *usp;
				unsigned long *ulp;
				int k;
				for (k=0; k < x1 - x0; k++) {
					if (pixelsize == 4) {
						ulp = (unsigned long *)csrc;
						*ulp = ul;
					} else if (pixelsize == 2) {
						usp = (unsigned short *)csrc;
						*usp = us;
					}
					csrc += pixelsize;
				}
			}

			/* apply black border for lw >= 2 */
			if (lw > 1) {
				if (yu == yblack) {
					memset(src, 0, (x1-x0)*pixelsize);
				}
				if (xblack1 >= 0) {
					src = src + (xblack1 - x0)*pixelsize;
					memset(src, 0, pixelsize);
				}
				if (xblack2 >= 0) {
					src = src + (xblack2 - x0)*pixelsize;
					memset(src, 0, pixelsize);
				}
			}
		}
		/* mark it for sending: */
		if (save[i]->saved) {
			mark_rect_as_modified(x0, y_min, x1, y_max+1, 0);
		}
	}
}

int direct_fb_copy(int x1, int y1, int x2, int y2, int mark) {
	char *src, *dst;
	int y, pixelsize = bpp/8;
	int xmin = -1, xmax = -1, ymin = -1, ymax = -1;
	int do_cmp = 2;
	double tm;
	int db = 0;

if (db) dtime0(&tm);

	x1 = nfix(x1, dpy_x);
	y1 = nfix(y1, dpy_y);
	x2 = nfix(x2, dpy_x+1);
	y2 = nfix(y2, dpy_y+1);

	if (x1 == x2) {
		return 1;
	}
	if (y1 == y2) {
		return 1;
	}

	X_LOCK;
	for (y = y1; y < y2; y++) {
		XRANDR_SET_TRAP_RET(0, "direct_fb_copy-set");
		copy_image(scanline, x1, y, x2 - x1, 1);
		XRANDR_CHK_TRAP_RET(0, "direct_fb_copy-chk");
		
		src = scanline->data;
		dst = main_fb + y * main_bytes_per_line + x1 * pixelsize;

		if (do_cmp == 0 || !mark) {
			memcpy(dst, src, (x2 - x1)*pixelsize);

		} else if (do_cmp == 1) {
			if (memcmp(dst, src, (x2 - x1)*pixelsize)) {
				if (ymin == -1 || y < ymin) {
					ymin = y;
				}
				if (ymax == -1 || y > ymax) {
					ymax = y;
				}
				memcpy(dst, src, (x2 - x1)*pixelsize);
			}

		} else if (do_cmp == 2) {
			int n, shift, xlo, xhi, k, block = 32;
			char *dst2, *src2;

			for (k=0; k*block < (x2 - x1); k++) {
				shift = k*block;
				xlo = x1  + shift;
				xhi = xlo + block;
				if (xhi > x2) {
					xhi = x2;
				}
				n = xhi - xlo;
				if (n < 1) {
					continue;
				}
				src2 = src + shift*pixelsize;
				dst2 = dst + shift*pixelsize;
				if (memcmp(dst2, src2, n*pixelsize)) {
					if (ymin == -1 || y < ymin) {
						ymin = y;
					}
					if (ymax == -1 || y > ymax) {
						ymax = y;
					}
					if (xmin == -1 || xlo < xmin) {
						xmin = xlo;
					}
					if (xmax == -1 || xhi > xmax) {
						xmax = xhi;
					}
					memcpy(dst2, src2, n*pixelsize);
				}
			}
		}
	}
	X_UNLOCK;

	if (do_cmp == 0) {
		xmin = x1;
		ymin = y1;
		xmax = x2;
		ymax = y2;
	} else if (do_cmp == 1) {
		xmin = x1;
		xmax = x2;
	}

	if (xmin < 0 || ymin < 0 || xmax < 0 || xmin < 0) {
		/* no diffs */
		return 1;
	}

	if (xmax < x2) {
		xmax++;
	}
	if (ymax < y2) {
		ymax++;
	}

	if (mark) {
		mark_rect_as_modified(xmin, ymin, xmax, ymax, 0);
	}

 if (db) {
	fprintf(stderr, "direct_fb_copy: %dx%d+%d+%d - %d  %.4f\n",
		x2 - x1, y2 - y1, x1, y1, mark, dtime(&tm));
 }

	return 1;
}

static int do_bdpush(Window wm_win, int x0, int y0, int w0, int h0, int bdx,
    int bdy, int bdskinny) {

	XWindowAttributes attr;
	sraRectangleIterator *iter;
	sraRect rect;
	sraRegionPtr frame, whole, tmpregion;
	int tx1, ty1, tx2, ty2;
	static Window last_wm_win = None;
	static int last_x, last_y, last_w, last_h;
	int do_fb_push = 0;
	int db = debug_scroll;

	if (wm_win == last_wm_win) {
		attr.x = last_x;
		attr.y = last_y;
		attr.width = last_w;
		attr.height = last_h;
	} else {
		if (!valid_window(wm_win, &attr, 1)) {
			return do_fb_push;
		}
		last_wm_win = wm_win;
		last_x = attr.x;
		last_y = attr.y;
		last_w = attr.width;
		last_h = attr.height;
	}
if (db > 1) fprintf(stderr, "BDP  %d %d %d %d  %d %d %d  %d %d %d %d\n",
	x0, y0, w0, h0, bdx, bdy, bdskinny, last_x, last_y, last_w, last_h);

	/* wm frame: */
	tx1 = attr.x;
	ty1 = attr.y;
	tx2 = attr.x + attr.width;
	ty2 = attr.y + attr.height;

	whole = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
	if (clipshift) {
		sraRgnOffset(whole, coff_x, coff_y);
	}
	if (subwin) {
		sraRgnOffset(whole, off_x, off_y);
	}
	frame = sraRgnCreateRect(tx1, ty1, tx2, ty2);
	sraRgnAnd(frame, whole);

	/* scrolling window: */
	tmpregion = sraRgnCreateRect(x0, y0, x0 + w0, y0 + h0);
	sraRgnAnd(tmpregion, whole);

	sraRgnSubtract(frame, tmpregion);
	sraRgnDestroy(tmpregion);

	if (!sraRgnEmpty(frame)) {
		double dt = 0.0, dm;
		dtime0(&dm);
		iter = sraRgnGetIterator(frame);
		while (sraRgnIteratorNext(iter, &rect)) {
			tx1 = rect.x1;
			ty1 = rect.y1;
			tx2 = rect.x2;
			ty2 = rect.y2;

			if (bdskinny > 0) {
				int ok = 0;
				if (nabs(ty2-ty1) <= bdskinny) {
					ok = 1;
				}
				if (nabs(tx2-tx1) <= bdskinny) {
					ok = 1;
				}
				if (! ok) {
					continue;
				}
			}

			if (bdx >= 0) {
				if (bdx < tx1 || tx2 <= bdx) {
					continue;
				}
			}
			if (bdy >= 0) {
				if (bdy < ty1 || ty2 <= bdy) {
					continue;
				}
			}
			if (clipshift) {
				tx1 -= coff_x;
				ty1 -= coff_y;
				tx2 -= coff_x;
				ty2 -= coff_y;
			}
			if (subwin) {
				tx1 -= off_x;
				ty1 -= off_y;
				tx2 -= off_x;
				ty2 -= off_y;
			}

			direct_fb_copy(tx1, ty1, tx2, ty2, 1);

			do_fb_push++;
			dt += dtime(&dm);
if (db > 1) fprintf(stderr, "  BDP(%d,%d-%d,%d)  dt: %.4f\n", tx1, ty1, tx2, ty2, dt);
		}
		sraRgnReleaseIterator(iter);
	}
	sraRgnDestroy(whole);
	sraRgnDestroy(frame);

	return do_fb_push;
}

static int set_ypad(void) {
	int ev, ev_tot = scr_ev_cnt;
	static Window last_win = None;
	static double last_time = 0.0;
	static int y_accum = 0, last_sign = 0;
	double now, cut = 0.1;
	int dy_sum = 0, ys = 0, sign;
	int font_size = 15;
	int win_y, scr_y, loc_cut = 4*font_size, y_cut = 10*font_size;
	
	if (!xrecord_set_by_keys || !xrecord_name_info) {
		return 0;
	}
	if (xrecord_name_info[0] == '\0') {
		return 0;
	}
	if (! ev_tot) {
		return 0;
	}
	if (xrecord_keysym == NoSymbol)  {
		return 0;
	}
	if (!xrecord_scroll_keysym(xrecord_keysym)) {
		return 0;
	}
	if (!scroll_term) {
		return 0;
	}
	if (!match_str_list(xrecord_name_info, scroll_term)) {
		return 0;
	}

	for (ev=0; ev < ev_tot; ev++) {
		dy_sum += nabs(scr_ev[ev].dy);
		if (scr_ev[ev].dy < 0) {
			ys--;
		} else if (scr_ev[ev].dy > 0) {
			ys++;
		} else {
			ys = 0;
			break;
		}
		if (scr_ev[ev].win != scr_ev[0].win) {
			ys = 0;
			break;
		}
		if (scr_ev[ev].dx != 0) {
			ys = 0;
			break;
		}
	}
	if (ys != ev_tot && ys != -ev_tot) {
		return 0;
	}
	if (ys < 0) {
		sign = -1;
	} else {
		sign = 1;
	}

	if (sign > 0) {
		/*
		 * this case is not as useful as scrolling near the
		 * bottom of a terminal.  But there are problems for it too.
		 */
		return 0;
	}

	win_y = scr_ev[0].win_y + scr_ev[0].win_h;
	scr_y = scr_ev[0].y + scr_ev[0].h;
	if (nabs(scr_y - win_y) > loc_cut) {
		/* require it to be near the bottom. */
		return 0;
	}

	now = dnow();

	if (now < last_time + cut) {
		int ok = 1;
		if (last_win && scr_ev[0].win != last_win) {
			ok = 0;
		}
		if (last_sign && sign != last_sign) {
			ok = 0;
		}
		if (! ok) {
			last_win = None;
			last_sign = 0;
			y_accum = 0;
			last_time = 0.0;
			return 0;
		}
	} else {
		last_win = None;
		last_sign = 0;
		last_time = 0.0;
		y_accum = 0;
	}

	y_accum += sign * dy_sum;

	if (4 * nabs(y_accum) > scr_ev[0].h && y_cut) {
		;	/* TBD */
	}

	last_sign = sign;
	last_win = scr_ev[0].win;
	last_time = now;

	return y_accum;
}

static void scale_mark(int x1, int y1, int x2, int y2, int mark) {
	int s = 2;
	x1 = nfix(x1 - s, dpy_x);
	y1 = nfix(y1 - s, dpy_y);
	x2 = nfix(x2 + s, dpy_x+1);
	y2 = nfix(y2 + s, dpy_y+1);
	scale_and_mark_rect(x1, y1, x2, y2, mark);
}

#define PUSH_TEST(n)  \
if (n) { \
	double dt = 0.0, tm; dtime0(&tm); \
	fprintf(stderr, "PUSH---\n"); \
	while (dt < 2.0) { rfbPE(50000); dt += dtime(&tm); } \
	fprintf(stderr, "---PUSH\n"); \
}

int batch_dxs[], batch_dys[];
sraRegionPtr batch_reg[];
void batch_push(int ncr, double delay);

static int push_scr_ev(double *age, int type, int bdpush, int bdx, int bdy,
    int bdskinny, int first_push) {
	Window frame, win, win0;
	int x, y, w, h, wx, wy, ww, wh, dx, dy;
	int x0, y0, w0, h0;
	int nx, ny, nw, nh;
	int dret = 1, do_fb_push = 0, obscured;
	int ev, ev_tot = scr_ev_cnt;
	double tm, dt, st, waittime = 0.125;
	double max_age = *age;
	int db = debug_scroll, rrate = get_read_rate();
	sraRegionPtr backfill, whole, tmpregion, tmpregion2;
	int link, latency, netrate;
	int ypad = 0;
	double last_scroll_event_save = last_scroll_event;
	int fast_push = 0, rc;

	/* we return the oldest one. */
	*age = 0.0;

	if (ev_tot == 0) {
		return dret;
	}

	link = link_rate(&latency, &netrate);

	if (link == LR_DIALUP) {
		waittime *= 5;
	} else if (link == LR_BROADBAND) {
		waittime *= 3;
	} else if (latency > 80 || netrate < 40) {
		waittime *= 3;
	}

	backfill = sraRgnCreate();
	whole = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
	if (clipshift) {
		sraRgnOffset(whole, coff_x, coff_y);
	}
	if (subwin) {
		sraRgnOffset(whole, off_x, off_y);
	}

	win0 = scr_ev[0].win;
	x0 = scr_ev[0].win_x;
	y0 = scr_ev[0].win_y;
	w0 = scr_ev[0].win_w;
	h0 = scr_ev[0].win_h;

	ypad = set_ypad();

if (db) fprintf(stderr, "ypad: %d  dy[0]: %d ev_tot: %d\n", ypad, scr_ev[0].dy, ev_tot);

	for (ev=0; ev < ev_tot; ev++) {
		double ag;
	
		x   = scr_ev[ev].x;
		y   = scr_ev[ev].y;
		w   = scr_ev[ev].w;
		h   = scr_ev[ev].h;
		dx  = scr_ev[ev].dx;
		dy  = scr_ev[ev].dy;
		win = scr_ev[ev].win;
		wx  = scr_ev[ev].win_x;
		wy  = scr_ev[ev].win_y;
		ww  = scr_ev[ev].win_w;
		wh  = scr_ev[ev].win_h;
		nx  = scr_ev[ev].new_x;
		ny  = scr_ev[ev].new_y;
		nw  = scr_ev[ev].new_w;
		nh  = scr_ev[ev].new_h;
		st  = scr_ev[ev].t;

		ag = (dnow() - servertime_diff) - st;
		if (ag > *age) {
			*age = ag;
		}

		if (dabs(ag) > max_age) {
if (db) fprintf(stderr, "push_scr_ev: TOO OLD: %.4f :: (%.4f - %.4f) "
    "- %.4f \n", ag, dnow(), servertime_diff, st);				
			dret = 0;
			break;
		} else {
if (db) fprintf(stderr, "push_scr_ev: AGE:     %.4f\n", ag);
		}
		if (win != win0) {
if (db) fprintf(stderr, "push_scr_ev: DIFF WIN: 0x%lx != 0x%lx\n", win, win0);
			dret = 0;
			break;
		}
		if (wx != x0 || wy != y0) {
if (db) fprintf(stderr, "push_scr_ev: WIN SHIFT: %d %d, %d %d", wx, x0, wy, y0);
			dret = 0;
			break;
		}
		if (ww != w0 || wh != h0) {
if (db) fprintf(stderr, "push_scr_ev: WIN RESIZE: %d %d, %d %d", ww, w0, wh, h0);
			dret = 0;
			break;
		}
		if (w < 1 || h < 1 || ww < 1 || wh < 1) {
if (db) fprintf(stderr, "push_scr_ev: NEGATIVE h/w: %d %d %d %d\n", w, h, ww, wh);
			dret = 0;
			break;
		}

if (db > 1) fprintf(stderr, "push_scr_ev: got: %d x: %4d y: %3d"
    " w: %4d h: %3d  dx: %d dy: %d %dx%d+%d+%d   win: 0x%lx\n",
    ev, x, y, w, h, dx, dy, w, h, x, y, win);

if (db > 1) fprintf(stderr, "------------ got: %d x: %4d y: %3d"
    " w: %4d h: %3d %dx%d+%d+%d\n",
    ev, wx, wy, ww, wh, ww, wh, wx, wy);

if (db > 1) fprintf(stderr, "------------ got: %d x: %4d y: %3d"
    " w: %4d h: %3d %dx%d+%d+%d\n",
    ev, nx, ny, nw, nh, nw, nh, nx, ny);

		frame = None;
		if (xrecord_wm_window) {
			frame = xrecord_wm_window;
		}
		if (! frame) {
			X_LOCK;
			frame = query_pointer(rootwin);
			X_UNLOCK;
		}
		if (! frame) {
			frame = win;
		}

		dtime0(&tm);

		tmpregion = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
		if (clipshift) {
			sraRgnOffset(tmpregion, coff_x, coff_y);
		}
		if (subwin) {
			sraRgnOffset(tmpregion, off_x, off_y);
		}
		tmpregion2 = sraRgnCreateRect(wx, wy, wx+ww, wy+wh);
		sraRgnAnd(tmpregion2, whole);
		sraRgnSubtract(tmpregion, tmpregion2);
		sraRgnDestroy(tmpregion2);

		/* do the wm frame just incase the above is bogus too. */
		if (frame && frame != win) {
			int k, gotk = -1;
			for (k = stack_list_num - 1; k >= 0; k--) {
				if (stack_list[k].win == frame &&
				    stack_list[k].fetched && 
				    stack_list[k].valid && 
				    stack_list[k].map_state == IsViewable) {
					gotk = k;
					break;
				}
			}
			if (gotk != -1) {
				int tx1, ty1, tx2, ty2;
				tx1 = stack_list[gotk].x;
				ty1 = stack_list[gotk].y;
				tx2 = tx1 + stack_list[gotk].width;
				ty2 = ty1 + stack_list[gotk].height;
				tmpregion2 = sraRgnCreateRect(tx1,ty1,tx2,ty2);
				sraRgnAnd(tmpregion2, whole);
				sraRgnSubtract(tmpregion, tmpregion2);
				sraRgnDestroy(tmpregion2);
			}
		}

		/*
		 * XXX Need to also clip:
		 *	children of win
		 *	siblings of win higher in stacking order.
		 * ignore for now... probably will make some apps
		 * act very strangely.
		 */
		if (ypad) {
			if (ypad < 0) {
				if (h > -ypad) {
					h += ypad;
				} else {
					ypad = 0;
				}
			} else {
				if (h > ypad) {
					y += ypad;
				} else {
					ypad = 0;
				}
			}
		}

		if (fast_push) {
			int nbatch = 0; 
			double delay, d1 = 0.1, d2 = 0.02;
			rc = try_copyrect(frame, frame, x, y, w, h, dx, dy, &obscured,
			    tmpregion, waittime, &nbatch);

			if (first_push) {
				delay = d1;
			} else {
				delay = d2;
			}

			batch_push(nbatch, delay);
			fb_push();
		} else {
			rc = try_copyrect(frame, frame, x, y, w, h, dx, dy, &obscured,
			    tmpregion, waittime, NULL);
			if (rc) {
				last_scroll_type = type;
				dtime0(&last_scroll_event);

				do_fb_push++;
				urgent_update = 1;
				sraRgnDestroy(tmpregion);
PUSH_TEST(0);
			}
		}

		if (! rc) {
			dret = 0;
			sraRgnDestroy(tmpregion);
			break;	
		}
		dt = dtime(&tm);
if (0) fprintf(stderr, "  try_copyrect dt: %.4f\n", dt);

		if (ev > 0) {
			sraRgnOffset(backfill, dx, dy);
			sraRgnAnd(backfill, whole);
		}

		if (ypad) {
			if (ypad < 0) {
				ny += ypad;	
				nh -= ypad;
			} else {
				;
			}
		}

		tmpregion = sraRgnCreateRect(nx, ny, nx + nw, ny + nh);
		sraRgnAnd(tmpregion, whole);
		sraRgnOr(backfill, tmpregion);
		sraRgnDestroy(tmpregion);
	}

	/* try to update the backfill region (new window contents) */
	if (dret != 0) {
		double est, win_area = 0.0, area = 0.0;
		sraRectangleIterator *iter;
		sraRect rect;
		int tx1, ty1, tx2, ty2;

		tmpregion = sraRgnCreateRect(x0, y0, x0 + w0, y0 + h0);
		sraRgnAnd(tmpregion, whole);

		sraRgnAnd(backfill, tmpregion);

		iter = sraRgnGetIterator(tmpregion);
		while (sraRgnIteratorNext(iter, &rect)) {
			tx1 = rect.x1;
			ty1 = rect.y1;
			tx2 = rect.x2;
			ty2 = rect.y2;

			win_area += (tx2 - tx1)*(ty2 - ty1);
		}
		sraRgnReleaseIterator(iter);

		sraRgnDestroy(tmpregion);


		iter = sraRgnGetIterator(backfill);
		while (sraRgnIteratorNext(iter, &rect)) {
			tx1 = rect.x1;
			ty1 = rect.y1;
			tx2 = rect.x2;
			ty2 = rect.y2;

			area += (tx2 - tx1)*(ty2 - ty1);
		}
		sraRgnReleaseIterator(iter);

		est = (area * (bpp/8)) / (1000000.0 * rrate);
if (db) fprintf(stderr, "  area %.1f win_area %.1f est: %.4f", area, win_area, est);
		if (area > 0.90 * win_area) {
if (db) fprintf(stderr, "  AREA_TOO_MUCH");
			dret = 0;
		} else if (est > 0.6) {
if (db) fprintf(stderr, "  EST_TOO_LARGE");
			dret = 0;
		} else if (area <= 0.0) {
			;
		} else {
			dtime0(&tm);
			iter = sraRgnGetIterator(backfill);
			while (sraRgnIteratorNext(iter, &rect)) {
				tx1 = rect.x1;
				ty1 = rect.y1;
				tx2 = rect.x2;
				ty2 = rect.y2;

				if (clipshift) {
					tx1 -= coff_x;
					ty1 -= coff_y;
					tx2 -= coff_x;
					ty2 -= coff_y;
				}
				if (subwin) {
					tx1 -= off_x;
					ty1 -= off_y;
					tx2 -= off_x;
					ty2 -= off_y;
				}
				tx1 = nfix(tx1, dpy_x);
				ty1 = nfix(ty1, dpy_y);
				tx2 = nfix(tx2, dpy_x+1);
				ty2 = nfix(ty2, dpy_y+1);

				dtime(&tm);
if (db) fprintf(stderr, "  DFC(%d,%d-%d,%d)", tx1, ty1, tx2, ty2);
				direct_fb_copy(tx1, ty1, tx2, ty2, 1);
				if (fast_push) {
					fb_push();
				}
				do_fb_push++;
PUSH_TEST(0);
			}
			sraRgnReleaseIterator(iter);

			dt = dtime(&tm);
if (db) fprintf(stderr, "  dfc---- dt: %.4f", dt);

		}
if (db &&  dret) fprintf(stderr, " **** dret=%d", dret);
if (db && !dret) fprintf(stderr, " ---- dret=%d", dret);
if (db) fprintf(stderr, "\n");
	}

if (db && bdpush) fprintf(stderr, "BDPUSH-TIME:  0x%lx\n", xrecord_wm_window);

	if (bdpush && xrecord_wm_window != None) {
		int x, y, w, h;
		x = scr_ev[0].x;
		y = scr_ev[0].y;
		w = scr_ev[0].w;
		h = scr_ev[0].h;
		do_fb_push += do_bdpush(xrecord_wm_window, x, y, w, h,
		    bdx, bdy, bdskinny); 
		if (fast_push) {
			fb_push();
		}
	}

	if (do_fb_push) {
		dtime0(&tm);
		fb_push();
		dt = dtime(&tm);
if (0) fprintf(stderr, "  fb_push dt: %.4f", dt);
		if (scaling) {
			static double last_time = 0.0;
			double now = dnow(), delay = 0.4, first_wait = 3.0;
			double trate;
			int repeating, lat, rate;
			int link = link_rate(&lat, &rate);
			int skip_first = 0;

			if (link == LR_DIALUP || rate < 35) {
				delay *= 4;
			} else if (link != LR_LAN || rate < 100) {
				delay *= 2;
			}

			trate = typing_rate(0.0, &repeating);
			
			if (xrecord_set_by_mouse || repeating >= 3) {
				if (now > last_scroll_event_save + first_wait) {
					skip_first = 1;
				}
			}

			if (skip_first) {
				/* 
				 * try not to send the first one, but a
				 * single keystroke scroll would be OK.
				 */
			} else if (now > last_time + delay) {

				scale_mark(x0, y0, x0 + w0, y0 + h0, 1);
				last_copyrect_fix = now;
			}
			last_time = now;
		}
	}

	sraRgnDestroy(backfill);
	sraRgnDestroy(whole);
	return dret;
}

static void get_client_regions(int *req, int *mod, int *cpy, int *num)  {
	
	rfbClientIteratorPtr i;
	rfbClientPtr cl;

	*req = 0;
	*mod = 0;
	*cpy = 0;
	*num = 0;

	i = rfbGetClientIterator(screen);
	while( (cl = rfbClientIteratorNext(i)) ) {
		*req += sraRgnCountRects(cl->requestedRegion);
		*mod += sraRgnCountRects(cl->modifiedRegion);
		*cpy += sraRgnCountRects(cl->copyRegion);
		*num += 1;
	}
	rfbReleaseClientIterator(i);
}

/*
 * Wrapper to apply the rfbDoCopyRegion taking into account if scaling
 * is being done.  Note that copyrect under the scaling case is often
 * only approximate.
 */
int DCR_Normal = 0;
int DCR_FBOnly = 1;
int DCR_Direct = 2;

void do_copyregion(sraRegionPtr region, int dx, int dy, int mode)  {
	sraRectangleIterator *iter;
	sraRect rect;
	int Bpp0 = bpp/8, Bpp;
	int x1, y1, x2, y2, w, stride, stride0;
	int sx1, sy1, sx2, sy2, sdx, sdy;
	int req, mod, cpy, ncli;
	char *dst = NULL, *src = NULL;

	last_copyrect = dnow();

	if (rfb_fb == main_fb && ! rotating && mode == DCR_Normal) {
		/* normal case, no -scale or -8to24 */
		get_client_regions(&req, &mod, &cpy, &ncli);
if (0 || debug_scroll > 1) fprintf(stderr, ">>>-rfbDoCopyRect req: %d mod: %d cpy: %d\n", req, mod, cpy); 

		rfbDoCopyRegion(screen, region, dx, dy);

		get_client_regions(&req, &mod, &cpy, &ncli);
if (0 || debug_scroll > 1) fprintf(stderr, "<<<-rfbDoCopyRect req: %d mod: %d cpy: %d\n", req, mod, cpy); 

		return;
	}

	/* rarer case, we need to call rfbDoCopyRect with scaled xy */
	stride0 = dpy_x * Bpp0;

	iter = sraRgnGetReverseIterator(region, dx < 0, dy < 0);
	while(sraRgnIteratorNext(iter, &rect)) {
		int j, c, t;

		x1 = rect.x1;
		y1 = rect.y1;
		x2 = rect.x2;
		y2 = rect.y2;

		for (c= 0; c < 2; c++) {

			Bpp = Bpp0;
			stride = stride0;

			if (c == 0) {
				dst = main_fb + y1*stride + x1*Bpp;
				src = main_fb + (y1-dy)*stride + (x1-dx)*Bpp;

			} else if (c == 1) {
				if (! cmap8to24_fb || cmap8to24_fb == rfb_fb) {
					continue;
				}
				if (cmap8to24 && depth == 8) {
					Bpp = 4 * Bpp0;
					stride = 4 * stride0;
				}
				dst = cmap8to24_fb + y1*stride + x1*Bpp;
				src = cmap8to24_fb + (y1-dy)*stride + (x1-dx)*Bpp;
			}

			w = (x2 - x1)*Bpp; 
			
			if (dy < 0) {
				for (j=y1; j<y2; j++) {
					memmove(dst, src, w);
					dst += stride;
					src += stride;
				}
			} else {
				dst += (y2 - y1 - 1)*stride;
				src += (y2 - y1 - 1)*stride;
				for (j=y2-1; j>=y1; j--) {
					memmove(dst, src, w);
					dst -= stride;
					src -= stride;
				}
			}
		}

		if (mode == DCR_FBOnly) {
			continue;
		}


		if (scaling) {
			sx1 = ((double) x1 / dpy_x) * scaled_x;
			sy1 = ((double) y1 / dpy_y) * scaled_y;
			sx2 = ((double) x2 / dpy_x) * scaled_x;
			sy2 = ((double) y2 / dpy_y) * scaled_y;
			sdx = ((double) dx / dpy_x) * scaled_x;
			sdy = ((double) dy / dpy_y) * scaled_y;
		} else {
			sx1 = x1;
			sy1 = y1;
			sx2 = x2;
			sy2 = y2;
			sdx = dx;
			sdy = dy;
		}
if (0) fprintf(stderr, "sa.. %d %d %d %d %d %d\n", sx1, sy1, sx2, sy2, sdx, sdy);

		if (rotating) {
			rotate_coords(sx1, sy1, &sx1, &sy1, -1, -1);
			rotate_coords(sx2, sy2, &sx2, &sy2, -1, -1);
			if (rotating == ROTATE_X) {
				sdx = -sdx;
			} else if (rotating == ROTATE_Y) {
				sdy = -sdy;
			} else if (rotating == ROTATE_XY) {
				sdx = -sdx;
				sdy = -sdy;
			} else if (rotating == ROTATE_90) {
				t = sdx;
				sdx = -sdy;
				sdy = t;
			} else if (rotating == ROTATE_90X) {
				t = sdx;
				sdx = sdy;
				sdy = t;
			} else if (rotating == ROTATE_90Y) {
				t = sdx;
				sdx = -sdy;
				sdy = -t;
			} else if (rotating == ROTATE_270) {
				t = sdx;
				sdx = sdy;
				sdy = -t;
			}
		}

		/* XXX -1? */
		if (sx2 < 0) sx2 = 0;
		if (sy2 < 0) sy2 = 0;
		
		if (sx2 < sx1) {
			t = sx1;
			sx1 = sx2;
			sx2 = t;
		}
		if (sy2 < sy1) {
			t = sy1;
			sy1 = sy2;
			sy2 = t;
		}
if (0) fprintf(stderr, "sb.. %d %d %d %d %d %d\n", sx1, sy1, sx2, sy2, sdx, sdy);

		if (mode == DCR_Direct) {
			rfbClientIteratorPtr i;
			rfbClientPtr cl;
			sraRegionPtr r = sraRgnCreateRect(sx1, sy1, sx2, sy2);

			i = rfbGetClientIterator(screen);
			while( (cl = rfbClientIteratorNext(i)) ) {
				rfbSendCopyRegion(cl, r, sdx, sdy);
			}
			rfbReleaseClientIterator(i);
			sraRgnDestroy(r);
			
		} else {
			rfbDoCopyRect(screen, sx1, sy1, sx2, sy2, sdx, sdy);
		}
	}
	sraRgnReleaseIterator(iter);
}

void batch_copyregion(sraRegionPtr* region, int *dx, int *dy, int ncr, double delay)  {
	rfbClientIteratorPtr i;
	rfbClientPtr cl;
	int k, direct, mode, nrects = 0, bad = 0;
	double t1, t2, start = dnow();

	for (k=0; k < ncr; k++) {
		sraRectangleIterator *iter;
		sraRect rect;

		iter = sraRgnGetIterator(region[k]);
		while (sraRgnIteratorNext(iter, &rect)) {
			int x1 = rect.x1;
			int y1 = rect.y1;
			int x2 = rect.x2;
			int y2 = rect.y2;
			int ym = dpy_y * (ncache+1);
			int xm = dpy_x;
			if (x1 > xm || y1 > ym || x2 > xm || y2 > ym) {
				if (ncdb) fprintf(stderr, "batch_copyregion: BAD RECTANGLE: %d,%d %d,%d\n", x1, y1, x2, y2);
				bad = 1;
			}
			if (x1 < 0 || y1 < 0 || x2 < 0 || y2 < 0) {
				if (ncdb) fprintf(stderr, "batch_copyregion: BAD RECTANGLE: %d,%d %d,%d\n", x1, y1, x2, y2);
				bad = 1;
			}
		}
		sraRgnReleaseIterator(iter);
		nrects += sraRgnCountRects(region[k]);
	}
	if (bad || nrects == 0) {
		return;
	}

	if (delay < 0.0) {
		delay = 0.1;
	}
	fb_push_wait(delay, FB_COPY|FB_MOD);

	t1 = dnow();

#if 0
	i = rfbGetClientIterator(screen);
	while( (cl = rfbClientIteratorNext(i)) ) {
		if (cl->ublen != 0) {
			fprintf(stderr, "batch_copyregion: *** BAD ublen != 0: %d\n", cl->ublen);
			bad++;
		}
	}
	rfbReleaseClientIterator(i);

	if (bad) {
		return;
	}
#endif

	i = rfbGetClientIterator(screen);
	while( (cl = rfbClientIteratorNext(i)) ) {
		rfbFramebufferUpdateMsg *fu = (rfbFramebufferUpdateMsg *)cl->updateBuf;
		fu->nRects = Swap16IfLE((uint16_t)(nrects));
		fu->type = rfbFramebufferUpdate;

		if (cl->ublen != 0) fprintf(stderr, "batch_copyregion: *** BAD ublen != 0: %d\n", cl->ublen);

		cl->ublen = sz_rfbFramebufferUpdateMsg;
	}
	rfbReleaseClientIterator(i);

	if (rfb_fb == main_fb && !rotating) {
		direct = 0;
		mode = DCR_FBOnly;
	} else {
		direct = 1;
		mode = DCR_Direct;
	}
	for (k=0; k < ncr; k++) {
		do_copyregion(region[k], dx[k], dy[k], mode);
	}

	t2 = dnow();

	i = rfbGetClientIterator(screen);
	while( (cl = rfbClientIteratorNext(i)) ) {
		if (!direct)  {
			for (k=0; k < ncr; k++) {
				rfbSendCopyRegion(cl, region[k], dx[k], dy[k]);
			}
		}
		rfbSendUpdateBuf(cl);
	}
	rfbReleaseClientIterator(i);

	last_copyrect = dnow();

if (0) fprintf(stderr, "batch_copyregion: nrects: %d nregions: %d  tot=%.4f t10=%.4f t21=%.4f t32=%.4f  %.4f\n",
    nrects, ncr, last_copyrect - start, t1 - start, t2 - t1, last_copyrect - t2, dnowx());

}

void batch_push(int nreg, double delay) {
	int k;
	batch_copyregion(batch_reg, batch_dxs, batch_dys, nreg, delay);
	/* XXX Y */
	fb_push();
	for (k=0; k < nreg; k++) {
		sraRgnDestroy(batch_reg[k]);
	}
}

void fb_push0(void) {
	char *httpdir = screen->httpDir;
	int defer = screen->deferUpdateTime;
	int req0, mod0, cpy0, req1, mod1, cpy1, ncli;
	int db = (debug_scroll || debug_wireframe);

	screen->httpDir = NULL;
	screen->deferUpdateTime = 0;

if (db)	get_client_regions(&req0, &mod0, &cpy0, &ncli);

	rfbPE(0);

	screen->httpDir = httpdir;
	screen->deferUpdateTime = defer;

if (db) {
	get_client_regions(&req1, &mod1, &cpy1, &ncli);
	fprintf(stderr, "\nFB_push: req: %d/%d  mod: %d/%d  cpy: %d/%d  %.4f\n",
	req0, req1, mod0, mod1, cpy0, cpy1, dnowx());
}

}

void fb_push(void) {
	int req0, mod0, cpy0, req1, mod1, cpy1, ncli;
	int db = (debug_scroll || debug_wireframe);
	rfbClientIteratorPtr i;
	rfbClientPtr cl;
	
if (db)	get_client_regions(&req0, &mod0, &cpy0, &ncli);

	i = rfbGetClientIterator(screen);
	while( (cl = rfbClientIteratorNext(i)) ) {
		if (cl->sock >= 0 && !cl->onHold && FB_UPDATE_PENDING(cl) &&
		    !sraRgnEmpty(cl->requestedRegion)) {
			if (!rfbSendFramebufferUpdate(cl, cl->modifiedRegion)) {
				fprintf(stderr, "*** rfbSendFramebufferUpdate *FAILED* #1\n");
				if (cl->ublen) fprintf(stderr, "*** fb_push ublen not zero: %d\n", cl->ublen);
				break;
			}
			if (cl->ublen) fprintf(stderr, "*** fb_push ublen NOT ZERO: %d\n", cl->ublen);
		}
	}
	rfbReleaseClientIterator(i);

if (db) {
	get_client_regions(&req1, &mod1, &cpy1, &ncli);
	fprintf(stderr, "\nFB_push: req: %d/%d  mod: %d/%d  cpy: %d/%d  %.4f\n",
	req0, req1, mod0, mod1, cpy0, cpy1, dnowx());
}

}

int fb_push_wait0(double max_wait, int flags) {
	double tm, dt = 0.0;
	int req, mod, cpy, ncli;
	int ok = 0;

	dtime0(&tm);	
	while (dt < max_wait) {
		int done = 1;
		rfbCFD(0);
		get_client_regions(&req, &mod, &cpy, &ncli);
		if (flags & FB_COPY && cpy) {
			done = 0;
		}
		if (flags & FB_MOD && mod) {
			done = 0;
		}
		if (flags & FB_REQ && req) {
			done = 0;
		}
		if (done) {
			ok = 1;
			break;
		}

		usleep(1000);
		fb_push();
		dt += dtime(&tm);
	}
	return ok;
}

int fb_push_wait(double max_wait, int flags) {
	double tm, dt = 0.0;
	int req, mod, cpy, ncli;
	int ok = 0, first = 1;

	dtime0(&tm);	
	while (dt < max_wait) {
		int done = 1;
		fb_push();
		get_client_regions(&req, &mod, &cpy, &ncli);
		if (flags & FB_COPY && cpy) {
			done = 0;
		}
		if (flags & FB_MOD && mod) {
			done = 0;
		}
		if (flags & FB_REQ && req) {
			done = 0;
		}
		if (done) {
			ok = 1;
			break;
		}
		if (first) {
			first = 0;
			continue;	
		}

		rfbCFD(0);
		usleep(1000);
		dt += dtime(&tm);
	}
	return ok;
}

/* 
 * utility routine for CopyRect of the window (but not CopyRegion)
 */
static int crfix(int x, int dx, int Lx) {
	/* adjust x so that copy source is on screen */
	if (dx > 0) {
		if (x-dx < 0) {
			/* off on the left */
			x = dx;	
		}
	} else {
		if (x-dx >= Lx) {
			/* off on the right */
			x = Lx + dx - 1;
		}
	}
	return x;
}

typedef struct scroll_result {
	Window win;
	double time;
	int result;
} scroll_result_t;

#define SCR_RESULTS_MAX 256
static scroll_result_t scroll_results[SCR_RESULTS_MAX];

static int scrollability(Window win, int set) {
	double oldest = -1.0;
	int i, index = -1, next_index = -1;
	static int first = 1;

	if (first) {
		for (i=0; i<SCR_RESULTS_MAX; i++) {
			scroll_results[i].win = None;
			scroll_results[i].time = 0.0;
			scroll_results[i].result = 0;
		}
		first = 0;
	}

	if (win == None) {
		return 0;
	}
	if (set == SCR_NONE) {
		/* lookup case */
		for (i=0; i<SCR_RESULTS_MAX; i++) {
			if (win == scroll_results[i].win) {
				return scroll_results[i].result;
			}
			if (scroll_results[i].win == None) {
				break;
			}
		}
		return 0;
	}

	for (i=0; i<SCR_RESULTS_MAX; i++) {
		if (oldest == -1.0 || scroll_results[i].time < oldest) {
			next_index = i;
			oldest = scroll_results[i].time;
		}
		if (win == scroll_results[i].win) {
			index = i;
			break;
		}
		if (next_index >= 0 && scroll_results[i].win == None) {
			break;
		}
	}

	if (set == SCR_SUCCESS) {
		set = 1;
	} else if (set == SCR_FAIL) {
		set = -1;
	} else {
		set = 0;
	}
	if (index == -1) {
		scroll_results[next_index].win = win;
		scroll_results[next_index].time = dnow();
		scroll_results[next_index].result = set;
	} else {
		if (scroll_results[index].result == 1) {
			/*
			 * once a success, always a success, until they
			 * forget about us...
			 */
			set = 1;
		} else {
			scroll_results[index].result = set;
		}
		scroll_results[index].time = dnow();
	}

	return set;
}

void eat_viewonly_input(int max_eat, int keep) {
	int i, gp, gk;
	
	for (i=0; i<max_eat; i++) {
		int cont = 0;
		gp = got_pointer_calls;
		gk = got_keyboard_calls;
		rfbCFD(0);
		if (got_pointer_calls > gp)  {
			if (debug_pointer) {
				rfbLog("eat_viewonly_input: pointer: %d\n", i);
			}
			cont++;
		}
		if (got_keyboard_calls > gk)  {
			if (debug_keyboard) {
				rfbLog("eat_viewonly_input: keyboard: %d\n", i);
			}
			cont++;
		}
		if (i >= keep - 1 && ! cont) {
			break;
		}
	}
}

static int eat_pointer(int max_ptr_eat, int keep) {
	int i, count = 0,  gp = got_pointer_input;

	for (i=0; i<max_ptr_eat; i++) {
		rfbCFD(0);
		if (got_pointer_input > gp)  {
			count++;
if (0) fprintf(stderr, "GP*-%d\n", i);
			gp = got_pointer_input;
		} else if (i > keep) {
			break;
		}
	}
	return count;
}

static void set_bdpush(int type, double *last_bdpush, int *pushit) {
	double now, delay = 0.0;
	int link, latency, netrate;

	*pushit = 0;

	if (type == SCR_MOUSE) {
		delay = scr_mouse_bdpush_time;
	} else if (type == SCR_KEY) {
		delay = scr_key_bdpush_time;
	}

	link = link_rate(&latency, &netrate);
	if (link == LR_DIALUP) {
		delay *= 1.5;
	} else if (link == LR_BROADBAND) {
		delay *= 1.25;
	}

	dtime0(&now);
	if (delay > 0.0 && now > *last_bdpush + delay) {
		*pushit = 1;
		*last_bdpush = now;
	}
}

void mark_for_xdamage(int x, int y, int w, int h) {
	int tx1, ty1, tx2, ty2;
	sraRegionPtr tmpregion;

	if (! use_xdamage) {
		return;
	}

	tx1 = nfix(x, dpy_x);
	ty1 = nfix(y, dpy_y);
	tx2 = nfix(x + w, dpy_x+1);
	ty2 = nfix(y + h, dpy_y+1);

	tmpregion = sraRgnCreateRect(tx1, ty1, tx2, ty2);
	add_region_xdamage(tmpregion);
	sraRgnDestroy(tmpregion);
}

void mark_region_for_xdamage(sraRegionPtr region) {
	sraRectangleIterator *iter;
	sraRect rect;
	iter = sraRgnGetIterator(region);
	while (sraRgnIteratorNext(iter, &rect)) {
		int x1 = rect.x1;
		int y1 = rect.y1;
		int x2 = rect.x2;
		int y2 = rect.y2;
		mark_for_xdamage(x1, y1, x2 - x1, y2 - y1);
	}
	sraRgnReleaseIterator(iter);
}

void set_xdamage_mark(int x, int y, int w, int h) {
	sraRegionPtr region;

	if (! use_xdamage) {
		return;
	}
	mark_for_xdamage(x, y, w, h);

	if (xdamage_scheduled_mark == 0.0) {
		xdamage_scheduled_mark = dnow() + 2.0;
	}

	if (xdamage_scheduled_mark_region == NULL) {
		xdamage_scheduled_mark_region = sraRgnCreate();
	}
	region = sraRgnCreateRect(x, y, x + w, y + w);
	sraRgnOr(xdamage_scheduled_mark_region, region);
	sraRgnDestroy(region);
}

static int repeat_check(double last_key_scroll) {
	int repeating;
	double rate = typing_rate(0.0, &repeating);
	double now = dnow(), delay = 0.5;
	if (rate > 2.0 && repeating && now > last_key_scroll + delay) {
		return 0;
	} else {
		return 1;
	}
}

static int check_xrecord_keys(void) {
	static int last_wx, last_wy, last_ww, last_wh;
	double spin = 0.0, tm, tnow;
	int scr_cnt = 0, input = 0, scroll_rep;
	int get_out, got_one = 0, flush1 = 0, flush2 = 0;
	int gk, gk0, ret = 0, db = debug_scroll;
	int fail = 0;
	int link, latency, netrate;

	static double last_key_scroll = 0.0;
	static double persist_start = 0.0;
	static double last_bdpush = 0.0;
	static int persist_count = 0;
	int scroll_keysym = 0;
	double last_scroll, scroll_persist = scr_key_persist;
	double spin_fac = 1.0, scroll_fac = 2.0, noscroll_fac = 0.75;
	double max_spin, max_long_spin = 0.3;
	double set_repeat_in;
	static double set_repeat = 0.0;


	RAWFB_RET(0)

	if (unixpw_in_progress) return 0;

	set_repeat_in = set_repeat;
	set_repeat = 0.0;

	get_out = 1;
	if (got_keyboard_input) {
		get_out = 0;
	} 

	dtime0(&tnow);
	if (tnow < last_key_scroll + scroll_persist) {
		get_out = 0;
	}

	if (set_repeat_in > 0.0 && tnow < last_key_scroll + set_repeat_in) {
		get_out = 0;
	}

	if (get_out) {
		persist_start = 0.0;
		persist_count = 0;
		last_bdpush = 0.0;
		if (xrecording) {
			xrecord_watch(0, SCR_KEY);
		}
		return 0;
	}

#if 0
	/* not used for keyboard yet */
	scroll_rep = scrollability(xrecord_ptr_window, SCR_NONE) + 1;
	if (scroll_rep == 1) {
		scroll_rep = 2;		/* if no info, assume the best. */
	}
#endif

	scroll_keysym = xrecord_scroll_keysym(last_rfb_keysym);

	max_spin = scr_key_time;

	if (set_repeat_in > 0.0 && tnow < last_key_scroll + 2*set_repeat_in) {
		max_spin = 2 * set_repeat_in;
	} else if (tnow < last_key_scroll + scroll_persist) {
		max_spin = 1.25*(tnow - last_key_scroll);
	} else if (tnow < last_key_to_button_remap_time + 1.5*scroll_persist) {
		/* mostly a hack I use for testing -remap key -> btn4/btn5 */
		max_spin = scroll_persist;
	} else if (scroll_keysym) {
		if (repeat_check(last_key_scroll)) {
			spin_fac = scroll_fac;
		} else {
			spin_fac = noscroll_fac;
		}
	}
	if (max_spin > max_long_spin) {
		max_spin = max_long_spin;
	}

	/* XXX use this somehow  */
if (0)	link = link_rate(&latency, &netrate);

	gk = gk0 = got_keyboard_input;
	dtime0(&tm);

if (db) fprintf(stderr, "check_xrecord_keys: BEGIN LOOP: scr_ev_cnt: "
    "%d max: %.3f  %.4f\n", scr_ev_cnt, max_spin, tm - x11vnc_start);

	while (1) {

		if (scr_ev_cnt) {
			got_one = 1;

			scrollability(xrecord_ptr_window, SCR_SUCCESS);
			scroll_rep = 2;

			dtime0(&last_scroll);
			last_key_scroll = last_scroll;
			scr_cnt++;
			break;
		}

		X_LOCK;
		flush1 = 1;
		XFlush_wr(dpy);
		X_UNLOCK;

		if (set_repeat_in > 0.0) {
			max_keyrepeat_time = set_repeat_in;
		}

		if (use_threads) {
			usleep(1000);
		} else {
			rfbCFD(1000);
		}
		spin += dtime(&tm);

		X_LOCK;
		if (got_keyboard_input > gk) {
			gk = got_keyboard_input;
			input++;
			if (set_repeat_in) {
				;
			} else if (xrecord_scroll_keysym(last_rfb_keysym)) {
				if (repeat_check(last_key_scroll)) {
					spin_fac = scroll_fac;
				} else {
					spin_fac = noscroll_fac;
				}
			}
if (0 || db) fprintf(stderr, "check_xrecord: more keys: %.3f  0x%x "
    " %.4f  %s  %s\n", spin, last_rfb_keysym, last_rfb_keytime - x11vnc_start,
    last_rfb_down ? "down":"up  ", last_rfb_key_accepted ? "accept":"skip");
			flush2 = 1;
			XFlush_wr(dpy);
		}
#if LIBVNCSERVER_HAVE_RECORD
		SCR_LOCK;
		XRecordProcessReplies(rdpy_data);
		SCR_UNLOCK;
#endif
		X_UNLOCK;

		if (spin >= max_spin * spin_fac) {
if (0 || db) fprintf(stderr, "check_xrecord: SPIN-OUT: %.3f/%.3f\n", spin,
    max_spin * spin_fac);
			fail = 1;
			break;
		}
	}

	max_keyrepeat_time = 0.0;

	if (scr_ev_cnt) {
		int dret, ev = scr_ev_cnt - 1;
		int bdx, bdy, bdskinny, bdpush = 0;
		double max_age = 0.25, age, tm, dt;
		static double last_scr_ev = 0.0;

		last_wx = scr_ev[ev].win_x;
		last_wy = scr_ev[ev].win_y;
		last_ww = scr_ev[ev].win_w;
		last_wh = scr_ev[ev].win_h;

		/* assume scrollbar on rhs: */
		bdx = last_wx + last_ww + 3;
		bdy = last_wy + last_wh/2;
		bdskinny = 32;
			
		if (persist_start == 0.0) {
			bdpush = 0;
		} else {
			set_bdpush(SCR_KEY, &last_bdpush, &bdpush);
		}

		dtime0(&tm);
		age = max_age;
		dret = push_scr_ev(&age, SCR_KEY, bdpush, bdx, bdy, bdskinny, 1);
		dt = dtime(&tm);

		ret = 1 + dret;
		scr_ev_cnt = 0;

		if (ret == 2 && xrecord_scroll_keysym(last_rfb_keysym)) {
			int repeating;
			double time_lo = 1.0/max_keyrepeat_lo;
			double time_hi = 1.0/max_keyrepeat_hi;
			double rate = typing_rate(0.0, &repeating);
if (0 || db) fprintf(stderr, "Typing: dt: %.4f rate: %.1f\n", dt, rate);
			if (repeating) {
				/* n.b. the "quantum" is about 1/30 sec. */
				max_keyrepeat_time = 1.0*dt;
				if (max_keyrepeat_time > time_lo ||
				    max_keyrepeat_time < time_hi) {
					max_keyrepeat_time = 0.0;
				} else {
					set_repeat = max_keyrepeat_time;
if (0 || db) fprintf(stderr, "set max_keyrepeat_time: %.2f\n", max_keyrepeat_time);
				}
			}
		}

		last_scr_ev = dnow();
	}

	if ((got_one && ret < 2) || persist_count) {
		set_xdamage_mark(last_wx, last_wy, last_ww, last_wh);
	}

	if (fail) {
		scrollability(xrecord_ptr_window, SCR_FAIL);
	}

	if (xrecording) {
		if (ret < 2) {
			xrecord_watch(0, SCR_KEY);
		}
	}

	if (ret == 2) {
		if (persist_start == 0.0) {
			dtime(&persist_start);
			last_bdpush = persist_start;
		}
	} else {
		persist_start = 0.0;
		last_bdpush = 0.0;
	}

	/* since we've flushed it, we might as well avoid -input_skip */
	if (flush1 || flush2) {
		got_keyboard_input = 0;
		got_pointer_input = 0;
	}

	return ret;
}

static int check_xrecord_mouse(void) {
	static int last_wx, last_wy, last_ww, last_wh;
	double spin = 0.0, tm, tnow;
	int i, scr_cnt = 0, input = 0, scroll_rep;
	int get_out, got_one = 0, flush1 = 0, flush2 = 0;
	int gp, gp0, ret = 0, db = debug_scroll;
	int gk, gk0;
	int fail = 0;
	int link, latency, netrate;

	int start_x, start_y, last_x, last_y;
	static double last_mouse_scroll = 0.0;
	double last_scroll;
	double max_spin[3], max_long[3], persist[3];
	double flush1_time = 0.01;
	static double last_flush = 0.0;
	double last_bdpush = 0.0, button_up_time = 0.0;
	int button_mask_save;
	int already_down = 0, max_ptr_eat = 20;
	static int want_back_in = 0;
	int came_back_in;
	int first_push = 1;

	int scroll_wheel = 0;
	int btn4 = (1<<3);
	int btn5 = (1<<4);

	RAWFB_RET(0)

	get_out = 1;
	if (button_mask) {
		get_out = 0;
	} 
	if (want_back_in) {
		get_out = 0;
	} 
	dtime0(&tnow);
if (0) fprintf(stderr, "check_xrecord_mouse: IN xrecording: %d\n", xrecording);

	if (get_out) {
		if (xrecording) {
			xrecord_watch(0, SCR_MOUSE);
		}
		return 0;
	}

	scroll_rep = scrollability(xrecord_ptr_window, SCR_NONE) + 1;
	if (scroll_rep == 1) {
		scroll_rep = 2;		/* if no info, assume the best. */
	}

	if (button_mask_prev) {
		already_down = 1;
	}
	if (want_back_in) {
		came_back_in = 1;
		first_push = 0;
	} else {
		came_back_in = 0;
	}
	want_back_in = 0;

	if (button_mask & (btn4|btn5)) {
		scroll_wheel = 1;
	}

	/*
	 * set up times for the various "reputations"
	 *
	 * 0 => -1, has been tried but never found a scroll.
	 * 1 =>  0, has not been tried.
	 * 2 => +1, has been tried and found a scroll.
	 */

	/* first spin-out time (no events) */
	max_spin[0] = 1*scr_mouse_time;
	max_spin[1] = 2*scr_mouse_time;
	max_spin[2] = 4*scr_mouse_time;
	if (!already_down) {
		for (i=0; i<3; i++) {
			max_spin[i] *= 1.5;
		}
	}

	/* max time between events */
	persist[0] = 1*scr_mouse_persist;
	persist[1] = 2*scr_mouse_persist;
	persist[2] = 4*scr_mouse_persist;

	/* absolute max time in the loop */
	max_long[0] = scr_mouse_maxtime;
	max_long[1] = scr_mouse_maxtime;
	max_long[2] = scr_mouse_maxtime;

	pointer_flush_delay = scr_mouse_pointer_delay;

	/* slow links: */
	link = link_rate(&latency, &netrate);
	if (link == LR_DIALUP) {
		for (i=0; i<3; i++) {
			max_spin[i] *= 2.0;
		}
		pointer_flush_delay *= 2;
	} else if (link == LR_BROADBAND) {
		pointer_flush_delay *= 2;
	}

	gp = gp0 = got_pointer_input;
	gk = gk0 = got_keyboard_input;
	dtime0(&tm);

	/*
	 * this is used for border pushes (bdpush) to guess location
	 * of scrollbar (region rects containing this point are pushed).
	 */
	last_x = start_x = cursor_x;
	last_y = start_y = cursor_y;

if (db) fprintf(stderr, "check_xrecord_mouse: BEGIN LOOP: scr_ev_cnt: "
    "%d max: %.3f  %.4f\n", scr_ev_cnt, max_spin[scroll_rep], tm - x11vnc_start);

	while (1) {
		double spin_check;
		if (scr_ev_cnt) {
			int dret, ev = scr_ev_cnt - 1;
			int bdpush = 0, bdx, bdy, bdskinny;
			double tm, dt, age = 0.35;

			got_one = 1;
			scrollability(xrecord_ptr_window, SCR_SUCCESS);
			scroll_rep = 2;

			scr_cnt++;

			dtime0(&last_scroll);
			last_mouse_scroll = last_scroll;

			if (last_bdpush == 0.0) {
				last_bdpush = last_scroll;
			}

			bdx = start_x;
			bdy = start_y;
			if (clipshift) {
				bdx += coff_x;
				bdy += coff_y;
			}
			if (subwin) {
				bdx += off_x;
				bdy += off_y;
			}
			bdskinny = 32;
			
			set_bdpush(SCR_MOUSE, &last_bdpush, &bdpush);

			dtime0(&tm);

			dret = push_scr_ev(&age, SCR_MOUSE, bdpush, bdx,
			    bdy, bdskinny, first_push);
			if (first_push) first_push = 0;
			ret = 1 + dret;

			dt = dtime(&tm);

if (db) fprintf(stderr, "  dret: %d  scr_ev_cnt: %d dt: %.4f\n",
	dret, scr_ev_cnt, dt);

			last_wx = scr_ev[ev].win_x;
			last_wy = scr_ev[ev].win_y;
			last_ww = scr_ev[ev].win_w;
			last_wh = scr_ev[ev].win_h;
			scr_ev_cnt = 0;

			if (! dret) {
				break;
			}
			if (0 && button_up_time > 0.0) {
				/* we only take 1 more event with button up */
if (db) fprintf(stderr, "check_xrecord: BUTTON_UP_SCROLL: %.3f\n", spin);
				break;
			}
		}


		if (! flush1) {
			if (! already_down || (!scr_cnt && spin>flush1_time)) {
				flush1 = 1;
				X_LOCK;
				XFlush_wr(dpy);
				X_UNLOCK;
				dtime0(&last_flush);
			}
		}

		if (use_threads) {
			usleep(1000);
		} else {
			rfbCFD(1000);
			rfbCFD(0);
		}
		spin += dtime(&tm);

		if (got_pointer_input > gp) {
			flush2 = 1;
			input += eat_pointer(max_ptr_eat, 1);
			gp = got_pointer_input;
		}
		if (got_keyboard_input > gk) {
			gk = got_keyboard_input;
			input++;
		}
		X_LOCK;
#if LIBVNCSERVER_HAVE_RECORD
		SCR_LOCK;
		XRecordProcessReplies(rdpy_data);
		SCR_UNLOCK;
#endif
		X_UNLOCK;

		if (! input) {
			spin_check = 1.5 * max_spin[scroll_rep];
		} else {
			spin_check = max_spin[scroll_rep];
		}

		if (button_up_time > 0.0) {
			if (tm > button_up_time + max_spin[scroll_rep]) {
if (db) fprintf(stderr, "check_xrecord: SPIN-OUT-BUTTON_UP: %.3f/%.3f\n", spin, tm - button_up_time);
				break;
			}
		} else if (!scr_cnt) {
			if (spin >= spin_check) {

if (db) fprintf(stderr, "check_xrecord: SPIN-OUT-1: %.3f/%.3f\n", spin, spin_check);
				fail = 1;
				break;
			}
		} else {
			if (tm >= last_scroll + persist[scroll_rep]) {

if (db) fprintf(stderr, "check_xrecord: SPIN-OUT-2: %.3f/%.3f\n", spin, tm - last_scroll);
				break;
			}
		}
		if (spin >= max_long[scroll_rep]) {

if (db) fprintf(stderr, "check_xrecord: SPIN-OUT-3: %.3f/%.3f\n", spin, max_long[scroll_rep]);
			break;
		}

		if (! button_mask) {
			int doflush = 0;
			if (button_up_time > 0.0) {
				;
			} else if (came_back_in) {
				dtime0(&button_up_time);
				doflush = 1;
			} else if (scroll_wheel) {
if (db) fprintf(stderr, "check_xrecord: SCROLL-WHEEL-BUTTON-UP-KEEP-GOING:  %.3f/%.3f %d/%d %d/%d\n", spin, max_long[scroll_rep], last_x, last_y, cursor_x, cursor_y);
				doflush = 1;
				dtime0(&button_up_time);
			} else if (last_x == cursor_x && last_y == cursor_y) {
if (db) fprintf(stderr, "check_xrecord: BUTTON-UP:  %.3f/%.3f %d/%d %d/%d\n", spin, max_long[scroll_rep], last_x, last_y, cursor_x, cursor_y);
				break;
			} else {
if (db) fprintf(stderr, "check_xrecord: BUTTON-UP-KEEP-GOING:  %.3f/%.3f %d/%d %d/%d\n", spin, max_long[scroll_rep], last_x, last_y, cursor_x, cursor_y);
				doflush = 1;
				dtime0(&button_up_time);
			}
			if (doflush) {
				flush1 = 1;
				X_LOCK;
				XFlush_wr(dpy);
				X_UNLOCK;
				dtime0(&last_flush);
			}
		}

		last_x = cursor_x;
		last_y = cursor_y;
	}

	if (got_one) {
		set_xdamage_mark(last_wx, last_wy, last_ww, last_wh);
	}

	if (fail) {
		scrollability(xrecord_ptr_window, SCR_FAIL);
	}

	/* flush any remaining pointer events. */
	button_mask_save = button_mask;
	pointer_queued_sent = 0;
	last_x = cursor_x;
	last_y = cursor_y;
	pointer(-1, 0, 0, NULL);
	pointer_flush_delay = 0.0;

	if (xrecording && pointer_queued_sent && button_mask_save &&
	    (last_x != cursor_x || last_y != cursor_y) ) {
if (db) fprintf(stderr, "  pointer() push yields events on: ret=%d\n", ret);
		if (ret == 2) {
if (db) fprintf(stderr, "  we decide to send ret=3\n");
			want_back_in = 1;
			ret = 3;
			flush2 = 1;
		} else {
			if (ret) {
				ret = 1;
			} else {
				ret = 0;
			}
			xrecord_watch(0, SCR_MOUSE);
		}
	} else {
		if (ret) {
			ret = 1;
		} else {
			ret = 0;
		}
		if (xrecording) {
			xrecord_watch(0, SCR_MOUSE);
		}
	}

	if (flush2) {
		X_LOCK;
		XFlush_wr(dpy);
		XFlush_wr(rdpy_ctrl);
		X_UNLOCK;

		flush2 = 1;
		dtime0(&last_flush);

if (db) fprintf(stderr, "FLUSH-2\n");
	}

	/* since we've flushed it, we might as well avoid -input_skip */
	if (flush1 || flush2) {
		got_keyboard_input = 0;
		got_pointer_input = 0;
	}

	if (ret) {
		return ret;
	} else if (scr_cnt) {
		return 1;
	} else {
		return 0;
	}
}

int check_xrecord(void) {
	int watch_keys = 0, watch_mouse = 0, consider_mouse;
	static int mouse_wants_back_in = 0;

	RAWFB_RET(0)

	if (! use_xrecord) {
		return 0;
	}
	if (unixpw_in_progress) return 0;

	if (skip_cr_when_scaling("scroll")) {
		return 0;
	}

if (0) fprintf(stderr, "check_xrecord: IN xrecording: %d\n", xrecording);

	if (! xrecording) {
		return 0;
	}

	if (!strcmp(scroll_copyrect, "always")) {
		watch_keys = 1;
		watch_mouse = 1;
	} else if (!strcmp(scroll_copyrect, "keys")) {
		watch_keys = 1;
	} else if (!strcmp(scroll_copyrect, "mouse")) {
		watch_mouse = 1;
	}

	if (button_mask || mouse_wants_back_in) {
		consider_mouse = 1;
	} else {
		consider_mouse = 0;
	}
if (0) fprintf(stderr, "check_xrecord: button_mask: %d  mouse_wants_back_in: %d\n", button_mask, mouse_wants_back_in);

	if (watch_mouse && consider_mouse && xrecord_set_by_mouse) {
		int ret = check_xrecord_mouse();
		if (ret == 3) {
			mouse_wants_back_in = 1;
		} else {
			mouse_wants_back_in = 0;
		}
		return ret;
	} else if (watch_keys && xrecord_set_by_keys) {
		mouse_wants_back_in = 0;
		return check_xrecord_keys();
	} else {
		mouse_wants_back_in = 0;
		return 0;
	}
}

#define DB_SET \
	int db  = 0; \
	int db2 = 0; \
	if (debug_wireframe == 1) { \
		db = 1; \
	} \
	if (debug_wireframe == 2) { \
		db2 = 1; \
	} \
	if (debug_wireframe == 3) { \
		db = 1; \
		db2 = 1; \
	}

#define NBATCHMAX 1024
int batch_dxs[NBATCHMAX], batch_dys[NBATCHMAX];
sraRegionPtr batch_reg[NBATCHMAX];

static int try_copyrect(Window orig_frame, Window frame, int x, int y, int w, int h,
    int dx, int dy, int *obscured, sraRegionPtr extra_clip, double max_wait, int *nbatch) {

	static int dt_bad = 0;
	static time_t dt_bad_check = 0;
	int x1, y1, x2, y2, sent_copyrect = 0;
	int req, mod, cpy, ncli;
	double tm, dt;
	DB_SET

	if (nbatch == NULL) {
		get_client_regions(&req, &mod, &cpy, &ncli);
		if (cpy) {
			/* one is still pending... try to force it out: */
			if (!fb_push_wait(max_wait, FB_COPY)) {
				fb_push_wait(max_wait/2, FB_COPY);
			}

			get_client_regions(&req, &mod, &cpy, &ncli);
		}
		if (cpy) {
			return 0;
		}
	}

	*obscured = 0;
	/*
	 * XXX KDE and xfce do some weird things with the 
	 * stacking, it does not match XQueryTree.  Work around
	 * it for now by CopyRect-ing the *whole* on-screen 
	 * rectangle (whether obscured or not!)
	 */
	if (time(NULL) > dt_bad_check + 5) {
		char *dt = guess_desktop();
		if (!strcmp(dt, "kde_maybe_is_ok_now...")) {
			dt_bad = 1;
		} else if (!strcmp(dt, "xfce")) {
			dt_bad = 1;
		} else {
			dt_bad = 0;
		}
		dt_bad_check = time(NULL);
	}

	if (clipshift) {
		x -= coff_x;
		y -= coff_y;
	}
	if (subwin) {
		x -= off_x;
		y -= off_y;
	}
if (db2) fprintf(stderr, "try_copyrect: 0x%lx/0x%lx  bad: %d stack_list_num: %d\n", orig_frame, frame, dt_bad, stack_list_num);

/* XXX Y dt_bad = 0 */
	if (dt_bad && wireframe_in_progress) {
		sraRegionPtr rect;
		/* send the whole thing... */
		x1 = crfix(nfix(x,   dpy_x), dx, dpy_x);
		y1 = crfix(nfix(y,   dpy_y), dy, dpy_y);
		x2 = crfix(nfix(x+w, dpy_x+1), dx, dpy_x+1);
		y2 = crfix(nfix(y+h, dpy_y+1), dy, dpy_y+1);

		rect = sraRgnCreateRect(x1, y1, x2, y2);

		if (blackouts) {
			int i;
			sraRegionPtr bo_rect;
			for (i=0; i<blackouts; i++) {
				bo_rect = sraRgnCreateRect(blackr[i].x1,
				    blackr[i].y1, blackr[i].x2, blackr[i].y2);
				sraRgnSubtract(rect, bo_rect);
				sraRgnDestroy(bo_rect);
			}
		}
		if (!nbatch) {
			do_copyregion(rect, dx, dy, 0);
		} else {
			batch_dxs[*nbatch] = dx;
			batch_dys[*nbatch] = dy;
			batch_reg[*nbatch] = sraRgnCreateRgn(rect);
			(*nbatch)++;
		}
		sraRgnDestroy(rect);

		sent_copyrect = 1;
		*obscured = 1;	/* set to avoid an aggressive push */

	} else if (stack_list_num || dt_bad) {
		int k, tx1, tx2, ty1, ty2;
		sraRegionPtr moved_win, tmp_win, whole;
		sraRectangleIterator *iter;
		sraRect rect;
		int saw_me = 0;
		int orig_x, orig_y;
		int boff, bwin;
		XWindowAttributes attr;

		orig_x = x - dx;
		orig_y = y - dy;

		tx1 = nfix(orig_x,   dpy_x);
		ty1 = nfix(orig_y,   dpy_y);
		tx2 = nfix(orig_x+w, dpy_x+1);
		ty2 = nfix(orig_y+h, dpy_y+1);

if (db2) fprintf(stderr, "moved_win: %4d %3d, %4d %3d  0x%lx ---\n",
	tx1, ty1, tx2, ty2, frame);

		moved_win = sraRgnCreateRect(tx1, ty1, tx2, ty2);

		dtime0(&tm);

		boff = get_boff();
		bwin = get_bwin();

		X_LOCK;

		/*
		 * loop over the stack, top to bottom until we
		 * find our wm frame:
		 */
		for (k = stack_list_num - 1; k >= 0; k--) {
			Window swin;

			if (0 && dt_bad) {
				break;
			}

			swin = stack_list[k].win;
if (db2) fprintf(stderr, "sw: %d/%lx\n", k, swin);
			if (swin == frame || swin == orig_frame) {
 if (db2) {
 saw_me = 1; fprintf(stderr, "  ----------\n");
 } else {
				break;	
 }
			}

			/* skip some unwanted cases: */
#ifndef MACOSX
			if (swin == None) {
				continue;
			}
#endif
			if (boff <= (int) swin && (int) swin < boff + bwin) {
				;	/* blackouts */
			} else if (! stack_list[k].fetched ||
			    stack_list[k].time > tm + 2.0) {
				if (!valid_window(swin, &attr, 1)) {
					stack_list[k].valid = 0;
				} else {
					stack_list[k].valid = 1;
					stack_list[k].x = attr.x;
					stack_list[k].y = attr.y;
					stack_list[k].width = attr.width;
					stack_list[k].height = attr.height;
					stack_list[k].depth = attr.depth;
					stack_list[k].class = attr.class;
					stack_list[k].backing_store =
					    attr.backing_store;
					stack_list[k].map_state =
					    attr.map_state;
				}
				stack_list[k].fetched = 1;
				stack_list[k].time = tm;
			}
			if (!stack_list[k].valid) {
				continue;
			}

			attr.x      = stack_list[k].x;
			attr.y      = stack_list[k].y;
			attr.depth  = stack_list[k].depth;
			attr.width  = stack_list[k].width;
			attr.height = stack_list[k].height;
			attr.map_state = stack_list[k].map_state;

			if (attr.map_state != IsViewable) {
				continue;
			}
if (db2) fprintf(stderr, "sw: %d/%lx  %dx%d+%d+%d\n", k, swin, stack_list[k].width, stack_list[k].height, stack_list[k].x, stack_list[k].y);

			if (clipshift) {
				attr.x -= coff_x;
				attr.y -= coff_y;
			}
			if (subwin) {
				attr.x -= off_x;
				attr.y -= off_y;
			}

			/*
			 * first subtract any overlap from the initial
			 * window rectangle
			 */

			/* clip the window to the visible screen: */
			tx1 = nfix(attr.x, dpy_x);
			ty1 = nfix(attr.y, dpy_y);
			tx2 = nfix(attr.x + attr.width,  dpy_x+1);
			ty2 = nfix(attr.y + attr.height, dpy_y+1);

if (db2) fprintf(stderr, "  tmp_win-1: %4d %3d, %4d %3d  0x%lx\n",
	tx1, ty1, tx2, ty2, swin);
if (db2 && saw_me) continue;

			/* see if window clips us: */
			tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2);
			if (sraRgnAnd(tmp_win, moved_win)) {
				*obscured = 1;
if (db2) fprintf(stderr, "         : clips it.\n");
			}
			sraRgnDestroy(tmp_win);

			/* subtract it from our region: */
			tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2);
			sraRgnSubtract(moved_win, tmp_win);
			sraRgnDestroy(tmp_win);

			/*
			 * next, subtract from the initial window rectangle
			 * anything that would clip it.
			 */

			/* clip the window to the visible screen: */
			tx1 = nfix(attr.x - dx, dpy_x);
			ty1 = nfix(attr.y - dy, dpy_y);
			tx2 = nfix(attr.x - dx + attr.width,  dpy_x+1);
			ty2 = nfix(attr.y - dy + attr.height, dpy_y+1);

if (db2) fprintf(stderr, "  tmp_win-2: %4d %3d, %4d %3d  0x%lx\n",
	tx1, ty1, tx2, ty2, swin);
if (db2 && saw_me) continue;

			/* subtract it from our region: */
			tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2);
			sraRgnSubtract(moved_win, tmp_win);
			sraRgnDestroy(tmp_win);
		}
		X_UNLOCK;

		if (extra_clip && ! sraRgnEmpty(extra_clip)) {
		    whole = sraRgnCreateRect(0, 0, dpy_x, dpy_y);

		    if (clipshift) {
			sraRgnOffset(extra_clip, -coff_x, -coff_y);
		    }
		    if (subwin) {
			sraRgnOffset(extra_clip, -off_x, -off_y);
		    }

		    iter = sraRgnGetIterator(extra_clip);
		    while (sraRgnIteratorNext(iter, &rect)) {
			/* clip the window to the visible screen: */
			tx1 = rect.x1;
			ty1 = rect.y1;
			tx2 = rect.x2;
			ty2 = rect.y2;
			tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2);
			sraRgnAnd(tmp_win, whole);

			/* see if window clips us: */
			if (sraRgnAnd(tmp_win, moved_win)) {
				*obscured = 1;
			}
			sraRgnDestroy(tmp_win);

			/* subtract it from our region: */
			tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2);
			sraRgnSubtract(moved_win, tmp_win);
			sraRgnDestroy(tmp_win);

			/*
			 * next, subtract from the initial window rectangle
			 * anything that would clip it.
			 */
			tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2);
			sraRgnOffset(tmp_win, -dx, -dy);

			/* clip the window to the visible screen: */
			sraRgnAnd(tmp_win, whole);

			/* subtract it from our region: */
			sraRgnSubtract(moved_win, tmp_win);
			sraRgnDestroy(tmp_win);
		    }
		    sraRgnReleaseIterator(iter);
		    sraRgnDestroy(whole);
		}

		dt = dtime(&tm);
if (db2) fprintf(stderr, "  stack_work dt: %.4f\n", dt);

		if (*obscured && !strcmp(wireframe_copyrect, "top")) {
			;	/* cannot send CopyRegion */
		} else if (! sraRgnEmpty(moved_win)) {
			sraRegionPtr whole, shifted_region;

			whole = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
			shifted_region = sraRgnCreateRgn(moved_win);
			sraRgnOffset(shifted_region, dx, dy);
			sraRgnAnd(shifted_region, whole);

			sraRgnDestroy(whole);

			/* now send the CopyRegion: */
			if (! sraRgnEmpty(shifted_region)) {
				dtime0(&tm);
				if (!nbatch) {
					do_copyregion(shifted_region, dx, dy, 0);
				} else {
					batch_dxs[*nbatch] = dx;
					batch_dys[*nbatch] = dy;
					batch_reg[*nbatch] = sraRgnCreateRgn(shifted_region);
					(*nbatch)++;
					
				}
				dt = dtime(&tm);
if (0 || db2) fprintf(stderr, "do_copyregion: %d %d %d %d  dx: %d  dy: %d dt: %.4f\n",
	tx1, ty1, tx2, ty2, dx, dy, dt);
				sent_copyrect = 1;
			}
			sraRgnDestroy(shifted_region);
		}
		sraRgnDestroy(moved_win);
	}
	return sent_copyrect;
}
		
int near_wm_edge(int x, int y, int w, int h, int px, int py) {
	/* heuristics: */
	int wf_t = wireframe_top;
	int wf_b = wireframe_bot;
	int wf_l = wireframe_left;
	int wf_r = wireframe_right;

	int near_edge = 0;
	
	if (wf_t || wf_b || wf_l || wf_r) {
		if (nabs(y - py) < wf_t) {
			near_edge = 1;
		}
		if (nabs(y + h - py) < wf_b) {
			near_edge = 1;
		}
		if (nabs(x - px) < wf_l) {
			near_edge = 1;
		}
		if (nabs(x + w - px) < wf_r) {
			near_edge = 1;
		}
	} else {
		/* all zero; always "near" edge: */
		near_edge = 1;
	}
	return near_edge;
}

int near_scrollbar_edge(int x, int y, int w, int h, int px, int py) {
	/* heuristics: */
	int sb_t = scrollcopyrect_top;
	int sb_b = scrollcopyrect_bot;
	int sb_l = scrollcopyrect_left;
	int sb_r = scrollcopyrect_right;

	int near_edge = 0;
	
	if (sb_t || sb_b || sb_l || sb_r) {
		if (nabs(y - py) < sb_t) {
			near_edge = 1;
		}
		if (nabs(y + h - py) < sb_b) {
			near_edge = 1;
		}
		if (nabs(x - px) < sb_l) {
			near_edge = 1;
		}
		if (nabs(x + w - px) < sb_r) {
			near_edge = 1;
		}
	} else {
		/* all zero; always "near" edge: */
		near_edge = 1;
	}
	return near_edge;
}

void check_fixscreen(void) {
	double now = dnow();
	int didfull = 0, db = 0;

	if (!client_count) {
		return;
	}
	if (unixpw_in_progress) return;

	if (screen_fixup_X > 0.0) {
		static double last = 0.0;
		if (now > last + screen_fixup_X) {
			if (db) rfbLog("doing screen_fixup_X\n");
			do_copy_screen = 1;
			last = now;
			didfull = 1;
		}
		
	}
	if (screen_fixup_V > 0.0) {
		static double last = 0.0;
		if (now > last + screen_fixup_V) {
			if (! didfull) {
				refresh_screen(0);
				if (db) rfbLog("doing screen_fixup_V\n");
			}
			last = now;
			didfull = 1;
		}
	}
	if (screen_fixup_C > 0.0) {
		static double last = 0.0;
		if (last_copyrect_fix < last_copyrect &&
		    now > last_copyrect + screen_fixup_C) {
			if (! didfull) {
				refresh_screen(0);
				if (db) rfbLog("doing screen_fixup_C\n");
			}
			last_copyrect_fix = now;
			last = now;
			didfull = 1;
		}
	}
	if (scaling && last_copyrect_fix < last_copyrect) {
		static double last = 0.0;
		double delay = 3.0;
		if (now > last + delay) {
			if (! didfull) {
				scale_and_mark_rect(0, 0, dpy_x, dpy_y, 1);
				if (db) rfbLog("doing scale screen_fixup\n");
			}
			last_copyrect_fix = now;
			last = now;
			didfull = 1;
		}
	}
}

static int wireframe_mod_state() {
	if (! wireframe_mods) {
		return 0;
	}
	if (!strcmp(wireframe_mods, "all")) {
		if (track_mod_state(NoSymbol, FALSE, FALSE)) {
			return 1;
		} else {
			return 0;
		}

	} else if (!strcmp(wireframe_mods, "Alt")) {
		if (track_mod_state(XK_Alt_L, FALSE, FALSE) == 1) {
			return 1;
		} else if (track_mod_state(XK_Alt_R, FALSE, FALSE) == 1) {
			return 1;
		}
	} else if (!strcmp(wireframe_mods, "Shift")) {
		if (track_mod_state(XK_Shift_L, FALSE, FALSE) == 1) {
			return 1;
		} else if (track_mod_state(XK_Shift_R, FALSE, FALSE) == 1) {
			return 1;
		}
	} else if (!strcmp(wireframe_mods, "Control")) {
		if (track_mod_state(XK_Control_L, FALSE, FALSE) == 1) {
			return 1;
		} else if (track_mod_state(XK_Control_R, FALSE, FALSE) == 1) {
			return 1;
		}
	} else if (!strcmp(wireframe_mods, "Meta")) {
		if (track_mod_state(XK_Meta_L, FALSE, FALSE) == 1) {
			return 1;
		} else if (track_mod_state(XK_Meta_R, FALSE, FALSE) == 1) {
			return 1;
		}
	} else if (!strcmp(wireframe_mods, "Super")) {
		if (track_mod_state(XK_Super_L, FALSE, FALSE) == 1) {
			return 1;
		} else if (track_mod_state(XK_Super_R, FALSE, FALSE) == 1) {
			return 1;
		}
	} else if (!strcmp(wireframe_mods, "Hyper")) {
		if (track_mod_state(XK_Hyper_L, FALSE, FALSE) == 1) {
			return 1;
		} else if (track_mod_state(XK_Hyper_R, FALSE, FALSE) == 1) {
			return 1;
		}
	}
	return 0;
}

static int NPP_nreg = 0;
static sraRegionPtr NPP_roffscreen = NULL;
static sraRegionPtr NPP_r_bs_tmp = NULL;
static Window NPP_nwin = None;

void clear_win_events(Window win, int vis) {
#if !NO_X11
	if (dpy && win != None && ncache) {
		XEvent ev;
		XErrorHandler old_handler;
		old_handler = XSetErrorHandler(trap_xerror);
		trapped_xerror = 0;
		while (XCheckTypedWindowEvent(dpy, win, ConfigureNotify, &ev)) {
			if (ncdb) fprintf(stderr, ".");
			if (trapped_xerror) {
				break;
			}
			trapped_xerror = 0;
		}
/* XXX Y */
		if (vis) {
			while (XCheckTypedWindowEvent(dpy, win, VisibilityNotify, &ev)) {
				if (ncdb) fprintf(stderr, "+");
				if (trapped_xerror) {
					break;
				}
				trapped_xerror = 0;
			}
		}
		XSetErrorHandler(old_handler);
		if (ncdb) fprintf(stderr, " 0x%lx\n", win);
	}
#endif
}

void push_borders(sraRect *rects, int nrect) {
		int i, s = 2;
		sraRegionPtr r0, r1, r2;

		r0 = sraRgnCreate(); 
		r1 = sraRgnCreateRect(0, 0, dpy_x, dpy_y); 

		for (i=0; i<nrect; i++) {
			int x = rects[i].x1;
			int y = rects[i].y1;
			int w = rects[i].x2;
			int h = rects[i].y2;

			if (w > 0 && h > 0 && w * h > 64 * 64) {
				r2 = sraRgnCreateRect(x - s, y , x , y + h); 
				sraRgnOr(r0, r2); 
				sraRgnDestroy(r2);
				
				r2 = sraRgnCreateRect(x + w, y , x + w + s, y + h); 
				sraRgnOr(r0, r2); 
				sraRgnDestroy(r2);

				r2 = sraRgnCreateRect(x - s, y - s, x + w + s, y + s); 
				sraRgnOr(r0, r2); 
				sraRgnDestroy(r2);
				
				r2 = sraRgnCreateRect(x - s, y , x + w + s, y + h + s); 
				sraRgnOr(r0, r2); 
				sraRgnDestroy(r2);
			}
		}

		sraRgnAnd(r0, r1); 

		if (!sraRgnEmpty(r0)) {
			double d = dnow();
			sraRectangleIterator *iter;
			sraRect rect;
			int db = 0;

			if (db) fprintf(stderr, "SCALE_BORDER\n");
			fb_push_wait(0.05, FB_MOD|FB_COPY);

			iter = sraRgnGetIterator(r0);
			while (sraRgnIteratorNext(iter, &rect)) {
				/* clip the window to the visible screen: */
				int tx1 = rect.x1;
				int ty1 = rect.y1;
				int tx2 = rect.x2;
				int ty2 = rect.y2;
				scale_and_mark_rect(tx1, ty1, tx2, ty2, 1);
			}
			sraRgnReleaseIterator(iter);

			if (db) fprintf(stderr, "SCALE_BORDER %.4f\n", dnow() - d);
			fb_push_wait(0.1, FB_MOD|FB_COPY);
			if (db) fprintf(stderr, "SCALE_BORDER %.4f\n", dnow() - d);
		}
		sraRgnDestroy(r0);
		sraRgnDestroy(r1);
}

void ncache_pre_portions(Window orig_frame, Window frame, int *nidx_in, int try_batch, int *use_batch,
    int orig_x, int orig_y, int orig_w, int orig_h, int x, int y, int w, int h, double ntim) {
	int nidx, np = ncache_pad;

	if (!ntim) {}
	*use_batch = 0;
	*nidx_in = -1;
	NPP_nreg = 0;
	NPP_roffscreen = NULL;
	NPP_r_bs_tmp = NULL;
	NPP_nwin = None;
	
	if (ncache <= 0) {
		return;
	}

	if (rotating) {
		try_batch = 0;
	}

	if (*nidx_in == -1) {
		nidx = lookup_win_index(orig_frame);
		NPP_nwin = orig_frame;
		if (nidx < 0) {
			nidx = lookup_win_index(frame);
			NPP_nwin = frame;
		}
	} else {
		nidx = *nidx_in;
	}
	if (nidx > 0) {
		sraRegionPtr r0, r1, r2;
		int dx, dy;
		int bs_x = cache_list[nidx].bs_x;	
		int bs_y = cache_list[nidx].bs_y;	
		int bs_w = cache_list[nidx].bs_w;	
		int bs_h = cache_list[nidx].bs_h;	

		*nidx_in = nidx;

		if (bs_x < 0) {
			if (!find_rect(nidx, x, y, w, h)) {
				nidx = -1;
				return;
			}
			bs_x = cache_list[nidx].bs_x;
			bs_y = cache_list[nidx].bs_y;
			bs_w = cache_list[nidx].bs_w;
			bs_h = cache_list[nidx].bs_h;
		}
		if (bs_x < 0) {
			nidx = -1;
			return;
		}

		if (try_batch) {
			*use_batch = 1;
		}

		if (ncache_pad) {
			orig_x -= np;	
			orig_y -= np;	
			orig_w += 2 * np;	
			orig_h += 2 * np;	
			x -= np;	
			y -= np;	
			w += 2 * np;	
			h += 2 * np;	
		}

		if (clipshift) {
			orig_x -= coff_x;
			orig_y -= coff_y;
			x -= coff_x;
			y -= coff_y;
		}

		r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y); 

		r2 = sraRgnCreateRect(orig_x, orig_y, orig_x + orig_w, orig_y + orig_h);
		sraRgnSubtract(r2, r0);
		if (! sraRgnEmpty(r2) && cache_list[nidx].bs_time > 0.0) {
			/* some is initially offscreen */
			dx = bs_x - orig_x;
			dy = bs_y - orig_y;
			sraRgnOffset(r2, dx, dy);
			dx = 0;
			dy = dpy_y;
			sraRgnOffset(r2, dx, dy);
if (ncdb) fprintf(stderr, "FB_COPY: %.4f 1) offscreen check:\n", dnow() - ntim);

			/* 0) save it in the invalid (offscreen) SU portion */
			if (! *use_batch) {
				do_copyregion(r2, dx, dy, 0);
				if (! fb_push_wait(0.2, FB_COPY)) {
					fb_push_wait(0.1, FB_COPY);
				}
			} else {
				batch_dxs[NPP_nreg] = dx;
				batch_dys[NPP_nreg] = dy;
				batch_reg[NPP_nreg++] = sraRgnCreateRgn(r2);
			}
			NPP_roffscreen = sraRgnCreateRgn(r2);
		}
		sraRgnDestroy(r2);

		/* 1) use bs for temp storage of the new save under. */
		r1 = sraRgnCreateRect(x, y, x + w, y + h);
		sraRgnAnd(r1, r0);

		dx = bs_x - x;
		dy = bs_y - y;
		sraRgnOffset(r1, dx, dy);

if (ncdb) fprintf(stderr, "FB_COPY: %.4f 1) use tmp bs:\n", dnow() - ntim);
		if (! *use_batch) {
			do_copyregion(r1, dx, dy, 0);
			if (! fb_push_wait(0.2, FB_COPY)) {
if (ncdb) fprintf(stderr, "FB_COPY: %.4f 1) FAILED.\n", dnow() - ntim);
				fb_push_wait(0.1, FB_COPY);
			}
		} else {
			batch_dxs[NPP_nreg] = dx;
			batch_dys[NPP_nreg] = dy;
			batch_reg[NPP_nreg++] = sraRgnCreateRgn(r1);
		}
		NPP_r_bs_tmp = sraRgnCreateRgn(r1);
		sraRgnDestroy(r0);
		sraRgnDestroy(r1);
	}
}

void ncache_post_portions(int nidx, int use_batch, int orig_x, int orig_y, int orig_w, int orig_h,
    int x, int y, int w, int h, double batch_delay, double ntim) {
	int np = ncache_pad;
	int db = 0;

	if (ncache > 0 && nidx >= 0) {
		sraRegionPtr r0, r1, r2, r3;
		int dx, dy;
		int su_x = cache_list[nidx].su_x;
		int su_y = cache_list[nidx].su_y;
		int su_w = cache_list[nidx].su_w;
		int su_h = cache_list[nidx].su_h;
		int bs_x = cache_list[nidx].bs_x;
		int bs_y = cache_list[nidx].bs_y;
		int bs_w = cache_list[nidx].bs_w;
		int bs_h = cache_list[nidx].bs_h;
		int some_su = 0;

if (db) fprintf(stderr, "su: %dx%d+%d+%d  bs: %dx%d+%d+%d\n", su_w, su_h, su_x, su_y, bs_w, bs_h, bs_x, bs_y);

		if (bs_x < 0) {
			if (!find_rect(nidx, x, y, w, h)) {
				return;
			}
			su_x = cache_list[nidx].su_x;
			su_y = cache_list[nidx].su_y;
			su_w = cache_list[nidx].su_w;
			su_h = cache_list[nidx].su_h;
			bs_x = cache_list[nidx].bs_x;
			bs_y = cache_list[nidx].bs_y;
			bs_w = cache_list[nidx].bs_w;
			bs_h = cache_list[nidx].bs_h;
		}
		if (bs_x < 0) {
			return;
		}

		if (ncache_pad) {
			orig_x -= np;	
			orig_y -= np;	
			orig_w += 2 * np;	
			orig_h += 2 * np;	
			x -= np;	
			y -= np;	
			w += 2 * np;	
			h += 2 * np;	
		}

		if (clipshift) {
			orig_x -= coff_x;
			orig_y -= coff_y;
			x -= coff_x;
			y -= coff_y;
		}

		r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y); 

		/* 0b) copy this bs part stored in saveunder */
		if (NPP_roffscreen != NULL) {
			dx = x - su_x;
			dy = y - su_y;
			sraRgnOffset(NPP_roffscreen, dx, dy);
			sraRgnAnd(NPP_roffscreen, r0);
			
			if (! use_batch) {
				do_copyregion(NPP_roffscreen, dx, dy, 0);
				if (!fb_push_wait(0.2, FB_COPY)) {
					fb_push_wait(0.1, FB_COPY);
				}
			} else {
				batch_dxs[NPP_nreg] = dx;
				batch_dys[NPP_nreg] = dy;
				batch_reg[NPP_nreg++] = sraRgnCreateRgn(NPP_roffscreen);
			}
			sraRgnDestroy(NPP_roffscreen);
		}

		/* 3) copy from the saveunder to where orig win was */
		r1 = sraRgnCreateRect(orig_x, orig_y, orig_x + orig_w, orig_y + orig_h);
		sraRgnAnd(r1, r0);
		r2 = sraRgnCreateRect(x+np, y+np, x + w-np, y + h-np);
		sraRgnAnd(r2, r0);
		sraRgnSubtract(r1, r2);

		dx = orig_x - su_x;
		dy = orig_y - su_y;
if (db && ncdb) fprintf(stderr, "FB_COPY: %.4f 3) sent_copyrect: su_restore: %d %d\n", dnow() - ntim, dx, dy);
		if (cache_list[nidx].su_time == 0.0) {
			;
		} else if (! use_batch) {
			do_copyregion(r1, dx, dy, 0);
			if (!fb_push_wait(0.2, FB_COPY)) {
if (db && ncdb) fprintf(stderr, "FB_COPY: %.4f 3) FAILED.\n", dnow() - ntim);
				fb_push_wait(0.1, FB_COPY);
			}
		} else {
			batch_dxs[NPP_nreg] = dx;
			batch_dys[NPP_nreg] = dy;
			batch_reg[NPP_nreg++] = sraRgnCreateRgn(r1);
		}
if (db && ncdb) fprintf(stderr, "sent_copyrect: %.4f su_restore: done.\n", dnow() - ntim);
		sraRgnDestroy(r0);
		sraRgnDestroy(r1);
		sraRgnDestroy(r2);

		/* 4) if overlap between orig and displaced, move the corner that will still be su: */
		r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y); 

		r1 = sraRgnCreateRect(orig_x, orig_y, orig_x + orig_w, orig_y + orig_h);
		sraRgnAnd(r1, r0);
		r2 = sraRgnCreateRect(x, y, x + w, y + h);
		sraRgnAnd(r2, r0);
		r3 = NULL;
		if (sraRgnAnd(r2, r1) && cache_list[nidx].su_time > 0.0) {
			int dx2 = su_x - orig_x;
			int dy2 = su_y - orig_y;

			r3 = sraRgnCreateRgn(r2);
			sraRgnOffset(r2, dx2, dy2); 

			dx = su_x - x;
			dy = su_y - y;
			sraRgnOffset(r3, dx, dy); 

			dx = dx - dx2;
			dy = dy - dy2;

if (db && ncdb) fprintf(stderr, "FB_COPY: %.4f 4) move overlap inside su:\n", dnow() - ntim);
			if (! use_batch) {
				do_copyregion(r3, dx, dy, 0);
				if (!fb_push_wait(0.2, FB_COPY)) {
if (db) fprintf(stderr, "FB_COPY: %.4f 4) FAILED.\n", dnow() - ntim);
					fb_push_wait(0.1, FB_COPY);
				}
			} else {
				batch_dxs[NPP_nreg] = dx;
				batch_dys[NPP_nreg] = dy;
				batch_reg[NPP_nreg++] = sraRgnCreateRgn(r3);
			}
		}
		sraRgnDestroy(r0);
		sraRgnDestroy(r1);
		sraRgnDestroy(r2);

		/* 5) copy our temporary stuff from bs to su: */
		dx = su_x - bs_x;
		dy = su_y - bs_y;
		if (NPP_r_bs_tmp == NULL) {
			r1 = sraRgnCreateRect(su_x, su_y, su_x + su_w, su_y + su_h); 
		} else {
			r1 = sraRgnCreateRgn(NPP_r_bs_tmp);
			sraRgnOffset(r1, dx, dy);
			sraRgnDestroy(NPP_r_bs_tmp);
		}
		if (r3 != NULL) {
			sraRgnSubtract(r1, r3);
			sraRgnDestroy(r3);
		}
if (db) fprintf(stderr, "FB_COPY: %.4f 5) move tmp bs to su:\n", dnow() - ntim);
		if (! use_batch) {
			do_copyregion(r1, dx, dy, 0);
			if (!fb_push_wait(0.2, FB_COPY)) {
if (db) fprintf(stderr, "FB_COPY: %.4f 5) FAILED.\n", dnow() - ntim);
				fb_push_wait(0.1, FB_COPY);
			}
		} else {
			batch_dxs[NPP_nreg] = dx;
			batch_dys[NPP_nreg] = dy;
			batch_reg[NPP_nreg++] = sraRgnCreateRgn(r1);
		}
		if (! sraRgnEmpty(r1)) {
			some_su = 1;
		}
		sraRgnDestroy(r1);

		/* 6) not really necessary, update bs with current view: */
		r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y); 
		r1 = sraRgnCreateRect(x, y, x + w, y + h);
		sraRgnAnd(r1, r0);
		dx = bs_x - x;
		dy = bs_y - y;
		sraRgnOffset(r1, dx, dy);
if (db) fprintf(stderr, "FB_COPY: %.4f 6) snapshot bs:\n", dnow() - ntim);
		if (! use_batch) {
			do_copyregion(r1, dx, dy, 0);
			if (!fb_push_wait(0.2, FB_COPY)) {
if (db) fprintf(stderr, "FB_COPY: %.4f 6) FAILED.\n", dnow() - ntim);
				fb_push_wait(0.1, FB_COPY);
			}
		} else {
			batch_dxs[NPP_nreg] = dx;
			batch_dys[NPP_nreg] = dy;
			batch_reg[NPP_nreg++] = sraRgnCreateRgn(r1);
		}
		sraRgnDestroy(r0);
		sraRgnDestroy(r1);

		if (use_batch) {
			batch_push(NPP_nreg, batch_delay);
if (ncdb) fprintf(stderr, "FB_COPY: %.4f XX did batch 0x%x %3d su: %dx%d+%d+%d  bs: %dx%d+%d+%d\n", dnow() - ntim,
	(unsigned int) cache_list[nidx].win, nidx, su_w, su_h, su_x, su_y, bs_w, bs_h, bs_x, bs_y);
		}
		cache_list[nidx].x = x + np;
		cache_list[nidx].y = y + np;

		/* XXX Y */
		cache_list[nidx].bs_time = dnow();
		if (some_su) {
			cache_list[nidx].su_time = dnow();
		}
	} else {
		if (use_batch) {
			batch_push(NPP_nreg, batch_delay);
		}
	}

	if (scaling) {
		sraRect rects[2];	

		rects[0].x1 = orig_x;
		rects[0].y1 = orig_y;
		rects[0].x2 = orig_w;
		rects[0].y2 = orig_h;

		rects[1].x1 = x;
		rects[1].y1 = y;
		rects[1].x2 = w;
		rects[1].y2 = h;
		push_borders(rects, 2);
	}
}

void do_copyrect_drag_move(Window orig_frame, Window frame, int *nidx, int try_batch,
    int now_x, int now_y, int orig_w, int orig_h, int x, int y, int w, int h, double batch_delay) {

	int sent_copyrect = 1, obscured = 0;
	int dx, dy;
	int use_batch = 0;
	double ntim = dnow();
	sraRegionPtr r0, r1;

	dx = x - now_x;
	dy = y - now_y;
	if (dx == 0 && dy == 0) {
		return;
	}
if (ncdb) fprintf(stderr, "do_COPY: now_xy: %d %d, orig_wh: %d %d, xy: %d %d, wh: %d %d\n",now_x, now_y, orig_w, orig_h, x, y, w, h);
	
	ncache_pre_portions(orig_frame, frame, nidx, try_batch, &use_batch,
	    now_x, now_y, orig_w, orig_h, x, y, w, h, ntim);

	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y); 
	r1 = sraRgnCreateRect(x, y, x + w, y + h);
	sraRgnAnd(r1, r0);

	dx = x - now_x;
	dy = y - now_y;
	if (! use_batch) {
		do_copyregion(r1, dx, dy, 0);
		if (!fb_push_wait(0.2, FB_COPY)) {
if (ncdb) fprintf(stderr, "FB_COPY: %.4f 3) *FAILED*\n", dnow() - ntim);
			fb_push_wait(0.1, FB_COPY);
		}
	} else {
		batch_dxs[NPP_nreg] = dx;
		batch_dys[NPP_nreg] = dy;
		batch_reg[NPP_nreg++] = sraRgnCreateRgn(r1);
	}
	sraRgnDestroy(r0);
	sraRgnDestroy(r1);

	if (sent_copyrect) {
		if (use_batch) {
			;
		} else if (! obscured) {
			fb_push_wait(0.1, FB_COPY);
		} else {
			/* no diff for now... */
			fb_push_wait(0.1, FB_COPY);
		}
		ncache_post_portions(*nidx, use_batch,
		    now_x, now_y, orig_w, orig_h, x, y, w, h, batch_delay, ntim);
	}
if (ncdb) fprintf(stderr, "do_COPY: %.4f -- post_portion done.\n", dnow() - ntim);
}

void check_macosx_iconify(Window orig_frame, Window frame, int flush) {
#ifdef MACOSX
	static double last = 0.0;
	double now;
	int j, m = 5, idx = -1, ok = 0, unmapped = 0;

	if (! macosx_console) {
		return;
	}

	now = dnow();
	if (now < last + 0.3) {
		return;
	}
	last = now;

	if (ncache > 0 && orig_frame != None) {
		idx = lookup_win_index(orig_frame);
		if (idx >= 0) {
			if (cache_list[idx].map_state == IsUnmapped) {
if (0) fprintf(stderr, "FAW orig_frame unmapped.\n");
				unmapped = 1;
				m = 3;
			}
		}
	}

	if (unmapped) {
		;
	} else if (orig_frame && macosxCGS_follow_animation_win(orig_frame, -1, 0)) {
		if (0) fprintf(stderr, "FAW orig_frame %d\n", (int) orig_frame);
	} else if (0 && frame && macosxCGS_follow_animation_win(frame, -1, 0)) {
		if (0) fprintf(stderr, "FAW frame      %d\n", (int) frame);
	}
	for (j=0; j<m; j++) {
		macosxCGS_get_all_windows();
		if (macosx_checkevent(NULL)) {
			ok = 1;
			if (0) fprintf(stderr, "Check Event    1\n");
		} else {
			if (0) fprintf(stderr, "Check Event    0\n");
		}
		if (ok) {
			break;
		}
		usleep(10 * 1000);
	}
	if (ok) {
		if (flush) {
			fb_push_wait(0.1, FB_COPY|FB_MOD);
		}
		check_ncache(0, 2);
	}
#else
	if (!orig_frame || !frame || !flush) {}
#endif
}

void check_macosx_click_frame(void) {
#ifdef MACOSX
	if (macosx_console) {
if (0) fprintf(stderr, "macosx_click_frame: 0x%x\n", macosx_click_frame);
		check_macosx_iconify(macosx_click_frame, None, 0);
		macosx_click_frame = None;
		if (button_mask && !macosx_checkevent(NULL)) {
			check_macosx_iconify(None, None, 0);
		}
	}
#endif
}

int clipped(int idx);
void snap_old(void);

int check_copyrect_raise(int idx, Window orig_frame, int try_batch) {
	char *no = "none";
	int doraise = 1;
	int valid;
	XWindowAttributes attr;

	if (! ncache_wf_raises) {
		doraise = 0;
		no = "ncache_wf_raises";
	} else if (cache_list[idx].bs_time == 0.0) {
		doraise = 0;
		no = "bs_time";
	} else if (0 && cache_list[idx].vis_state == VisibilityUnobscured) {
		doraise = 0;
		no = "VisibilityUnobscured";
	} else if (!clipped(idx)) {
		doraise = 0;
		no = "!clipped()";
	}
	if (doraise) {
		int nr = 0, *nb = NULL;
if (ncdb) fprintf(stderr, "--YES, wf_raise\n");
		if (try_batch) {
			nb = &nr;
		}
		valid = 1;
		bs_restore(idx, nb, NULL, &attr, 0, 1, &valid, 1);
		try_to_fix_su(orig_frame, idx, 0x1, nb, NULL);	
		if (nb && nr) {
			batch_push(nr, -1.0);
		}
		fb_push(); /* XXX Y */
	} else {
if (ncdb && no) fprintf(stderr, "--NO,  wf_raise: %s\n", no);
	}
	if (ncache_wf_raises) {
		snapshot_stack_list(0, 0.0);
		snap_old();
	}
	return 1;
}

int set_copyrect_drag(int idx, Window orig_frame, int try_batch) {
	if (idx < 0) {
		return 0;
	}
	if (cache_list[idx].su_time > 0.0) {
		check_copyrect_raise(idx, orig_frame, try_batch);
		return 1;
	}
	return 0;
}

/*
 * Applied just before any check_user_input() modes.  Look for a
 * ButtonPress; find window it happened in; find the wm frame window
 * for it; watch for that window moving or resizing.  If it does, do the
 * wireframe animation.  Do this until ButtonRelease or timeouts occur.
 * Remove wireframe.
 *
 * Under -nowirecopyrect, return control to base scheme
 * (check_user_input() ...) that will repaint the screen with the window
 * in the new postion or size.  Under -wirecopyrect, apply rfbDoCopyRect
 * or rfbDoCopyRegion: this "pollutes" our framebuffer, but the normal
 * polling will quickly repair it. Under happy circumstances, this
 * reduces actual XShmGetImage work (i.e. if we correctly predicted how
 * the X fb has changed.
 *
 * -scale doesn't always work under -wirecopyrect, but the wireframe does.
 *
 * testing of this mode under -threads is incomplete.
 *
 * returns 1 if it did an animation, 0 if no move/resize triggers
 * went off.
 *
 * TBD: see if we can select StructureNotify ConfigureNotify events for
 * the toplevel windows to get better info on moves and resizes.
 */
int check_wireframe(void) {
	Window frame = None, orig_frame = None;
	XWindowAttributes attr;
	int dx, dy;

	int orig_px, orig_py, orig_x, orig_y, orig_w, orig_h;
	int px, py, x, y, w, h;
	int box_x, box_y, box_w, box_h;
	int orig_cursor_x, orig_cursor_y, g, gd;
	int already_down = 0, win_gone = 0, win_unmapped = 0;
	double spin = 0.0, tm, last_ptr = 0.0, last_draw;

	int frame_changed = 0, drew_box = 0, got_2nd_pointer = 0;
	int try_copyrect_drag = 1, do_copyrect_drag = -1;
	int now_x = 0, now_y = 0, nidx = -1;
	double copyrect_drag_delay = -1.0;
	int try_batch = 1;	/* XXX Y */
	int mac_skip = 0;

	int special_t1 = 0, break_reason = 0, last_draw_cnt = 0, gpi = 0;
	static double first_dt_ave = 0.0;
	static int first_dt_cnt = 0;
	static time_t last_save_stacklist = 0;
	int bdown0, bdown, gotui, cnt = 0;
	
	/* heuristics: */
	double first_event_spin   = wireframe_t1;
	double frame_changed_spin = wireframe_t2;
	double max_spin = wireframe_t3;
	double min_draw = wireframe_t4;
	int try_it = 0;
	DB_SET

	if (unixpw_in_progress) return 0;
	if (copyrect_drag_delay) {}

#ifdef MACOSX
	if (macosx_console) {
		;
	} else {
		RAWFB_RET(0)
	}
#else
	RAWFB_RET(0)
#endif

	if (nofb) {
		return 0;
	}
	if (subwin) {
		return 0;	/* don't even bother for -id case */
	}

if (db > 1 && button_mask) fprintf(stderr, "check_wireframe: bm: %d  gpi: %d\n", button_mask, got_pointer_input);

	bdown0 = 0;
	if (button_mask) {
		bdown0 = 1;
	} else if (wireframe_local && display_button_mask) {
		bdown0 = 2;
	}
	if (! bdown0) {
		return 0;	/* no button pressed down */
	}

	gotui = 0;
	if (got_pointer_input) {
		gotui = 1;
	} else if (wireframe_local && display_button_mask) {
		gotui = 2;
	}
	if (!use_threads && !gotui) {
		return 0;	/* need ptr input, e.g. button down, motion */
	}

if (db > 1) fprintf(stderr, "check_wireframe: %d\n", db);

if (db) fprintf(stderr, "\n*** button down!!  x: %d  y: %d\n", cursor_x, cursor_y);

	/*
	 * Query where the pointer is and which child of the root
	 * window.  We will assume this is the frame the window manager
	 * makes when it reparents the toplevel window.
	 */
	X_LOCK;
	if (! get_wm_frame_pos(&px, &py, &x, &y, &w, &h, &frame, NULL)) {
if (db) fprintf(stderr, "NO get_wm_frame_pos-1: 0x%lx\n", frame);
		X_UNLOCK;
#ifdef MACOSX
		check_macosx_click_frame();
#endif
		return 0;
	}
	X_UNLOCK;

	last_get_wm_frame_time = dnow();
	last_get_wm_frame = frame;

if (db) fprintf(stderr, "a: %d  wf: %.3f  A: %d  origfrm: 0x%lx\n", w*h, wireframe_frac, (dpy_x*dpy_y), frame);

	/*
	 * apply the percentage size criterion (allow opaque moves for
	 * small windows)
	 */
	if ((double) w*h < wireframe_frac * (dpy_x * dpy_y)) {
if (db) fprintf(stderr, "small window %.3f\n", ((double) w*h)/(dpy_x * dpy_y));
		return 0;
	}
if (db) fprintf(stderr, "  frame: x: %d  y: %d  w: %d  h: %d  px: %d  py: %d  fr: 0x%lx\n", x, y, w, h, px, py, frame);	

	/*
	 * see if the pointer is within range of the assumed wm frame
	 * decorations on the edge of the window.
	 */

	try_it = near_wm_edge(x, y, w, h, px, py);

	/* Often Alt+ButtonDown starts a window move: */
	if (! try_it && wireframe_mod_state()) {
		try_it = 1;
	}
	if (try_it && clipshift) {
		sraRegionPtr r1, r2;
		int xc = off_x + coff_x;
		int yc = off_y + coff_y;
		r1 = sraRgnCreateRect(x, y, x+w, y+h);
		r2 = sraRgnCreateRect(xc, yc, xc+dpy_x, yc+dpy_y);
		if (!sraRgnAnd(r1, r2)) {
if (db) fprintf(stderr, "OUTSIDE CLIPSHIFT\n");
			try_it = 0;
		}
		sraRgnDestroy(r1);
		sraRgnDestroy(r2);
	}
	if (! try_it) {
if (db) fprintf(stderr, "INTERIOR\n");
#ifdef MACOSX
		check_macosx_click_frame();
#endif
		return 0;
	}

	wireframe_in_progress = 1;

	if (button_mask_prev) {
		already_down = 1;
	}
	
	if (! wireframe_str || !strcmp(wireframe_str, WIREFRAME_PARMS)) {
		int link, latency, netrate;
		static int didmsg = 0;

		link = link_rate(&latency, &netrate);
		if (link == LR_DIALUP || link == LR_BROADBAND) {
			/* slow link, e.g. dialup, increase timeouts: */
			first_event_spin   *= 2.0;
			frame_changed_spin *= 2.0;
			max_spin *= 2.0;
			min_draw *= 1.5;
			if (link == LR_DIALUP) {
				max_spin *= 1.2;
				min_draw *= 1.7;
			}
			if (! didmsg) {
				rfbLog("increased wireframe timeouts for "
				    "slow network connection.\n");
				rfbLog("netrate: %d KB/sec, latency: %d ms\n",
				    netrate, latency);
				didmsg = 1;
			}
		}
	}

	/*
	 * pointer() should have snapped the stacking list for us, if
	 * not, do it now (if the XFakeButtonEvent has been flushed by
	 * now the stacking order may be incorrect).
	 */
	if (strcmp(wireframe_copyrect, "never")) {
		if (already_down) {
			double age = 0.0;
			/*
			 * see if we can reuse the stack list (pause
			 * with button down)
			 */
			if (stack_list_num) {
				int k, got_me = 0;
				for (k = stack_list_num -1; k >=0; k--) {
					if (frame == stack_list[k].win) {
						got_me = 1;
						break;
					}
				}
				if (got_me) {
					age = 1.0;
				}
				snapshot_stack_list(0, age);
			}
		}
		if (! stack_list_num) {
			snapshot_stack_list(0, 0.0);
		}
	}


	/* store initial parameters, we look for changes in them */
	orig_frame = frame;
	orig_px = px;		/* pointer position */
	orig_py = py;
	orig_x = x;		/* frame position */
	orig_y = y;
	orig_w = w;		/* frame size */
	orig_h = h;

	orig_cursor_x = cursor_x;
	orig_cursor_y = cursor_y;

	/* this is the box frame we would draw */
	box_x = x;
	box_y = y; 
	box_w = w;
	box_h = h; 

	dtime0(&tm);

	last_draw = spin;

	/* -threads support for check_wireframe() is rough... crash? */
	if (use_threads) {
		/* purge any stored up pointer events: */
		pointer(-1, 0, 0, NULL);
	}

	if (cursor_noshape_updates_clients(screen)) {
		try_batch = 0;
	}
	if (rotating) {
		try_batch = 0;
	}

	g = got_pointer_input;
	gd = got_local_pointer_input;

	while (1) {

		X_LOCK;
		XFlush_wr(dpy);
		X_UNLOCK;

		/* try to induce/waitfor some more user input */
		if (use_threads) {
			usleep(1000);
		} else if (drew_box && do_copyrect_drag != 1) {
			rfbPE(1000);
		} else {
			rfbCFD(1000);
		}
		if (bdown0 == 2) {
			/*
			 * This is to just update display_button_mask
			 * which will also update got_local_pointer_input.
			 */
			check_x11_pointer();
#if 0
			/* what was this for? */
			Window frame;
			int px, py, x, y, w, h;
#ifdef MACOSX
			if (macosx_console) {
				macosx_get_cursor_pos(&x, &y);
			}
			else
#endif
			get_wm_frame_pos(&px, &py, &x, &y, &w, &h, &frame, NULL);
#endif
		}

		cnt++;
		spin += dtime(&tm);

if (0) fprintf(stderr, "wf-spin: %.3f\n", spin);

		/* check for any timeouts: */
		if (frame_changed) {
			double delay;
			/* max time we play this game: */
			if (spin > max_spin) {
if (db || db2) fprintf(stderr, " SPIN-OUT-MAX: %.3f\n", spin);
				break_reason = 1;
				break;
			}
			/* watch for pointer events slowing down: */
			if (special_t1) {
				delay = max_spin;
			} else {
				delay = 2.0* frame_changed_spin;
				if (spin > 3.0 * frame_changed_spin) {
					delay = 1.5 * delay;
				}
			}
			if (spin > last_ptr + delay) {
if (db || db2) fprintf(stderr, " SPIN-OUT-NOT-FAST: %.3f\n", spin);
				break_reason = 2;
				break;
			}
		} else if (got_2nd_pointer) {
			/*
			 * pointer is moving, max time we wait for wm
			 * move or resize to be detected
			 */
			if (spin > frame_changed_spin) {
if (db || db2) fprintf(stderr, " SPIN-OUT-NOFRAME-SPIN: %.3f\n", spin);
				break_reason = 3;
				break;
			}
		} else {
			/* max time we wait for any pointer input */
			if (spin > first_event_spin) {
if (db || db2) fprintf(stderr, " SPIN-OUT-NO2ND_PTR: %.3f\n", spin);
				break_reason = 4;
				break;
			}
		}

		gpi = 0;
		/* see if some pointer input occurred: */
		if (got_pointer_input > g ||
		    (wireframe_local && (got_local_pointer_input > gd))) {

if (db) fprintf(stderr, "  ++pointer event!! [%02d]  dt: %.3f  x: %d  y: %d  mask: %d\n",
    got_2nd_pointer+1, spin, cursor_x, cursor_y, button_mask);	

			g = got_pointer_input;
			gd = got_local_pointer_input;
			gpi = 1;

			X_LOCK;
			XFlush_wr(dpy);
			X_UNLOCK;

			/* periodically try to let the wm get moving: */
			if (!frame_changed && got_2nd_pointer % 4 == 0) {
				if (got_2nd_pointer == 0) {
					usleep(50 * 1000);
				} else {
					usleep(25 * 1000);
				}
			}
			got_2nd_pointer++;
			last_ptr = spin;

			/*
			 * see where the pointer currently is.  It may
			 * not be our starting frame (i.e. mouse now
			 * outside of the moving window).
			 */
			frame = 0x0;
			X_LOCK;

			if (! get_wm_frame_pos(&px, &py, &x, &y, &w, &h,
			    &frame, NULL)) {
				frame = 0x0;
if (db) fprintf(stderr, "NO get_wm_frame_pos-2: 0x%lx\n", frame);
			}

			if (frame != orig_frame) {
				/* see if our original frame is still there */
				if (!valid_window(orig_frame, &attr, 1)) {
					X_UNLOCK;
					/* our window frame went away! */
					win_gone = 1;
if (db) fprintf(stderr, "FRAME-GONE: 0x%lx\n", orig_frame);
					break_reason = 5;
					break;
				}
				if (attr.map_state == IsUnmapped) {
					X_UNLOCK;
					/* our window frame is now unmapped! */
					win_unmapped = 1;
if (db) fprintf(stderr, "FRAME-UNMAPPED: 0x%lx\n", orig_frame);
					break_reason = 5;
					break;
				}

if (db) fprintf(stderr, "OUT-OF-FRAME: old: x: %d  y: %d  px: %d py: %d 0x%lx\n", x, y, px, py, frame);

				/* new parameters for our frame */
				x = attr.x;	/* n.b. rootwin is parent */
				y = attr.y;
				w = attr.width;
				h = attr.height;
			}
			X_UNLOCK;

if (db) fprintf(stderr, "  frame: x: %d  y: %d  w: %d  h: %d  px: %d  py: %d  fr: 0x%lx\n", x, y, w, h, px, py, frame);	
if (db) fprintf(stderr, "        MO,PT,FR: %d/%d %d/%d %d/%d\n", cursor_x - orig_cursor_x, cursor_y - orig_cursor_y, px - orig_px, py - orig_py, x - orig_x, y - orig_y);	

			if (frame_changed && frame != orig_frame) {
if (db) fprintf(stderr, "CHANGED and window switch: 0x%lx\n", frame);
			}
			if (frame_changed && px - orig_px != x - orig_x) {
if (db) fprintf(stderr, "MOVED and diff DX\n");
			}
			if (frame_changed && py - orig_py != y - orig_y) {
if (db) fprintf(stderr, "MOVED and diff DY\n");
			}

			/* check and see if our frame has been resized: */
			if (!frame_changed && (w != orig_w || h != orig_h)) {
				int n;
				if (!already_down) {
					first_dt_ave += spin;
					first_dt_cnt++;
				}
				n = first_dt_cnt ? first_dt_cnt : 1;
				frame_changed = 2;

if (db) fprintf(stderr, "WIN RESIZE  1st-dt: %.3f\n", first_dt_ave/n);
			}

			/* check and see if our frame has been moved: */
			if (!frame_changed && (x != orig_x || y != orig_y)) {
				int n;
				if (!already_down) {
					first_dt_ave += spin;
					first_dt_cnt++;
				}
				n = first_dt_cnt ? first_dt_cnt : 1;
				frame_changed = 1;
if (db) fprintf(stderr, "FRAME MOVE  1st-dt: %.3f\n", first_dt_ave/n);
			}
		}

		/*
		 * see if it is time to draw any or a new wireframe box
		 */

		if (frame_changed) {
			int drawit = 0;
			if (x != box_x || y != box_y) {
				/* moved since last */
if (0) fprintf(stderr, "DRAW1 %d %d\n", x - box_x, y - box_y);
				drawit = 1;
			} else if (w != box_w || h != box_h) {
				/* resize since last */
				drawit = 1;
			}
			if (drawit) {
				int doit = 0;
				/*
				 * check time (to avoid too much
				 * animations on slow machines
				 * or links).
				 */
				if (gpi) {
					if (spin > last_draw + min_draw || ! drew_box) {
						doit = 1;
					}
					if (macosx_console && doit && !mac_skip) {
						if (x != box_x && y != box_y && w != box_w && h != box_h) {
							doit = 0;
						} else if (!button_mask) {
							doit = 0;
						}
						mac_skip++;
					}
				} else {
					if (drew_box && cnt > last_draw_cnt) 	{
						doit = 1;
if (0) fprintf(stderr, "*** NO GPI DRAW_BOX\n");
					}
				}
		
				if (doit) {
					if (try_copyrect_drag && ncache > 0) {
						if (!ncache_copyrect) {
							do_copyrect_drag = 0;
						} else if (w != box_w || h != box_h) {
							do_copyrect_drag = 0;
						} else if (do_copyrect_drag < 0) {
							Window fr = orig_frame;
							int idx = lookup_win_index(fr);
							if (idx < 0) {
								fr = frame;
								idx = lookup_win_index(fr);
							}
							if (idx >= 0) {
								do_copyrect_drag = set_copyrect_drag(idx, fr, try_batch);
								if (do_copyrect_drag) {
									min_draw *= 0.66;
								}
								nidx = idx;
							} else {
								do_copyrect_drag = 0;
							}
							now_x = orig_x;
							now_y = orig_y;
						}
						if (do_copyrect_drag) {
							if (orig_w != w || orig_h != h) {
								do_copyrect_drag = 0;
							}
						}
					}

					if (do_copyrect_drag <= 0) {
						if (ncache <= 0) {
							;
						} else if (!drew_box && ncache_wf_raises) {
							Window fr = orig_frame;
							int idx = lookup_win_index(fr);
							if (idx < 0) {
								fr = frame;
								idx = lookup_win_index(fr);
							}
							if (idx >= 0) {
								check_copyrect_raise(idx, fr, try_batch);
							}
						}
						draw_box(x, y, w, h, 0);
						fb_push(); /* XXX Y */
						rfbPE(1000);
					} else {
#ifndef NO_NCACHE
						do_copyrect_drag_move(orig_frame, frame, &nidx,
						    try_batch, now_x, now_y, orig_w, orig_h, x, y, w, h,
						    copyrect_drag_delay);
						now_x = x;
						now_y = y;
						if (copyrect_drag_delay == -1.0) {
							copyrect_drag_delay = 0.04;
						}
#endif
					}
					drew_box = 1;
					last_wireframe = dnow();

					last_draw = spin;
					last_draw_cnt = cnt;
				}
			}
			box_x = x;
			box_y = y;
			box_w = w;
			box_h = h;
		}

		/* 
		 * Now (not earlier) check if the button has come back up.
		 * we check here to get a better location and size of
		 * the final window.
		 */
		bdown = 0;
		if (button_mask) {
			bdown = 1;
		} else if (wireframe_local && display_button_mask) {
			bdown = 2;
		}
		if (! bdown) {
if (db || db2) fprintf(stderr, "NO button_mask\n");
			break_reason = 6;
			break;	
		}
	}

	if (! drew_box) {
		/* nice try, but no move or resize detected.  cleanup. */
		if (stack_list_num) {
			stack_list_num = 0;
		}
		wireframe_in_progress = 0;
		if (macosx_console && (break_reason == 6 || break_reason == 5)) {
			check_macosx_iconify(orig_frame, frame, drew_box);
		}
		return 0;
	}

	/* remove the wireframe */
	if (do_copyrect_drag <= 0) {
		draw_box(0, 0, 0, 0, 1);
		fb_push(); /* XXX Y */
	} else {
		do_copyrect_drag_move(orig_frame, frame, &nidx,
		    try_batch, now_x, now_y, orig_w, orig_h, x, y, w, h, -1.0);
		fb_push_wait(0.15, FB_COPY|FB_MOD);
	}

	dx = x - orig_x;
	dy = y - orig_y;

	/*
	 * see if we can apply CopyRect or CopyRegion to the change:
	 */
	if (!strcmp(wireframe_copyrect, "never")) {
		;
	} else if (win_gone || win_unmapped) {
		;
	} else if (skip_cr_when_scaling("wireframe")) {
		;
	} else if (w != orig_w || h != orig_h) {
		if (ncache > 0) {
			try_to_fix_resize_su(orig_frame, orig_x, orig_y, orig_w, orig_h, x, y, w, h, try_batch);
			clear_win_events(orig_frame, 1);
			if (frame != orig_frame) {
				clear_win_events(frame, 1);
			}
		}
	} else if (dx == 0 && dy == 0) {
		;
	} else if (do_copyrect_drag > 0) {
		clear_win_events(NPP_nwin, 0);
	} else {
		int spin_ms = (int) (spin * 1000 * 1000);
		int obscured, sent_copyrect = 0;

		int nidx = -1;
		int use_batch = 0;
		double ntim;

		/*
		 * set a timescale comparable to the spin time,
		 * but not too short or too long.
		 */
		if (spin_ms < 30) {
			spin_ms = 30;
		} else if (spin_ms > 400) {
			spin_ms = 400;
		}
		ntim = dnow();

		/* try to flush the wireframe removal: */
if (ncdb && ncache) fprintf(stderr, "\nSEND_COPYRECT  %.4f %.4f\n", dnowx(), dnow() - ntim);

		if (! fb_push_wait(0.15, FB_COPY|FB_MOD)) {

if (ncdb && ncache) fprintf(stderr, "FB_COPY *FAILED*, try one more... %.4f", dnow() - ntim);

			if (! fb_push_wait(0.15, FB_COPY|FB_MOD)) {

if (ncdb && ncache) fprintf(stderr, "FB_COPY *FAILED* again! %.4f", dnow() - ntim);

			}
		}

		ncache_pre_portions(orig_frame, frame, &nidx, try_batch, &use_batch,
		    orig_x, orig_y, orig_w, orig_h, x, y, w, h, ntim);

		/* 2) try to send a clipped copyrect of translation: */

		if (! try_batch) {
			sent_copyrect = try_copyrect(orig_frame, frame, x, y, w, h, dx, dy,
			    &obscured, NULL, 0.15, NULL);
		} else {
			try_copyrect(orig_frame, frame, x, y, w, h, dx, dy,
			    &obscured, NULL, 0.15, &NPP_nreg);	/* XXX */
			sent_copyrect = 1;
			use_batch = 1;
		}

if ((ncache || db) && ncdb) fprintf(stderr, "sent_copyrect: %d - obs: %d  frame: 0x%lx\n", sent_copyrect, obscured, frame);
		if (sent_copyrect) {
			/* try to push the changes to viewers: */
			if (use_batch) {
				;
			} else if (! obscured) {
				fb_push_wait(0.1, FB_COPY);
			} else {
				/* no diff for now... */
				fb_push_wait(0.1, FB_COPY);
			}
			ncache_post_portions(nidx, use_batch,
			    orig_x, orig_y, orig_w, orig_h, x, y, w, h, -1.0, ntim);
			clear_win_events(NPP_nwin, 0);

			if (scaling && !use_batch) {
				static double last_time = 0.0;
				double now = dnow(), delay = 0.35;

				fb_push_wait(0.1, FB_COPY);

				if (now > last_time + delay) {
					int xt = x, yt = y;

					if (clipshift) {
						xt -= coff_x;
						yt -= coff_y;
					}
					if (subwin) {
						xt -= off_x;
						yt -= off_y;
					}

					scale_mark(xt, yt, xt+w, yt+h, 1);
					last_time = now;
					last_copyrect_fix = now;
				}
			}
		}
	}

	if (stack_list_num) {
		/* clean up stack_list for next time: */
		if (break_reason == 1 || break_reason == 2) {
			/*
			 * save the stack list, perhaps the user has
			 * paused with button down.
			 */
			last_save_stacklist = time(NULL);
		} else {
			stack_list_num = 0;
		}
	}

	/* final push (for -nowirecopyrect) */
	rfbPE(1000);
	wireframe_in_progress = 0;

	if (0) {
	/* No longer needed.  see draw_box() */
	    if (frame_changed && cmap8to24 && multivis_count) {
		/* handle -8to24 kludge, mark area and check 8bpp... */
		int x1, x2, y1, y2, f = 16;
		x1 = nmin(box_x, orig_x) - f;
		y1 = nmin(box_y, orig_y) - f;
		x2 = nmax(box_x + box_w, orig_x + orig_w) + f;
		y2 = nmax(box_y + box_h, orig_y + orig_h) + f;
		x1 = nfix(x1, dpy_x);
		x2 = nfix(x2, dpy_x+1);
		y1 = nfix(y1, dpy_y);
		y2 = nfix(y2, dpy_y+1);
		check_for_multivis();
		mark_rect_as_modified(x1, y1, x2, y2, 0);
	    }
	}

	urgent_update = 1;
	if (use_xdamage) {
		/* DAMAGE can queue ~1000 rectangles for a move */
		clear_xdamage_mark_region(NULL, 1);
		xdamage_scheduled_mark = dnow() + 2.0;
	}

	if (macosx_console && (break_reason == 6 || break_reason == 5)) {
		check_macosx_iconify(orig_frame, frame, drew_box);
	}

	return 1;
}

/*
 * We need to handle user input, particularly pointer input, carefully.
 * This function is only called when non-threaded.  Note that
 * rfbProcessEvents() only processes *one* pointer event per call,
 * so if we interlace it with scan_for_updates(), we can get swamped
 * with queued up pointer inputs.  And if the pointer inputs are inducing
 * large changes on the screen (e.g. window drags), the whole thing
 * bogs down miserably and only comes back to life at some time after
 * one stops moving the mouse.  So, to first approximation, we are trying
 * to eat as much user input here as we can using some hints from the
 * duration of the previous scan_for_updates() call (in dt).
 *
 * note: we do this even under -nofb
 *
 * return of 1 means watch_loop should short-circuit and reloop,
 * return of 0 means watch_loop should proceed to scan_for_updates().
 * (this is for pointer_mode == 1 mode, the others do it all internally,
 * cnt is also only for that mode).
 */

static void check_user_input2(double dt) {

	int eaten = 0, miss = 0, max_eat = 50, do_flush = 1;
	int g, g_in;
	double spin = 0.0, tm;
	double quick_spin_fac  = 0.40;
	double grind_spin_time = 0.175;

	dtime0(&tm);
	g = g_in = got_pointer_input;
	if (!got_pointer_input) {
		return;
	}
	/*
	 * Try for some "quick" pointer input processing.
	 *
	 * About as fast as we can, we try to process user input calling
	 * rfbProcessEvents or rfbCheckFds.  We do this for a time on
	 * order of the last scan_for_updates() time, dt, but if we stop
	 * getting user input we break out.  We will also break out if
	 * we have processed max_eat inputs.
	 *
	 * Note that rfbCheckFds() does not send any framebuffer updates,
	 * so is more what we want here, although it is likely they have
	 * all be sent already.
	 */
	while (1) {
		if (show_multiple_cursors) {
			rfbPE(1000);
		} else {
			rfbCFD(1000);
		}
		rfbCFD(0);

		spin += dtime(&tm);

		if (spin > quick_spin_fac * dt) {
			/* get out if spin time comparable to last scan time */
			break;
		}
		if (got_pointer_input > g) {
			int i, max_extra = max_eat / 2;
			g = got_pointer_input;
			eaten++;
			for (i=0; i<max_extra; i++)  {
				rfbCFD(0);
				if (got_pointer_input > g) {
					g = got_pointer_input;
					eaten++;
				} else if (i > 1) {
					break;
				}
			}
			X_LOCK;
			do_flush = 0;
if (0) fprintf(stderr, "check_user_input2-A: XFlush %.4f\n", tm);
			XFlush_wr(dpy);
			X_UNLOCK;
			if (eaten < max_eat) {
				continue;
			}
		} else {
			miss++;
		}
		if (miss > 1) {	/* 1 means out on 2nd miss */
			break;
		}
	}
	if (do_flush) {
		X_LOCK;
if (0) fprintf(stderr, "check_user_input2-B: XFlush %.4f\n", tm);
		XFlush_wr(dpy);
		X_UNLOCK;
	}


	/*
	 * Probably grinding with a lot of fb I/O if dt is this large.
	 * (need to do this more elegantly)
	 *
	 * Current idea is to spin our wheels here *not* processing any
	 * fb I/O, but still processing the user input.  This user input
	 * goes to the X display and changes it, but we don't poll it
	 * while we "rest" here for a time on order of dt, the previous
	 * scan_for_updates() time.  We also break out if we miss enough
	 * user input.
	 */
	if (dt > grind_spin_time) {
		int i, ms, split = 30;
		double shim;

		/*
		 * Break up our pause into 'split' steps.  We get at
		 * most one input per step.
		 */
		shim = 0.75 * dt / split;

		ms = (int) (1000 * shim);

		/* cutoff how long the pause can be */
		if (split * ms > 300) {
			ms = 300 / split;
		}

		spin = 0.0;
		dtime0(&tm);

		g = got_pointer_input;
		miss = 0;
		for (i=0; i<split; i++) {
			usleep(ms * 1000);
			if (show_multiple_cursors) {
				rfbPE(1000);
			} else {
				rfbCFD(1000);
			}
			spin += dtime(&tm);
			if (got_pointer_input > g) {
				int i, max_extra = max_eat / 2;
				for (i=0; i<max_extra; i++)  {
					rfbCFD(0);
					if (got_pointer_input > g) {
						g = got_pointer_input;
					} else if (i > 1) {
						break;
					}
				}
				X_LOCK;
if (0) fprintf(stderr, "check_user_input2-C: XFlush %.4f\n", tm);
				XFlush_wr(dpy);
				X_UNLOCK;
				miss = 0;
			} else {
				miss++;
			}
			g = got_pointer_input;
			if (miss > 2) {
				break;
			}
			if (1000 * spin > ms * split)  {
				break;
			}
		}
	}
}

static void check_user_input3(double dt, double dtr, int tile_diffs) {

	int allowed_misses, miss_tweak, i, g, g_in;
	int last_was_miss, consecutive_misses;
	double spin, spin_max, tm, to, dtm;
	int rfb_wait_ms = 2;
	static double dt_cut = 0.075;
	int gcnt, ginput;
	static int first = 1;

	if (dtr || tile_diffs) {} /* unused vars warning: */

	if (first) {
		char *p = getenv("SPIN");
		if (p) {
			double junk;
			sscanf(p, "%lf,%lf", &dt_cut, &junk);
		}
		first = 0;
	}

	if (!got_pointer_input) {
		return;
	}


	if (dt < dt_cut) {
		dt = dt_cut;	/* this is to try to avoid early exit */
	}
	spin_max = 0.5;

	spin = 0.0;		/* amount of time spinning */
	allowed_misses = 10;	/* number of ptr inputs we can miss */
	miss_tweak = 8;
	last_was_miss = 0;
	consecutive_misses = 1;
	gcnt = 0;
	ginput = 0;

	dtime0(&tm);
	to = tm;	/* last time we did rfbPE() */

	g = g_in = got_pointer_input;

	while (1) {
		int got_input = 0;

		gcnt++;

		if (button_mask) {
			drag_in_progress = 1;
		}

		rfbCFD(rfb_wait_ms * 1000);

		dtm = dtime(&tm);
		spin += dtm;

		if (got_pointer_input == g) {
			if (last_was_miss) {
				consecutive_misses++;
			}
			last_was_miss = 1;
		} else {
			ginput++;
			if (ginput % miss_tweak == 0) {
				allowed_misses++;
			}
			consecutive_misses = 1;
			last_was_miss = 0;
		}

		if (spin > spin_max) {
			/* get out if spin time over limit */
			break;

		} else if (got_pointer_input > g) {
			/* received some input, flush to display. */
			got_input = 1;
			g = got_pointer_input;
			X_LOCK;
			XFlush_wr(dpy);
			X_UNLOCK;
		} else if (--allowed_misses <= 0) {
			/* too many misses */
			break;
		} else if (consecutive_misses >=3) {
			/* too many misses */
			break;
		} else {
			/* these are misses */
			int wms = 0;
			if (gcnt == 1 && button_mask) {
				/*
				 * missed our first input, wait
				 * for a defer time. (e.g. on
				 * slow link) hopefully client
				 * will batch them.
				 */
				wms = 50;
			} else if (button_mask) {
				wms = 10;
			} else {
			}
			if (wms) {
				usleep(wms * 1000);
			}
		}
	}

	if (ginput >= 2) {
		/* try for a couple more quick ones */
		for (i=0; i<2; i++) {
			rfbCFD(rfb_wait_ms * 1000);
		}
	}

	drag_in_progress = 0;
}

int fb_update_sent(int *count) {
	static int last_count = 0;
	int sent = 0, rc = 0;
	rfbClientIteratorPtr i;
	rfbClientPtr cl;

	if (nofb) {
		return 0;
	}

	i = rfbGetClientIterator(screen);
	while( (cl = rfbClientIteratorNext(i)) ) {
#if 0
		sent += cl->framebufferUpdateMessagesSent;
#else
		sent += rfbStatGetMessageCountSent(cl, rfbFramebufferUpdate);
#endif
	}
	rfbReleaseClientIterator(i);
	if (sent != last_count) {
		rc = 1;
	}
	if (count != NULL) {
		*count = sent;
	}
	last_count = sent;
	return rc; 
}

static void check_user_input4(double dt, double dtr, int tile_diffs) {

	int g, g_in, i, ginput, gcnt, tmp;
	int last_was_miss, consecutive_misses;
	int min_frame_size = 10;	/* 10 tiles */
	double spin, tm, to, tc, dtm, rpe_last;
	int rfb_wait_ms = 2;
	static double dt_cut = 0.050;
	static int first = 1;

	int Btile = tile_x * tile_y * bpp/8; 	/* Bytes per tile */
	double Ttile, dt_use;
	double screen_rate = 6000000.;    /* 5 MB/sec */
	double vnccpu_rate = 80 * 100000.; /* 20 KB/sec @ 80X compression */
	double net_rate = 50000.;
	static double Tfac_r = 1.0, Tfac_v = 1.0, Tfac_n = 1.0, Tdelay = 0.001;
	static double dt_min = -1.0, dt_max = -1.0;
	double dt_min_fallback = 0.050;
	static int ssec = 0, total_calls = 0;
	static int push_frame = 0, update_count = 0;

	if (first) {
		char *p = getenv("SPIN");
		if (p) {
			sscanf(p, "%lf,%lf,%lf,%lf", &dt_cut, &Tfac_r, &Tfac_v, &Tfac_n);
		}
		first = 0;
		ssec = time(NULL);

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

	total_calls++;

	if (dt_min < 0.0 || dt < dt_min) {
		if (dt > 0.0) {
			dt_min = dt;
		}
	}
	if (dt_min < 0.0) {
		/* sensible value for the very 1st call if dt = 0.0 */
		dt_min = dt_min_fallback;
	}
	if (dt_max < 0.0 || dt > dt_max) {
		dt_max = dt;
	}

	if (total_calls > 30 && dt_min > 0.0) {
		static int first = 1;
		/*
		 * dt_min will soon be the quickest time to do
		 * one scan_for_updates with no tiles copied.
		 * use this (instead of copy_tiles) to estimate
		 * screen read rate.
		 */
		screen_rate = (main_bytes_per_line * ntiles_y) / dt_min;
		if (first) {
			rfbLog("measured screen read rate: %.2f Bytes/sec\n",
			    screen_rate);
		}
		first = 0;
	}

	dtime0(&tm);

	if (dt < dt_cut) {
		dt_use = dt_cut;
	} else {
		dt_use = dt;
	}

	if (push_frame) {
		int cnt, iter = 0;
		double tp, push_spin = 0.0;
		dtime0(&tp);
		while (push_spin < dt_use * 0.5) {
			fb_update_sent(&cnt);
			if (cnt != update_count) {
				break;
			}
			/* damn, they didn't push our frame! */
			iter++;
			rfbPE(rfb_wait_ms * 1000);
			
			push_spin += dtime(&tp);
		}
		if (iter) {
			X_LOCK;
			XFlush_wr(dpy);
			X_UNLOCK;
		}
		push_frame = 0;
		update_count = 0;
	}

	/*
	 * when we first enter we require some pointer input
	 */
	if (!got_pointer_input) {
		return;
	}

	vnccpu_rate = get_raw_rate();

	if ((tmp = get_read_rate()) != 0) {
		screen_rate = (double) tmp;
	}
	if ((tmp = get_net_rate()) != 0) {
		net_rate = (double) tmp;
	}
	net_rate = (vnccpu_rate/get_cmp_rate()) * net_rate;

	if ((tmp = get_net_latency()) != 0) {
		Tdelay = 0.5 * ((double) tmp)/1000.;
	}

	Ttile = Btile * (Tfac_r/screen_rate + Tfac_v/vnccpu_rate + Tfac_n/net_rate);

	spin = 0.0;		/* amount of time spinning */
	last_was_miss = 0;
	consecutive_misses = 1;
	gcnt = 0;
	ginput = 0;

	rpe_last = to = tc = tm;	/* last time we did rfbPE() */
	g = g_in = got_pointer_input;

	tile_diffs = 0;	/* reset our knowlegde of tile_diffs to zero */

	while (1) {
		int got_input = 0;

		gcnt++;

		if (button_mask) {
			/* this varible is used by our pointer handler */
			drag_in_progress = 1;
		}

		/* turn libvncserver crank to process events: */
		rfbCFD(rfb_wait_ms * 1000);

		dtm = dtime(&tm);
		spin += dtm;

		if ( (gcnt == 1 && got_pointer_input > g) || tm-tc > 2*dt_min) {
			tile_diffs = scan_for_updates(1);
			tc = tm;
		}

		if (got_pointer_input == g) {
			if (last_was_miss) {
				consecutive_misses++;
			}
			last_was_miss = 1;
		} else {
			ginput++;
			consecutive_misses = 1;
			last_was_miss = 0;
		}

		if (tile_diffs > min_frame_size && spin > Ttile * tile_diffs + Tdelay) {
			/* we think we can push the frame */
			push_frame = 1;
			fb_update_sent(&update_count);
			break;

		} else if (got_pointer_input > g) {
			/* received some input, flush it to display. */
			got_input = 1;
			g = got_pointer_input;
			X_LOCK;
			XFlush_wr(dpy);
			X_UNLOCK;

		} else if (consecutive_misses >= 2) {
			/* too many misses in a row */
			break;

		} else {
			/* these are pointer input misses */
			int wms;
			if (gcnt == 1 && button_mask) {
				/*
				 * missed our first input, wait for
				 * a defer time. (e.g. on slow link)
				 * hopefully client will batch many
				 * of them for the next read.
				 */
				wms = 50;

			} else if (button_mask) {
				wms = 10;
			} else {
				wms = 0;
			}
			if (wms) {
				usleep(wms * 1000);
			}
		}
	}
	if (ginput >= 2) {
		/* try for a couple more quick ones */
		for (i=0; i<2; i++) {
			rfbCFD(rfb_wait_ms * 1000);
		}
	}
	drag_in_progress = 0;
}

int check_user_input(double dt, double dtr, int tile_diffs, int *cnt) {

	if (rawfb_vnc_reflect) {
		if (got_user_input) {
			if (0) vnc_reflect_process_client();
		}
		if (got_user_input && *cnt % ui_skip != 0) {
			/* every n-th drops thru to scan */
			*cnt = *cnt + 1;
			return 1;	/* short circuit watch_loop */
		}
	}
#ifdef MACOSX
	if (! macosx_console) {
		RAWFB_RET(0)
	}
#else
	RAWFB_RET(0)
#endif

	if (use_xrecord) {
		int rc = check_xrecord();
		/*
		 * 0: nothing found, proceed to other user input schemes.
		 * 1: events found, want to do a screen update now.
		 * 2: events found, want to loop back for some more.
		 * 3: events found, want to loop back for some more,
		 *    and not have rfbPE() called.
		 *
		 * For 0, we precede below, otherwise return rc-1.
		 */
if (debug_scroll && rc > 1) fprintf(stderr, "  CXR: check_user_input ret %d\n", rc - 1);
		if (rc == 0) {
			;	/* proceed below. */
		} else {
			return rc - 1;
		}
	}

	if (wireframe) {
		if (check_wireframe()) {
			return 0;
		}
	}

	if (pointer_mode == 1) {
		if ((got_user_input || ui_skip < 0) && *cnt % ui_skip != 0) {
			/* every ui_skip-th drops thru to scan */
			*cnt = *cnt + 1;
			X_LOCK;
			XFlush_wr(dpy);
			X_UNLOCK;
			return 1;	/* short circuit watch_loop */
		} else {
			return 0;
		}
	}
	if (pointer_mode >= 2 && pointer_mode <= 4) {
		if (got_keyboard_input) {
			/*
			 * for these modes, short circuit watch_loop on
			 * *keyboard* input.
			 */
			if (*cnt % ui_skip != 0) {
				*cnt = *cnt + 1; 
				return 1;
			}
		}
		/* otherwise continue below with pointer input method */
	}

	if (pointer_mode == 2) {
		check_user_input2(dt);
	} else if (pointer_mode == 3) {
		check_user_input3(dt, dtr, tile_diffs);
	} else if (pointer_mode == 4) {
		check_user_input4(dt, dtr, tile_diffs);
	}
	return 0;
}

#if defined(NO_NCACHE) || (NO_X11 && !defined(MACOSX))
int check_ncache(int a, int b) {
	if (!a || !b) {}
	ncache = 0;
	return 0;
}
int lookup_win_index(Window win) {
	if (!win) {}
	return -1;
}
int find_rect(int idx, int x, int y, int w, int h) {
	if (!idx || !x || !y || !w || !h) {}
	return 0;
}
void snap_old(void) {
	return;
}
int clipped(int idx) {
	if (!idx) {}
	return 0;
}
int bs_restore(int idx, int *nbatch, sraRegionPtr rmask, XWindowAttributes *attr, int clip, int nopad, int *valid, int verb) {
	if (!idx || !nbatch || !rmask || !attr || !clip || !nopad || !valid || !verb) {}
	return 0;
}
int try_to_fix_su(Window win, int idx, Window above, int *nbatch, char *mode) {
	if (!win || !idx || !above || !nbatch || !mode) {}
	return 0;
}
int try_to_fix_resize_su(Window orig_frame, int orig_x, int orig_y, int orig_w, int orig_h,
    int x, int y, int w, int h, int try_batch) {
	if (!orig_frame || !orig_x || !orig_y || !orig_w || !orig_h || !x || !y || !w || !h || !try_batch) {}
	return 0;
}
void set_ncache_xrootpmap(void) {
	return;
}
#else
/* maybe ncache.c it if works */

winattr_t* cache_list = NULL;
int cache_list_num = 0;
int cache_list_len = 0;

void snapshot_cache_list(int free_only, double allowed_age) {
	static double last_snap = 0.0, last_free = 0.0;
	double now; 
	int num, rc, i;
	unsigned int ui;
	Window r, w;
	Window *list;
	int start = 512;

	if (! cache_list) {
		cache_list = (winattr_t *) calloc(start*sizeof(winattr_t), 1);
		cache_list_num = 0;
		cache_list_len = start;
	}

	dtime0(&now);
	if (free_only) {
		/* we really don't free it, just reset to zero windows */
		cache_list_num = 0;
		last_free = now;
		return;
	}

	if (cache_list_num && now < last_snap + allowed_age) {
		return;
	}

	cache_list_num = 0;
	last_free = now;

#ifdef MACOSX
	if (! macosx_console) {
		RAWFB_RET_VOID
	}
#else
	RAWFB_RET_VOID
#endif


#if NO_X11 && !defined(MACOSX)
	num = rc = i = 0;	/* compiler warnings */
	ui = 0;
	r = w = None;
	list = NULL;
	return;
#else

	X_LOCK;
	/* no need to trap error since rootwin */
	rc = XQueryTree_wr(dpy, rootwin, &r, &w, &list, &ui);
	X_UNLOCK;
	num = (int) ui;

	if (! rc) {
		cache_list_num = 0;
		last_free = now;
		last_snap = 0.0;
		return;
	}

	last_snap = now;
	if (num > cache_list_len) {
		int n = 2*num;
		n = num + 3;
		free(cache_list);
		cache_list = (winattr_t *) calloc(n*sizeof(winattr_t), 1);
		cache_list_len = n;
	}
	for (i=0; i<num; i++) {
		cache_list[i].win = list[i];
		cache_list[i].fetched = 0;
		cache_list[i].valid = 0;
		cache_list[i].time = now;
		cache_list[i].selectinput = 0;
		cache_list[i].vis_cnt = 0;
		cache_list[i].map_cnt = 0;
		cache_list[i].unmap_cnt = 0;
		cache_list[i].create_cnt = 0;
		cache_list[i].vis_state = -1;
		cache_list[i].above = None;
	}
	if (num == 0) {
		cache_list[0].win = None;
		cache_list[0].fetched = 0;
		cache_list[0].valid = 0;
		cache_list[0].time = now;
		cache_list[0].selectinput = 0;
		cache_list[0].vis_cnt = 0;
		cache_list[0].map_cnt = 0;
		cache_list[0].unmap_cnt = 0;
		cache_list[0].create_cnt = 0;
		cache_list[0].vis_state = -1;
		cache_list[0].above = None;
		num++;
	}

	cache_list_num = num;

	if (num) {
		X_LOCK;
		XFree_wr(list);
		X_UNLOCK;
	}
#endif	/* NO_X11 */
}

void quick_snap(Window *wins, int *size) {
	int num, rc, i;
	unsigned int ui;
	Window r, w;
	Window *list;

#ifdef MACOSX
	if (1 || ! macosx_console) {
		RAWFB_RET_VOID
	}
#else
	RAWFB_RET_VOID
#endif


#if NO_X11 && !defined(MACOSX)
	num = rc = i = 0;	/* compiler warnings */
	ui = 0;
	r = w = None;
	list = NULL;
	return;
#else

	X_LOCK;
	/* no need to trap error since rootwin */
	rc = XQueryTree_wr(dpy, rootwin, &r, &w, &list, &ui);
	X_UNLOCK;
	num = (int) ui;

	if (! rc || num == 0) {
		*size = 0;
		return;
	} else {
		int m = *size;
		if (num < m) {
			m = num;
		}
		for (i=0; i < m; i++) {
			wins[i] = list[i];
		}
		if (num) {
			X_LOCK;
			XFree_wr(list);
			X_UNLOCK;
		}
		*size = m;
	}
#endif	/* NO_X11 */
}

int get_bs_n(int y) {
	int n;
	for (n = 1; n < ncache; n += 2) {
		if (n*dpy_y <= y && y < (n+1)*dpy_y) {
			return n;
		}
	}
	return -1;
}

#define NRECENT 32
Window recent[NRECENT];
int    recidx[NRECENT];
int rlast, rfree;

int lookup_win_index(Window win) {
	int k, idx = -1;
	int foundfree = 0;
	static int s1 = 0, s2 = 0, s3 = 0;

	if (win == rootwin || win == None) {
		return -1;
	}
	for (k = 0; k < NRECENT; k++) {
		if (recent[k] == win) {
			int k2 = recidx[k];
			if (cache_list[k2].win == win) {
				idx = k2;
if (0) fprintf(stderr, "recentA(shortcut): %d  0x%lx\n", idx, win);
				s1++;
				break;
			}
		}
	}
	if (idx < 0) {
		for(k=0; k<cache_list_num; k++) {
			if (!foundfree && cache_list[k].win == None) {
				rfree = k;
				foundfree = 1;
			}
			if (cache_list[k].win == win) {
				idx = k;
if (0) fprintf(stderr, "recentB(normal): %d  0x%lx\n", idx, win);
				s2++;
				break;
			}
		}
		if (idx >= 0) {
			recent[rlast] = win;
			recidx[rlast++] = idx;
			rlast = rlast % NRECENT;
		}
	}
	if (idx < 0) {
if (ncdb) fprintf(stderr, "recentC(fail): %d  0x%lx\n", idx, win);
		s3++;
	}
	if (s1 + s2 + s3 >= 1000) {
if (ncdb) fprintf(stderr, "lookup_win_index recent hit stats: %d/%d/%d\n", s1, s2, s3);
		s1 = s2 = s3 = 0;
	}
	return idx;
}

int lookup_free_index(void) {
	int k;

	if (rfree >= 0) {
		if (cache_list[rfree].win == None) {
if (ncdb) fprintf(stderr, "lookup_freeA: %d\n", rfree);
			return rfree;
		}
	}
	rfree = -1;
	for(k=0; k<cache_list_num; k++) {
		if (cache_list[k].win == None) {
			rfree = k;
			break;
		}
	}
	if (rfree < 0) {
		if (ncdb) fprintf(stderr, "*** LOOKUP_FREE_INDEX: incrementing cache_list_num %d/%d\n", cache_list_num, cache_list_len);

		rfree = cache_list_num++;
		if (rfree >= cache_list_len)  {
			int i, n = 2*cache_list_len;
			winattr_t *cache_new;

			if (ncdb) fprintf(stderr, "lookup_free_index: growing cache_list_len: %d -> %d\n", cache_list_len, n);

			cache_new = (winattr_t *) calloc(n*sizeof(winattr_t), 1);
			for (i=0; i<cache_list_num-1; i++) {
				cache_new[i] = cache_list[i]; 
			}
			cache_list_len = n;
			free(cache_list);
			cache_list = cache_new;
		}
		cache_list[rfree].win = None;
		cache_list[rfree].fetched = 0;
		cache_list[rfree].valid = 0;
		cache_list[rfree].time = 0.0;
		cache_list[rfree].selectinput = 0;
		cache_list[rfree].vis_cnt = 0;
		cache_list[rfree].map_cnt = 0;
		cache_list[rfree].unmap_cnt = 0;
		cache_list[rfree].create_cnt = 0;
		cache_list[rfree].vis_state = -1;
		cache_list[rfree].above = None;
	}

if (ncdb) fprintf(stderr, "lookup_freeB: %d\n", rfree);
	return rfree;
}

#define STACKMAX 4096
Window old_stack[STACKMAX];
Window new_stack[STACKMAX];
Window old_stack_map[STACKMAX];
Window new_stack_map[STACKMAX];
int old_stack_index[STACKMAX];
int old_stack_mapped[STACKMAX];
int old_stack_n = 0;
int new_stack_n = 0;
int old_stack_map_n = 0;
int new_stack_map_n = 0;

void snap_old(void) {
	int i;
	old_stack_n = STACKMAX;
	quick_snap(old_stack, &old_stack_n);
if (0) fprintf(stderr, "snap_old: %d  %.4f\n", old_stack_n, dnowx());
#if 0
	for (i= old_stack_n - 1; i >= 0; i--) {
		int idx = lookup_win_index(old_stack[i]);
		if (idx >= 0) {
			if (cache_list[idx].map_state == IsViewable) {
				if (ncdb) fprintf(stderr, "   %03d  0x%x\n", i, old_stack[i]);
			}
		}
	}
#endif
	for (i=0; i < old_stack_n; i++) {
		old_stack_mapped[i] = -1;
	}
}

void snap_old_index(void) {
	int i, idx;
	for (i=0; i < old_stack_n; i++) {
		idx = lookup_win_index(old_stack[i]);
		old_stack_index[i] = idx;
		if (idx >= 0) {
			if (cache_list[idx].map_state == IsViewable) {
				old_stack_mapped[i] = 1;
			} else {
				old_stack_mapped[i] = 0;
			}
		}
	}
}

int lookup_old_stack_index(int ic) {
	int idx = old_stack_index[ic];

	if (idx < 0) {
		return -1;
	}
	if (cache_list[idx].win != old_stack[ic]) {
		snap_old_index();
	}
	idx = old_stack_index[ic];
	if (idx < 0 || cache_list[idx].win != old_stack[ic]) {
		return -1;
	}
	if (cache_list[idx].map_state == IsViewable) {
		old_stack_mapped[ic] = 1;
	} else {
		old_stack_mapped[ic] = 0;
	}
	return idx;
}

#define STORE(k, w, attr) \
	if (0) fprintf(stderr, "STORE(%d) = 0x%lx\n", k, w); \
	cache_list[k].win = w;  \
	cache_list[k].fetched = 1;  \
	cache_list[k].valid = 1;  \
	cache_list[k].x = attr.x;  \
	cache_list[k].y = attr.y;  \
	cache_list[k].width = attr.width;  \
	cache_list[k].height = attr.height;  \
	cache_list[k].map_state = attr.map_state; \
	cache_list[k].time = dnow();

#define CLEAR(k) \
	if (0) fprintf(stderr, "CLEAR(%d)\n", k); \
	cache_list[k].bs_x = -1;  \
	cache_list[k].bs_y = -1;  \
	cache_list[k].bs_w = -1;  \
	cache_list[k].bs_h = -1;  \
	cache_list[k].su_x = -1;  \
	cache_list[k].su_y = -1;  \
	cache_list[k].su_w = -1;  \
	cache_list[k].su_h = -1;  \
	cache_list[k].time = 0.0;  \
	cache_list[k].bs_time = 0.0;  \
	cache_list[k].su_time = 0.0;  \
	cache_list[k].vis_obs_time = 0.0;  \
	cache_list[k].vis_unobs_time = 0.0;

#define DELETE(k) \
	if (0) fprintf(stderr, "DELETE(%d) = 0x%lx\n", k, cache_list[k].win); \
	cache_list[k].win = None;  \
	cache_list[k].fetched = 0;  \
	cache_list[k].valid = 0; \
	cache_list[k].selectinput = 0; \
	cache_list[k].vis_cnt = 0; \
	cache_list[k].map_cnt = 0; \
	cache_list[k].unmap_cnt = 0; \
	cache_list[k].create_cnt = 0; \
	cache_list[k].vis_state = -1; \
	cache_list[k].above = None; \
	free_rect(k);	/* does CLEAR(k) */

static	char unk[32];

char *Etype(int type) {
	if (type == KeyPress)		return "KeyPress";
	if (type == KeyRelease)		return "KeyRelease";
	if (type == ButtonPress)	return "ButtonPress";
	if (type == ButtonRelease)	return "ButtonRelease";
	if (type == MotionNotify)	return "MotionNotify";
	if (type == EnterNotify)	return "EnterNotify";
	if (type == LeaveNotify)	return "LeaveNotify";
	if (type == FocusIn)		return "FocusIn";
	if (type == FocusOut)		return "FocusOut";
	if (type == KeymapNotify)	return "KeymapNotify";
	if (type == Expose)		return "Expose";
	if (type == GraphicsExpose)	return "GraphicsExpose";
	if (type == NoExpose)		return "NoExpose";
	if (type == VisibilityNotify)	return "VisibilityNotify";
	if (type == CreateNotify)	return "CreateNotify";
	if (type == DestroyNotify)	return "DestroyNotify";
	if (type == UnmapNotify)	return "UnmapNotify";
	if (type == MapNotify)		return "MapNotify";
	if (type == MapRequest)		return "MapRequest";
	if (type == ReparentNotify)	return "ReparentNotify";
	if (type == ConfigureNotify)	return "ConfigureNotify";
	if (type == ConfigureRequest)	return "ConfigureRequest";
	if (type == GravityNotify)	return "GravityNotify";
	if (type == ResizeRequest)	return "ResizeRequest";
	if (type == CirculateNotify)	return "CirculateNotify";
	if (type == CirculateRequest)	return "CirculateRequest";
	if (type == PropertyNotify)	return "PropertyNotify";
	if (type == SelectionClear)	return "SelectionClear";
	if (type == SelectionRequest)	return "SelectionRequest";
	if (type == SelectionNotify)	return "SelectionNotify";
	if (type == ColormapNotify)	return "ColormapNotify";
	if (type == ClientMessage)	return "ClientMessage";
	if (type == MappingNotify)	return "MappingNotify";
	if (type == LASTEvent)		return "LASTEvent";
	sprintf(unk, "Unknown %d", type);
	return unk;
}
char *VState(int state) {
	if (state == VisibilityFullyObscured)		return "VisibilityFullyObscured";
	if (state == VisibilityPartiallyObscured)	return "VisibilityPartiallyObscured";
	if (state == VisibilityUnobscured)		return "VisibilityUnobscured";
	sprintf(unk, "Unknown %d", state);
	return unk;
}
char *MState(int state) {
	if (state == IsViewable)	return "IsViewable";
	if (state == IsUnmapped)	return "IsUnmapped";
	sprintf(unk, "Unknown %d", state);
	return unk;
}
sraRegionPtr rect_reg[64];
sraRegionPtr zero_rects = NULL;

int free_rect(int idx) {
	int n, ok = 0;
	sraRegionPtr r1, r2;
	int x, y, w, h;

	if (idx < 0 || idx >= cache_list_num) {
if (0) fprintf(stderr, "free_rect: bad index: %d\n", idx);
		clean_up_exit(1);
	}

	x = cache_list[idx].bs_x;
	y = cache_list[idx].bs_y;
	w = cache_list[idx].bs_w;
	h = cache_list[idx].bs_h;

	if (x < 0) {
		CLEAR(idx);
if (dnow() > last_client + 5 && ncdb) fprintf(stderr, "free_rect: already bs_x invalidated: %d bs_x: %d\n", idx, x);
		return 1;
	}

	r2 = sraRgnCreateRect(x, y, x+w, y+h);

	n = get_bs_n(y);
	if (n >= 0) {
		r1 = rect_reg[n];
		sraRgnOr(r1, r2);
		ok = 1;
	}

	if (zero_rects) {
		sraRgnOr(zero_rects, r2);
		x = cache_list[idx].su_x;
		y = cache_list[idx].su_y;
		w = cache_list[idx].su_w;
		h = cache_list[idx].su_h;
		if (x >= 0) {
			sraRgnDestroy(r2);
			r2 = sraRgnCreateRect(x, y, x+w, y+h);
			sraRgnOr(zero_rects, r2);
		}
	}
	sraRgnDestroy(r2);

	CLEAR(idx);
if (! ok && ncdb) fprintf(stderr, "**** free_rect: not-found %d\n", idx);
	return ok;
}

int fr_BIG1 = 0;
int fr_BIG2 = 0;
int fr_REGION = 0;
int fr_GRID = 0;
int fr_EXPIRE = 0;
int fr_FORCE = 0;
int fr_FAIL = 0;
int fr_BIG1t = 0;
int fr_BIG2t = 0;
int fr_REGIONt = 0;
int fr_GRIDt = 0;
int fr_EXPIREt = 0;
int fr_FORCEt = 0;
int fr_FAILt = 0;

void expire_rects1(int idx, int w, int h, int *x_hit, int *y_hit, int big1, int big2, int cram) {
	sraRegionPtr r1, r2, r3;
	int x = -1, y = -1, n;

	if (*x_hit < 0) {
		int i, k, old[10], N = 4;
		double dold[10], fa, d, d1, d2, d3;
		int a0 = w * h, a1;

		for (k=1; k<=N; k++) {
			old[k] = -1;
			dold[k] = -1.0;
		}
		for (i=0; i<cache_list_num; i++) {
			int wb = cache_list[i].bs_w;
			int hb = cache_list[i].bs_h;
			if (cache_list[i].bs_x < 0) {
				continue;
			}
			if (w > wb || h > hb) {
				continue;
			}
			if (wb == 0 || hb == 0) {
				continue;
			}
			if (a0 == 0) {
				continue;
			}
			if (i == idx) {
				continue;
			}
			a1 = wb * hb;
			fa = ((double) a1) / a0;
			k = (int) fa;

			if (k < 1) k = 1;
			if (k > N) continue;

			d1 = cache_list[i].time;
			d2 = cache_list[i].bs_time;
			d3 = cache_list[i].su_time;

			d = d1;
			if (d2 > d) d = d2;
			if (d3 > d) d = d3;

			if (dold[k] == -1.0 || d < dold[k]) {
				old[k] = i;
				dold[k] = d;
			}
		}

		for (k=1; k<=N; k++) {
			if (old[k] >= 0) {
				int ik = old[k];
				int k_x = cache_list[ik].bs_x;
				int k_y = cache_list[ik].bs_y;
				int k_w = cache_list[ik].bs_w;
				int k_h = cache_list[ik].bs_h;

if (ncdb) fprintf(stderr, ">>**--**>> found rect via EXPIRE: %d 0x%lx -- %dx%d+%d+%d %d %d --  %dx%d+%d+%d  A: %d/%d\n",
    ik, cache_list[ik].win, w, h, x, y, *x_hit, *y_hit, k_w, k_h, k_x, k_y, k_w * k_h, w * h);

				free_rect(ik);
				fr_EXPIRE++;
				fr_EXPIREt++;
				*x_hit = k_x;
				*y_hit = k_y;
				n = get_bs_n(*y_hit);
				if (n >= 0) {
					r1 = rect_reg[n];
					r2 = sraRgnCreateRect(*x_hit, *y_hit, *x_hit + w, *y_hit + h);
					sraRgnSubtract(r1, r2);
					sraRgnDestroy(r2);
				} else {
					fprintf(stderr, "failure to find y n in find_rect\n");
					clean_up_exit(1);
				}
				break;
			}
		}
	}

	/* next, force ourselves into some corner, expiring many */
	if (*x_hit < 0) {
		int corner_x = (int) (2 * rfac());
		int corner_y = (int) (2 * rfac());
		int x0 = 0, y0 = 0, i, nrand, nr = ncache/2;
		if (nr == 1) {
			nrand = 1;
		} else {
			if (! big1) {
				nrand = 1;
			} else {
				if (big2 && nr > 2) {
					nrand =  1 + (int) ((nr - 2) * rfac());
					nrand += 2; 
				} else {
					nrand =  1 + (int) ((nr - 1) * rfac());
					nrand += 1; 
				}
			}
		}
		if (nrand < 0 || nrand > nr) {
			nrand = nr;
		}
		if (cram && big1) {
			corner_x = 1;
		}

		y0 += dpy_y; 
		if (nrand > 1) {
			y0 += 2 * (nrand - 1) * dpy_y; 
		}
		if (corner_y) {
			y0 += dpy_y - h; 
		}
		if (corner_x) {
			x0 += dpy_x - w; 
		}
		r1 = sraRgnCreateRect(x0, y0, x0+w, y0+h);

		for (i=0; i<cache_list_num; i++) {
			int xb = cache_list[i].bs_x;
			int yb = cache_list[i].bs_y;
			int wb = cache_list[i].bs_w;
			int hb = cache_list[i].bs_h;
			if (xb < 0) {
				continue;
			}
			if (nabs(yb - y0) > dpy_y) {
				continue;
			}
			r2 = sraRgnCreateRect(xb, yb, xb+wb, yb+hb);
			if (sraRgnAnd(r2, r1)) {
				free_rect(i);
			}
			sraRgnDestroy(r2);
		}
		*x_hit = x0;
		*y_hit = y0;
		r3 = rect_reg[2*nrand-1];
		sraRgnSubtract(r3, r1);
		sraRgnDestroy(r1);

if (ncdb) fprintf(stderr, ">>**--**>> found rect via FORCE: %dx%d+%d+%d -- %d %d\n", w, h, x, y, *x_hit, *y_hit);

		fr_FORCE++;
		fr_FORCEt++;
	}
}

void expire_rects2(int idx, int w, int h, int *x_hit, int *y_hit, int big1, int big2, int cram) {
	sraRegionPtr r1, r2, r3;
	int x = -1, y = -1, n, i, j, k;
	int nwgt_max = 128, nwgt = 0;
	int type[128];
	int val[4][128];
	double wgt[128], norm;
	int Expire = 1, Force = 2;
	int do_expire = 1;
	int do_force = 1;
	double now = dnow(), r;
	double newest = -1.0, oldest = -1.0, basetime;
	double map_factor = 0.25;

	for (i=0; i<cache_list_num; i++) {
		double d, d1, d2;

		d1 = cache_list[i].bs_time;
		d2 = cache_list[i].su_time;

		d = d1;
		if (d2 > d) d = d2;

		if (d == 0.0) {
			continue;
		}

		if (oldest == -1.0 || d < oldest) {
			oldest = d;
		}
		if (newest == -1.0 || d > newest) {
			newest = d;
		}
	}
	if (newest == -1.0) {
		newest = now;
	}
	if (oldest == -1.0) {
		oldest = newest - 1800;
	}

	basetime = newest + 0.1 * (newest - oldest);

	if (do_expire) {
		int old[10], N = 4;
		double dold[10], fa, d, d1, d2;
		int a0 = w * h, a1;

		for (k=1; k<=N; k++) {
			old[k] = -1;
			dold[k] = -1.0;
		}
		for (i=0; i<cache_list_num; i++) {
			int wb = cache_list[i].bs_w;
			int hb = cache_list[i].bs_h;
			if (cache_list[i].bs_x < 0) {
				continue;
			}
			if (w > wb || h > hb) {
				continue;
			}
			if (wb == 0 || hb == 0) {
				continue;
			}
			if (a0 == 0) {
				continue;
			}
			if (i == idx) {
				continue;
			}

			a1 = wb * hb;
			fa = ((double) a1) / a0;
			k = (int) fa;

			if (k < 1) k = 1;
			if (k > N) continue;

			d1 = cache_list[i].bs_time;
			d2 = cache_list[i].su_time;

			d = d1;
			if (d2 > d) d = d2;
			if (d == 0.0) d = oldest;

			if (dold[k] == -1.0 || d < dold[k]) {
				old[k] = i;
				dold[k] = d;
			}
		}

		for (k=1; k<=N; k++) {
			if (old[k] >= 0) {
				int ik = old[k];
				int k_w = cache_list[ik].bs_w;
				int k_h = cache_list[ik].bs_h;

				wgt[nwgt] =  (basetime - dold[k]) / (k_w * k_h);
				if (cache_list[ik].map_state == IsViewable) {
					wgt[nwgt] *= map_factor;
				}
				type[nwgt] = Expire;
				val[0][nwgt] = ik;
if (ncdb) fprintf(stderr, "Expire[%02d]   %9.5f  age=%9.4f  area=%8d  need=%8d\n", nwgt, 10000 * wgt[nwgt], basetime - dold[k], k_w * k_h, w*h);
				nwgt++;
				if (nwgt >= nwgt_max) {
					break;
				}
			}
		}
	}

	/* next, force ourselves into some corner, expiring many rect */
	if (do_force) {
		int corner_x, corner_y;
		int x0, y0;

		for (n = 1; n < ncache; n += 2) {
		    if (big1 && ncache > 2 && n == 1) {
			continue;
		    }
		    if (big2 && ncache > 4 && n <= 3) {
			continue;
		    }
		    for (corner_x = 0; corner_x < 2; corner_x++) {
			if (cram && big1 && corner_x == 0) {
				continue;
			}
			for (corner_y = 0; corner_y < 2; corner_y++) {
				double age = 0.0, area = 0.0, amap = 0.0, a;
				double d, d1, d2, score;
				int nc = 0;

				x0 = 0;
				y0 = 0;
				y0 += n * dpy_y; 

				if (corner_y) {
					y0 += dpy_y - h; 
				}
				if (corner_x) {
					x0 += dpy_x - w; 
				}
				r1 = sraRgnCreateRect(x0, y0, x0+w, y0+h);

				for (i=0; i<cache_list_num; i++) {
					int xb = cache_list[i].bs_x;
					int yb = cache_list[i].bs_y;
					int wb = cache_list[i].bs_w;
					int hb = cache_list[i].bs_h;

					if (xb < 0) {
						continue;
					}
					if (nabs(yb - y0) > dpy_y) {
						continue;
					}

					r2 = sraRgnCreateRect(xb, yb, xb+wb, yb+hb);
					if (! sraRgnAnd(r2, r1)) {
						sraRgnDestroy(r2);
						continue;
					}
					sraRgnDestroy(r2);

					a = wb * hb;

					d1 = cache_list[i].bs_time;
					d2 = cache_list[i].su_time;

					d = d1;
					if (d2 > d) d = d2;
					if (d == 0.0) d = oldest;

					if (cache_list[i].map_state == IsViewable) {
						amap += a;
					}
					area += a;
					age += (basetime - d) * a;
					nc++;
				}
				if (nc == 0) {
					score = 999999.9;
				} else {
					double fac;
					age = age / area;
					score = age / area;
					fac = 1.0 * (1.0 - amap/area) + map_factor * (amap/area);
					score *= fac;
				}

				wgt[nwgt] =  score;
				type[nwgt] = Force;
				val[0][nwgt] = n;
				val[1][nwgt] = x0;
				val[2][nwgt] = y0;
if (ncdb) fprintf(stderr, "Force [%02d]   %9.5f  age=%9.4f  area=%8d  amap=%8d  need=%8d\n", nwgt, 10000 * wgt[nwgt], age, (int) area, (int) amap, w*h);
				nwgt++;
				if (nwgt >= nwgt_max) break;
				sraRgnDestroy(r1);
			}
			if (nwgt >= nwgt_max) break;
		    }
		    if (nwgt >= nwgt_max) break;
		}
	}

	if (nwgt == 0) {
if (ncdb) fprintf(stderr, "nwgt=0\n");
		*x_hit = -1;
		return;
	}

	norm = 0.0;
	for (i=0; i < nwgt; i++) {
		norm += wgt[i];
	}
	for (i=0; i < nwgt; i++) {
		wgt[i] /= norm; 
	}

	r = rfac();

	norm = 0.0;
	for (j=0; j < nwgt; j++) {
		norm += wgt[j];
if (ncdb) fprintf(stderr, "j=%2d  acc=%.6f r=%.6f\n", j, norm, r); 
		if (r < norm) {
			break;
		}
	}
	if (j >= nwgt) {
		j = nwgt - 1;
	}

	if (type[j] == Expire) {
		int ik = val[0][j];
		int k_x = cache_list[ik].bs_x;
		int k_y = cache_list[ik].bs_y;
		int k_w = cache_list[ik].bs_w;
		int k_h = cache_list[ik].bs_h;

if (ncdb) fprintf(stderr, ">>**--**>> found rect [%d] via RAN EXPIRE: %d 0x%lx -- %dx%d+%d+%d %d %d --  %dx%d+%d+%d  A: %d/%d\n",
	get_bs_n(*y_hit), ik, cache_list[ik].win, w, h, x, y, *x_hit, *y_hit, k_w, k_h, k_x, k_y, k_w * k_h, w * h);

		free_rect(ik);
		fr_EXPIRE++;
		fr_EXPIREt++;
		*x_hit = k_x;
		*y_hit = k_y;
		n = get_bs_n(*y_hit);
		if (n >= 0) {
			r1 = rect_reg[n];
			r2 = sraRgnCreateRect(*x_hit, *y_hit, *x_hit + w, *y_hit + h);
			sraRgnSubtract(r1, r2);
			sraRgnDestroy(r2);
		} else {
			fprintf(stderr, "failure to find y n in find_rect\n");
			clean_up_exit(1);
		}
		
	} else if (type[j] == Force) {

		int x0 = val[1][j];
		int y0 = val[2][j];
		n = val[0][j];
		
		r1 = sraRgnCreateRect(x0, y0, x0+w, y0+h);

		for (i=0; i<cache_list_num; i++) {
			int xb = cache_list[i].bs_x;
			int yb = cache_list[i].bs_y;
			int wb = cache_list[i].bs_w;
			int hb = cache_list[i].bs_h;
			if (xb < 0) {
				continue;
			}
			if (nabs(yb - y0) > dpy_y) {
				continue;
			}
			r2 = sraRgnCreateRect(xb, yb, xb+wb, yb+hb);
			if (sraRgnAnd(r2, r1)) {
				free_rect(i);
			}
			sraRgnDestroy(r2);
		}
		*x_hit = x0;
		*y_hit = y0;
		r3 = rect_reg[2*n-1];
		sraRgnSubtract(r3, r1);
		sraRgnDestroy(r1);

if (ncdb) fprintf(stderr, ">>**--**>> found rect [%d] via RAN FORCE: %dx%d+%d+%d -- %d %d\n", n, w, h, x, y, *x_hit, *y_hit);

		fr_FORCE++;
		fr_FORCEt++;
	}
}

void expire_rects(int idx, int w, int h, int *x_hit, int *y_hit, int big1, int big2, int cram) {
	int method = 2;
	if (method == 1) {
		expire_rects1(idx, w, h, x_hit, y_hit, big1, big2, cram);
	} else if (method == 2) {
		expire_rects2(idx, w, h, x_hit, y_hit, big1, big2, cram);
	}
}

int find_rect(int idx, int x, int y, int w, int h) {
	sraRegionPtr r1, r2;
	sraRectangleIterator *iter;
	sraRect rt;
	int n, x_hit = -1, y_hit = -1;
	int big1 = 0, big2 = 0, cram = 0;
	double fac1 = 0.1, fac2 = 0.25;
	double last_clean = 0.0;
	double now = dnow();
	static int nobigs = -1;

	if (rect_reg[1] == NULL) {
		for (n = 1; n <= ncache; n++) {
			rect_reg[n] = sraRgnCreateRect(0, n * dpy_y, dpy_x, (n+1) * dpy_y);
		}
	} else if (now > last_clean + 60) {
		last_clean = now;
		for (n = 1; n < ncache; n += 2) {
			int i, n2 = n+1;

			/* n */
			sraRgnDestroy(rect_reg[n]);
			r1 = sraRgnCreateRect(0, n * dpy_y, dpy_x, (n+1) * dpy_y);
			for (i=0; i<cache_list_num; i++) {
				int bs_x = cache_list[i].bs_x;
				int bs_y = cache_list[i].bs_y;
				int bs_w = cache_list[i].bs_w;
				int bs_h = cache_list[i].bs_h;
				if (bs_x < 0) {
					continue;
				}
				if (get_bs_n(bs_y) != n) {
					continue;
				}
				r2 = sraRgnCreateRect(bs_x, bs_y, bs_x+bs_w, bs_y+bs_h);
				sraRgnSubtract(r1, r2);
			}
			rect_reg[n] = r1;

			/* n+1 */
			sraRgnDestroy(rect_reg[n2]);
			r1 = sraRgnCreateRect(0, n2 * dpy_y, dpy_x, (n2+1) * dpy_y);
			for (i=0; i<cache_list_num; i++) {
				int bs_x = cache_list[i].bs_x;
				int su_x = cache_list[i].su_x;
				int su_y = cache_list[i].su_y;
				int su_w = cache_list[i].su_w;
				int su_h = cache_list[i].su_h;
				if (bs_x < 0) {
					continue;
				}
				if (get_bs_n(su_y) != n2) {
					continue;
				}
				r2 = sraRgnCreateRect(su_x, su_y, su_x+su_w, su_y+su_h);
				sraRgnSubtract(r1, r2);
			}
			rect_reg[n2] = r1;
		}
	}

	if (idx < 0 || idx >= cache_list_num) {
if (ncdb) fprintf(stderr, "free_rect: bad index: %d\n", idx);
		clean_up_exit(1);
	}

	cache_list[idx].bs_x = -1;
	cache_list[idx].su_x = -1;
	cache_list[idx].bs_time = 0.0;
	cache_list[idx].su_time = 0.0;

	if (ncache_pad) {
		x -= ncache_pad;	
		y -= ncache_pad;	
		w += 2 * ncache_pad;	
		h += 2 * ncache_pad;	
	}

	if (ncache <= 2) {
		cram = 1;
		fac2 = 0.45;
	} else if (ncache <= 4) {
		fac1 = 0.18;
		fac2 = 0.35;
	}
	if (macosx_console && !macosx_ncache_macmenu) {
		if (cram) {
			fac1 *= 1.5;	
			fac2 *= 1.5;	
		} else {
			fac1 *= 2.5;	
			fac2 *= 2.5;	
		}
	}
	if (w * h > fac1 * (dpy_x * dpy_y)) {
		big1 = 1;
	}
	if (w * h > fac2 * (dpy_x * dpy_y)) {
		big2 = 1;
	}

	if (nobigs < 0) {
		if (getenv("NOBIGS")) {
			nobigs = 1;
		} else {
			nobigs = 0;
		}
	}
	if (nobigs) {
		big1 = big2 = 0;
	}

	if (w > dpy_x || h > dpy_y) {
if (ncdb) fprintf(stderr, ">>**--**>> BIG1 rect: %dx%d+%d+%d -- %d %d\n", w, h, x, y, x_hit, y_hit);
		fr_BIG1++;
		fr_BIG1t++;
		return 0;
	}
	if (w == dpy_x && h == dpy_y) {
if (ncdb) fprintf(stderr, ">>**--**>> BIG1 rect: %dx%d+%d+%d -- %d %d (FULL DISPLAY)\n", w, h, x, y, x_hit, y_hit);
		fr_BIG1++;
		fr_BIG1t++;
		return 0;
	}
	if (cram && big2) {
if (ncdb) fprintf(stderr, ">>**--**>> BIG2 rect: %dx%d+%d+%d -- %d %d\n", w, h, x, y, x_hit, y_hit);
		fr_BIG2++;
		fr_BIG2t++;
		return 0;
	}

	/* first try individual rects of unused region */
	for (n = 1; n < ncache; n += 2) {
		r1 = rect_reg[n];
		r2 = NULL;
		if (big1 && n == 1 && ncache > 2) {
			continue;
		}
		if (big2 && n <= 3 && ncache > 4) {
			continue;
		}
		iter = sraRgnGetIterator(r1);
		while (sraRgnIteratorNext(iter, &rt)) {
			int rw = rt.x2 - rt.x1;
			int rh = rt.y2 - rt.y1;
			if (cram && big1 && rt.x1 < dpy_x/4) {
				continue;
			}
			if (rw >= w && rh >= h) {
				x_hit = rt.x1;
				y_hit = rt.y1;
				if (cram && big1) {
					x_hit = rt.x2 - w;
				}
				r2 = sraRgnCreateRect(x_hit, y_hit, x_hit + w, y_hit + h);
				break;
			}
		}
		sraRgnReleaseIterator(iter);
		if (r2 != NULL) {
if (ncdb) fprintf(stderr, ">>**--**>> found rect via REGION: %dx%d+%d+%d -- %d %d\n", w, h, x, y, x_hit, y_hit);
			fr_REGION++;
			fr_REGIONt++;
			sraRgnSubtract(r1, r2);
			sraRgnDestroy(r2);
			break;
		}
	}

	
	/* next try moving corner to grid points */
	if (x_hit < 0) {
	    for (n = 1; n < ncache; n += 2) {
		int rx, ry, Nx = 48, Ny = 24, ny = n * dpy_y;

		if (big1 && n == 1 && ncache > 2) {
			continue;
		}
		if (big2 && n == 3 && ncache > 4) {
			continue;
		}

		r1 = sraRgnCreateRect(0, n * dpy_y, dpy_x, (n+1) * dpy_y);
		sraRgnSubtract(r1, rect_reg[n]);
		r2 = NULL;

		rx = 0;
		while (rx + w <= dpy_x) {
		    ry = 0;
		    if (cram && big1 && rx < dpy_x/4) {
			rx += dpy_x/Nx;
		    	continue;
		    }
		    while (ry + h <= dpy_y) {
			r2 = sraRgnCreateRect(rx, ry+ny, rx + w, ry+ny + h);
			if (sraRgnAnd(r2, r1)) {
				sraRgnDestroy(r2);
				r2 = NULL;
			} else {
				sraRgnDestroy(r2);
				r2 = sraRgnCreateRect(rx, ry+ny, rx + w, ry+ny + h);
				x_hit = rx;
				y_hit = ry+ny;
			}
			ry += dpy_y/Ny;
			if (r2) break;
		    }
		    rx += dpy_x/Nx;
		    if (r2) break;
		}
		sraRgnDestroy(r1);
		if (r2 != NULL) {
			sraRgnSubtract(rect_reg[n], r2);
			sraRgnDestroy(r2);
if (ncdb) fprintf(stderr, ">>**--**>> found rect via GRID: %dx%d+%d+%d -- %d %d\n", w, h, x, y, x_hit, y_hit);
			fr_GRID++;
			fr_GRIDt++;
			break;
		}
	    }
	}

	/* next, try expiring the oldest/smallest used bs/su rectangle we fit in */

	if (x_hit < 0) {
		expire_rects(idx, w, h, &x_hit, &y_hit, big1, big2, cram);
	}

	cache_list[idx].bs_x = x_hit;
	cache_list[idx].bs_y = y_hit;
	cache_list[idx].bs_w = w;
	cache_list[idx].bs_h = h;

	cache_list[idx].su_x = x_hit;
	cache_list[idx].su_y = y_hit + dpy_y;
	cache_list[idx].su_w = w;
	cache_list[idx].su_h = h;

	if (x_hit < 0) {
		/* bad news, can it still happen? */
		if (ncdb) fprintf(stderr, ">>**--**>> *FAIL rect: %dx%d+%d+%d -- %d %d\n", w, h, x, y, x_hit, y_hit);
		fr_FAIL++;
		fr_FAILt++;
		return 0;
	} else {
		if (0) fprintf(stderr, ">>**--**>> found rect: %dx%d+%d+%d -- %d %d\n", w, h, x, y, x_hit, y_hit);
	}

	if (zero_rects) {
		r1 = sraRgnCreateRect(x_hit, y_hit, x_hit+w, y_hit+h);
		sraRgnSubtract(zero_rects, r1);
		sraRgnDestroy(r1);
		r1 = sraRgnCreateRect(x_hit, y_hit+dpy_y, x_hit+w, y_hit+dpy_y+h);
		sraRgnSubtract(zero_rects, r1);
		sraRgnDestroy(r1);
	}

	return 1;
}

static void cache_cr(sraRegionPtr r, int dx, int dy, double d0, double d1, int *nbatch) {
	if (sraRgnEmpty(r)) {
		return;
	}
	if (nbatch == NULL) {
		if (!fb_push_wait(d0, FB_COPY)) {
			fb_push_wait(d0/2, FB_COPY);
		}
		do_copyregion(r, dx, dy, 0);
		if (!fb_push_wait(d1, FB_COPY)) {
			fb_push_wait(d1/2, FB_COPY);
		}
	} else {
		batch_dxs[*nbatch] = dx;
		batch_dys[*nbatch] = dy;
		batch_reg[*nbatch] = sraRgnCreateRgn(r);
		(*nbatch)++;
	}
}

double save_delay0    = 0.02;
double restore_delay0 = 0.02;
double save_delay1    = 0.05;
double restore_delay1 = 0.05;
static double dtA, dtB;

int valid_wr(int idx, Window win, XWindowAttributes *attr) {
#ifdef MACOSX
	if (macosx_console) {
		/* this is all to avoid animation changing WxH+X+Y... */
		if (idx >= 0) {
			int rc = valid_window(win, attr, 1);
			attr->x = cache_list[idx].x;
			attr->y = cache_list[idx].y;
			attr->width = cache_list[idx].width;
			attr->height = cache_list[idx].height;
			return rc;
		} else {
			return valid_window(win, attr, 1);
		}
	}
#else
	if (!idx) {}
#endif
	return valid_window(win, attr, 1);
}

int clipped(int idx) {
	int ic;	
	sraRegionPtr r0, r1, r2;
	int x1, y1, w1, h1;
	Window win;
	int clip = 0;

	if (idx < 0) {
		return 0;
	}
	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);

	x1 = cache_list[idx].x;
	y1 = cache_list[idx].y;
	w1 = cache_list[idx].width;
	h1 = cache_list[idx].height;

	win = cache_list[idx].win;

	r1 = sraRgnCreateRect(x1, y1, x1+w1, y1+h1);
	sraRgnAnd(r1, r0);

	for (ic = old_stack_n - 1; ic >= 0; ic--) {
		int xc, yc, wc, hc, idx2;

		if (old_stack[ic] == win) {
			break;
		}
		if (old_stack_mapped[ic] == 0) {
			continue;
		}
		idx2 = lookup_old_stack_index(ic);
		if (idx2 < 0) {
			continue;
		}
		if (cache_list[idx2].win == win) {
			break;
		}
		if (cache_list[idx2].map_state != IsViewable) {
			continue;
		}
		xc = cache_list[idx2].x;
		yc = cache_list[idx2].y;
		wc = cache_list[idx2].width;
		hc = cache_list[idx2].height;

		r2 = sraRgnCreateRect(xc, yc, xc+wc, yc+hc);
		sraRgnAnd(r2, r0);
		if (sraRgnAnd(r2, r1)) {
if (0) fprintf(stderr, "clip[0x%lx]: 0x%lx, %d/%d\n", win, cache_list[idx2].win, ic, idx2);
			clip = 1;
		}
		sraRgnDestroy(r2);
		if (clip) {
			break;
		}
	}
	sraRgnDestroy(r0);
	sraRgnDestroy(r1);
if (0) fprintf(stderr, "clip[0x%lx]: %s\n", win, clip ? "clipped" : "no-clipped");
	return clip;
}

void clip_region(sraRegionPtr r, Window win) {
	int ic, idx2;	
	sraRegionPtr r1;
	for (ic = old_stack_n - 1; ic >= 0; ic--) {
		int xc, yc, wc, hc;

if (0) fprintf(stderr, "----[0x%lx]: 0x%lx, %d  %d\n", win, old_stack[ic], ic, old_stack_mapped[ic]);
		if (old_stack[ic] == win) {
			break;
		}
		if (old_stack_mapped[ic] == 0) {
			continue;
		}
		idx2 = lookup_old_stack_index(ic);
		if (idx2 < 0) {
			continue;
		}
		if (cache_list[idx2].win == win) {
			break;
		}
		if (cache_list[idx2].map_state != IsViewable) {
			continue;
		}
		xc = cache_list[idx2].x;
		yc = cache_list[idx2].y;
		wc = cache_list[idx2].width;
		hc = cache_list[idx2].height;
		r1 = sraRgnCreateRect(xc, yc, xc+wc, yc+hc);
		if (sraRgnAnd(r1, r)) {
			sraRgnSubtract(r, r1);
if (0) fprintf(stderr, "clip[0x%lx]: 0x%lx, %d/%d\n", win, cache_list[idx2].win, ic, idx2);
		}
		sraRgnDestroy(r1);
	}
}

int bs_save(int idx, int *nbatch, XWindowAttributes *attr, int clip, int only_if_tracking, int *valid, int verb) {
	Window win = cache_list[idx].win;
	int x1, y1, w1, h1;
	int x2, y2, w2, h2;
	int x, y, w, h;
	int dx, dy, rc = 1;
	sraRegionPtr r, r0;
	
	x1 = cache_list[idx].x;
	y1 = cache_list[idx].y;
	w1 = cache_list[idx].width;
	h1 = cache_list[idx].height;

if (ncdb && verb) fprintf(stderr, "backingstore save:       0x%lx  %3d clip=%d\n", win, idx, clip);
	
	X_LOCK;
	if (*valid) {
		attr->x = x1;
		attr->y = y1;
		attr->width = w1;
		attr->height = h1;
	} else if (! valid_wr(idx, win, attr)) {
if (ncdb) fprintf(stderr, "bs_save:    not a valid X window: 0x%lx\n", win);
		X_UNLOCK;
		*valid = 0;
		cache_list[idx].valid = 0;
		return 0;
	} else {
		*valid = 1;
	}
	X_UNLOCK;

	if (only_if_tracking && cache_list[idx].bs_x < 0) {
		return 0;
	}

	x2 = attr->x;
	y2 = attr->y;
	w2 = attr->width;
	h2 = attr->height;

	if (cache_list[idx].bs_x < 0) {
		rc = find_rect(idx, x2, y2, w2, h2);
	} else if (w2 > cache_list[idx].bs_w || h2 > cache_list[idx].bs_h) {
		free_rect(idx);
		rc = find_rect(idx, x2, y2, w2, h2);
	}

	x = cache_list[idx].bs_x;
	y = cache_list[idx].bs_y;
	w = cache_list[idx].bs_w;
	h = cache_list[idx].bs_h;

	if (x < 0 || ! rc) {
if (ncdb) fprintf(stderr, "BS_save: FAIL FOR: %d\n", idx);
		return 0;
	}

	if (ncache_pad) {
		x2 -= ncache_pad;	
		y2 -= ncache_pad;	
		w2 += 2 * ncache_pad;	
		h2 += 2 * ncache_pad;	
	}

	if (clipshift) {
		x2 -= coff_x;
		y2 -= coff_y;
	}

	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
	r = sraRgnCreateRect(x2, y2, x2+w2, y2+h2);
	sraRgnAnd(r, r0);

	if (clip) {
		clip_region(r, win);
	}

	if (sraRgnEmpty(r)) {
if (ncdb && verb) fprintf(stderr, "BS_save: Region Empty: %d\n", idx);
		sraRgnDestroy(r0);
		sraRgnDestroy(r);
		return 0;
	}

	dx = x - x2; 
	dy = y - y2; 

	sraRgnOffset(r, dx, dy);

	dtA =  dnowx();
if (ncdb && verb) fprintf(stderr, "BS_save: %.4f      %d dx=%d dy=%d\n", dtA, idx, dx, dy);
	if (w2 > 0 && h2 > 0) {
		cache_cr(r, dx, dy, save_delay0, save_delay1, nbatch);
	}
	dtB =  dnowx();
if (ncdb && verb) fprintf(stderr, "BS_save: %.4f %.2f %d done.  %dx%d+%d+%d %dx%d+%d+%d  %.2f %.2f\n", dtB, dtB-dtA, idx, w1, h1, x1, y1, w2, h2, x2, y2, cache_list[idx].bs_time - x11vnc_start, dnowx());

	sraRgnDestroy(r0);
	sraRgnDestroy(r);

	last_bs_save = cache_list[idx].bs_time = dnow();

	return 1;
}

int su_save(int idx, int *nbatch, XWindowAttributes *attr, int clip, int *valid, int verb) {
	Window win = cache_list[idx].win;
	int x1, y1, w1, h1;
	int x2, y2, w2, h2;
	int x, y, w, h;
	int dx, dy, rc = 1;
	sraRegionPtr r, r0;
	
if (ncdb && verb) fprintf(stderr, "save-unders save:        0x%lx  %3d \n", win, idx);

	x1 = cache_list[idx].x;
	y1 = cache_list[idx].y;
	w1 = cache_list[idx].width;
	h1 = cache_list[idx].height;
	
	X_LOCK;
	if (*valid) {
		attr->x = x1;
		attr->y = y1;
		attr->width = w1;
		attr->height = h1;
	} else if (! valid_wr(idx, win, attr)) {
if (ncdb) fprintf(stderr, "su_save:    not a valid X window: 0x%lx\n", win);
		X_UNLOCK;
		*valid = 0;
		cache_list[idx].valid = 0;
		return 0;
	} else {
		*valid = 1;
	}
	X_UNLOCK;

	x2 = attr->x;
	y2 = attr->y;
	w2 = attr->width;
	h2 = attr->height;

	if (cache_list[idx].bs_x < 0) {
		rc = find_rect(idx, x2, y2, w2, h2);
	} else if (w2 > cache_list[idx].su_w || h2 > cache_list[idx].su_h) {
		free_rect(idx);
		rc = find_rect(idx, x2, y2, w2, h2);
	}
	x = cache_list[idx].su_x;
	y = cache_list[idx].su_y;
	w = cache_list[idx].su_w;
	h = cache_list[idx].su_h;

	if (x < 0 || ! rc) {
if (ncdb) fprintf(stderr, "SU_save: FAIL FOR: %d\n", idx);
		return 0;
	}

	if (ncache_pad) {
		x2 -= ncache_pad;	
		y2 -= ncache_pad;	
		w2 += 2 * ncache_pad;	
		h2 += 2 * ncache_pad;	
	}

	if (clipshift) {
		x2 -= coff_x;
		y2 -= coff_y;
	}

	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
	r = sraRgnCreateRect(x2, y2, x2+w2, y2+h2);
	sraRgnAnd(r, r0);

	if (clip) {
		clip_region(r, win);
	}

	if (sraRgnEmpty(r)) {
if (ncdb && verb) fprintf(stderr, "SU_save: Region Empty: %d\n", idx);
		sraRgnDestroy(r0);
		sraRgnDestroy(r);
		return 0;
	}


	dx = x - x2; 
	dy = y - y2; 

	sraRgnOffset(r, dx, dy);

	dtA =  dnowx();
if (ncdb && verb) fprintf(stderr, "SU_save: %.4f      %d dx=%d dy=%d\n", dtA, idx, dx, dy);
	if (w2 > 0 && h2 > 0) {
		cache_cr(r, dx, dy, save_delay0, save_delay1, nbatch);
	}
	dtB =  dnowx();
if (ncdb && verb) fprintf(stderr, "SU_save: %.4f %.2f %d done.  %dx%d+%d+%d %dx%d+%d+%d  %.2f %.2f\n", dtB, dtB-dtA, idx, w1, h1, x1, y1, w2, h2, x2, y2, cache_list[idx].su_time - x11vnc_start, dnowx());

	sraRgnDestroy(r0);
	sraRgnDestroy(r);

	last_su_save = cache_list[idx].su_time = dnow();
	
	return 1;
}

int bs_restore(int idx, int *nbatch, sraRegionPtr rmask, XWindowAttributes *attr, int clip, int nopad, int *valid, int verb) {
	Window win = cache_list[idx].win;
	int x1, y1, w1, h1;
	int x2, y2, w2, h2;
	int x, y, w, h;
	int dx, dy;
	sraRegionPtr r, r0;

if (ncdb && verb) fprintf(stderr, "backingstore restore:    0x%lx  %3d \n", win, idx);

	x1 = cache_list[idx].x;
	y1 = cache_list[idx].y;
	w1 = cache_list[idx].width;
	h1 = cache_list[idx].height;
	
	X_LOCK;
	if (*valid) {
		attr->x = x1;
		attr->y = y1;
		attr->width = w1;
		attr->height = h1;
	} else if (! valid_wr(idx, win, attr)) {
if (ncdb) fprintf(stderr, "BS_restore: not a valid X window: 0x%lx\n", win);
		*valid = 0;
		X_UNLOCK;
		return 0;
	} else {
		*valid = 1;
	}
	X_UNLOCK;

	x2 = attr->x;
	y2 = attr->y;
	w2 = attr->width;
	h2 = attr->height;

	x = cache_list[idx].bs_x;
	y = cache_list[idx].bs_y;
	w = cache_list[idx].bs_w;
	h = cache_list[idx].bs_h;

	if (x < 0 || cache_list[idx].bs_time == 0.0) {
		return 0;
	}

	if (ncache_pad) {
		if (nopad) {
			x += ncache_pad;	
			y += ncache_pad;	
			w -= 2 * ncache_pad;	
			h -= 2 * ncache_pad;	
		} else {
			x2 -= ncache_pad;	
			y2 -= ncache_pad;	
			w2 += 2 * ncache_pad;	
			h2 += 2 * ncache_pad;	
		}
	}

	if (clipshift) {
		x2 -= coff_x;
		y2 -= coff_y;
	}

	if (w2 > w) {
		w2 = w;
	}
	if (h2 > h) {
		h2 = h;
	}

	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
	r = sraRgnCreateRect(x, y, x+w2, y+h2);

	dx = x2 - x; 
	dy = y2 - y; 

	sraRgnOffset(r, dx, dy);
	sraRgnAnd(r, r0);

	if (clip) {
		clip_region(r, win);
	}
	if (rmask != NULL) {
		sraRgnAnd(r, rmask);
	}

	dtA =  dnowx();
if (ncdb && verb) fprintf(stderr, "BS_rest: %.4f      %d dx=%d dy=%d\n", dtA, idx, dx, dy);
	if (w2 > 0 && h2 > 0) {
		cache_cr(r, dx, dy, restore_delay0, restore_delay1, nbatch);
	}
	dtB =  dnowx();
if (ncdb && verb) fprintf(stderr, "BS_rest: %.4f %.2f %d done.  %dx%d+%d+%d %dx%d+%d+%d  %.2f %.2f\n", dtB, dtB-dtA, idx, w1, h1, x1, y1, w2, h2, x2, y2, cache_list[idx].bs_time - x11vnc_start, dnowx());

	sraRgnDestroy(r0);
	sraRgnDestroy(r);

	last_bs_restore = dnow();
	
	return 1;
}

int su_restore(int idx, int *nbatch, sraRegionPtr rmask, XWindowAttributes *attr, int clip, int nopad, int *valid, int verb) {
	Window win = cache_list[idx].win;
	int x1, y1, w1, h1;
	int x2 = 0, y2 = 0, w2 = 0, h2 = 0;
	int x, y, w, h;
	int dx, dy;
	sraRegionPtr r, r0;

if (ncdb && verb) fprintf(stderr, "save-unders  restore:    0x%lx  %3d \n", win, idx);
	
	x1 = cache_list[idx].x;
	y1 = cache_list[idx].y;
	w1 = cache_list[idx].width;
	h1 = cache_list[idx].height;
	
	X_LOCK;
	if (*valid) {
		attr->x = x1;
		attr->y = y1;
		attr->width = w1;
		attr->height = h1;
		x2 = attr->x;
		y2 = attr->y;
		w2 = attr->width;
		h2 = attr->height;
	} else if (! valid_wr(idx, win, attr)) {
if (ncdb) fprintf(stderr, "SU_restore: not a valid X window: 0x%lx\n", win);
		*valid = 0;
		x2 = x1;
		y2 = y1;
		w2 = w1;
		h2 = h1;
	} else {
		x2 = attr->x;
		y2 = attr->y;
		w2 = attr->width;
		h2 = attr->height;
		*valid = 1;
	}
	X_UNLOCK;

	x = cache_list[idx].su_x;
	y = cache_list[idx].su_y;
	w = cache_list[idx].su_w;
	h = cache_list[idx].su_h;

	if (x < 0 || cache_list[idx].bs_x < 0 || cache_list[idx].su_time == 0.0) {
if (ncdb) fprintf(stderr, "SU_rest: su_x/bs_x/su_time: %d %d %.3f\n", x, cache_list[idx].bs_x, cache_list[idx].su_time);
		return 0;
	}

	if (ncache_pad) {
		if (nopad) {
			x += ncache_pad;	
			y += ncache_pad;	
			w -= 2 * ncache_pad;	
			h -= 2 * ncache_pad;	
		} else {
			x2 -= ncache_pad;	
			y2 -= ncache_pad;	
			w2 += 2 * ncache_pad;	
			h2 += 2 * ncache_pad;	
		}
	}

	if (clipshift) {
		x2 -= coff_x;
		y2 -= coff_y;
	}

	if (w2 > w) {
		w2 = w;
	}
	if (h2 > h) {
		h2 = h;
	}

	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
	r = sraRgnCreateRect(x, y, x+w2, y+h2);

	dx = x2 - x; 
	dy = y2 - y; 

	sraRgnOffset(r, dx, dy);
	sraRgnAnd(r, r0);

	if (clip) {
		clip_region(r, win);
	}
	if (rmask != NULL) {
		sraRgnAnd(r, rmask);
	}

	dtA =  dnowx();
if (ncdb && verb) fprintf(stderr, "SU_rest: %.4f      %d dx=%d dy=%d\n", dtA, idx, dx, dy);
	if (w2 > 0 && h2 > 0) {
		cache_cr(r, dx, dy, restore_delay0, restore_delay1, nbatch);
	}
	dtB =  dnowx();
if (ncdb && verb) fprintf(stderr, "SU_rest: %.4f %.2f %d done.  %dx%d+%d+%d %dx%d+%d+%d  %.2f %.2f\n", dtB, dtB-dtA, idx, w1, h1, x1, y1, w2, h2, x2, y2, cache_list[idx].su_time - x11vnc_start, dnowx());

	sraRgnDestroy(r0);
	sraRgnDestroy(r);

	last_su_restore = dnow();

	return 1;
}

void check_zero_rects(void) {
	sraRect rt;
	sraRectangleIterator *iter;
	if (! zero_rects) {
		zero_rects = sraRgnCreate();
	}
	if (sraRgnEmpty(zero_rects)) {
		return;
	}
		
	iter = sraRgnGetIterator(zero_rects);
	while (sraRgnIteratorNext(iter, &rt)) {
		zero_fb(rt.x1, rt.y1, rt.x2, rt.y2);
		mark_rect_as_modified(rt.x1, rt.y1, rt.x2, rt.y2, 0);
	}
	sraRgnReleaseIterator(iter);
	sraRgnMakeEmpty(zero_rects);
}

void block_stats(void) {
	int n, k, s1, s2;
	static int t = -1;
	int vcnt, icnt, tcnt, vtot = 0, itot = 0, ttot = 0;
	t++;
	for (n = 1; n < ncache+1; n += 2) {
		double area = 0.0, frac;
		vcnt = 0;
		icnt = 0;
		tcnt = 0;
		for(k=0; k<cache_list_num; k++) {
			XWindowAttributes attr;
			int x = cache_list[k].bs_x;
			int y = cache_list[k].bs_y;
			int w = cache_list[k].bs_w;
			int h = cache_list[k].bs_h;
			int rc = 0;
			Window win = cache_list[k].win;
			if (win == None) {
				continue;
			}
			if (n == 1) {
				X_LOCK;
				rc = valid_window(win, &attr, 1);
				X_UNLOCK;
				if (rc) {
					vtot++;
				} else {
					itot++;
				}
				if (x >= 0) {
					ttot++;
				}
			}
			if (y < n*dpy_y || y > (n+1)*dpy_y) {
				continue;
			}
			if (n != 1) {
				X_LOCK;
				rc = valid_window(win, &attr, 1);
				X_UNLOCK;
			}
			if (rc) {
				vcnt++;
			} else {
				icnt++;
			}
			if (x >= 0) {
				tcnt++;
			}
			if (x < 0) {
				continue;
			}
			area += cache_list[k].width * cache_list[k].height;
			if (! rc && ! macosx_console) {
				char *u = getenv("USER");
				if (u && !strcmp(u, "runge"))	fprintf(stderr, "\a");
				if (ncdb) fprintf(stderr, "\n   *** UNRECLAIMED WINDOW: 0x%lx  %dx%d+%d+%d\n\n", win, w, h, x, y);
				DELETE(k);
			}
			if (t < 3 || (t % 4) == 0 || hack_val || macosx_console) {
				double t1 = cache_list[k].su_time;
				double t2 = cache_list[k].bs_time;
				if (t1 > 0.0) {t1 = dnow() - t1;} else {t1 = -1.0;}
				if (t2 > 0.0) {t2 = dnow() - t2;} else {t2 = -1.0;}
				if (ncdb) fprintf(stderr, "     [%02d] %04d 0x%08lx bs: %04dx%04d+%04d+%05d vw: %04dx%04d+%04d+%04d cl: %04dx%04d+%04d+%04d map=%d su=%9.3f bs=%9.3f cnt=%d/%d\n",
				    n, k, win, w, h, x, y, attr.width, attr.height, attr.x, attr.y,
				    cache_list[k].width, cache_list[k].height, cache_list[k].x, cache_list[k].y,
				    attr.map_state == IsViewable, t1, t2, cache_list[k].create_cnt, cache_list[k].map_cnt); 
			}
		}
		frac = area /(dpy_x * dpy_y);
		if (ncdb) fprintf(stderr, "block[%02d]  %.3f  %8d  trak/val/inval: %d/%d/%d of %d\n", n, frac, (int) area, tcnt, vcnt, icnt, vcnt+icnt);
	}

	if (ncdb) fprintf(stderr, "\n");
	if (ncdb) fprintf(stderr, "block: trak/val/inval %d/%d/%d of %d\n", ttot, vtot, itot, vtot+itot);

	s1 = fr_REGION  + fr_GRID  + fr_EXPIRE  + fr_FORCE  + fr_BIG1  + fr_BIG2  + fr_FAIL;
	s2 = fr_REGIONt + fr_GRIDt + fr_EXPIREt + fr_FORCEt + fr_BIG1t + fr_BIG2t + fr_FAILt;
	if (ncdb) fprintf(stderr, "\n");
	if (ncdb) fprintf(stderr, "find_rect:  REGION/GRID/EXPIRE/FORCE - BIG1/BIG2/FAIL  %d/%d/%d/%d - %d/%d/%d  of %d\n",
	    fr_REGION,  fr_GRID,  fr_EXPIRE,  fr_FORCE,  fr_BIG1,  fr_BIG2,  fr_FAIL, s1);
	if (ncdb) fprintf(stderr, "                                       totals:         %d/%d/%d/%d - %d/%d/%d  of %d\n",
	    fr_REGIONt, fr_GRIDt, fr_EXPIREt, fr_FORCEt, fr_BIG1t, fr_BIG2t, fr_FAILt, s2);

	fr_BIG1 = 0;
	fr_BIG2 = 0;
	fr_REGION = 0;
	fr_GRID = 0;
	fr_EXPIRE = 0;
	fr_FORCE = 0;
	fr_FAIL = 0;
	if (ncdb) fprintf(stderr, "\n");
}

#define NSCHED 128
Window sched_bs[NSCHED];
double sched_tm[NSCHED];
double last_sched_bs = 0.0;

#define SCHED(w, v) \
{ \
	int k, save = -1, empty = 1; \
	for (k=0; k < NSCHED; k++) { \
		if (sched_bs[k] == None) { \
			save = k; \
		} \
		if (sched_bs[k] == w) { \
			save = k; \
			empty = 0; \
			break; \
		} \
	} \
	if (save >= 0) { \
		sched_bs[save] = w; \
		if (empty) { \
			sched_tm[save] = dnow(); \
			if (v && ncdb) fprintf(stderr, "SCHED: %d %f\n", save, dnowx()); \
		} \
	} \
}

void xselectinput(Window w, unsigned long evmask, int sync) {
#if NO_X11
	trapped_xerror = 0;
	trapped_xioerror = 0;
	if (!evmask) {}
#else
	XErrorHandler   old_handler1;
	XIOErrorHandler old_handler2;

	old_handler1 = XSetErrorHandler(trap_xerror);
	old_handler2 = XSetIOErrorHandler(trap_xioerror);
	trapped_xerror = 0;
	trapped_xioerror = 0;

	XSelectInput(dpy, w, evmask);

	/*
	 * We seem to need to synchronize right away since the window
	 * might go away quickly.
	 */
	if (sync) {
		XSync(dpy, False);
	} else {
		XFlush_wr(dpy);
	}

	XSetErrorHandler(old_handler1);
	XSetIOErrorHandler(old_handler2);
#endif

	if (trapped_xerror) {
		if (ncdb) fprintf(stderr, "XSELECTINPUT: trapped X Error.");
	}
	if (trapped_xioerror) {
		if (ncdb) fprintf(stderr, "XSELECTINPUT: trapped XIO Error.");
	}
if (sync && ncdb) fprintf(stderr, "XSELECTINPUT: 0x%lx  sync=%d err=%d/%d\n", w, sync, trapped_xerror, trapped_xioerror);
}

Bool xcheckmaskevent(Display *d, long mask, XEvent *ev) {
#ifdef MACOSX
	if (macosx_console) {
		if (macosx_checkevent(ev)) {
			return True;
		} else {
			return False;
		}
	}
#endif
	RAWFB_RET(False);

#if NO_X11
	if (!d || !mask) {}
	return False;
#else
	return XCheckMaskEvent(d, mask, ev);
#endif
}

#include <rfb/default8x16.h>

#define EVMAX 2048
XEvent Ev[EVMAX];
int Ev_done[EVMAX];
int Ev_order[EVMAX];
int Ev_area[EVMAX];
int Ev_tmp[EVMAX];
int Ev_tmp2[EVMAX];
Window Ev_tmpwin[EVMAX];
Window Ev_win[EVMAX];
Window Ev_map[EVMAX];
Window Ev_unmap[EVMAX];
sraRect Ev_rects[EVMAX];

int tmp_stack[STACKMAX];
sraRegionPtr tmp_reg[STACKMAX];

#define CLEAN_OUT \
	for (i=0; i < n; i++) { \
		sraRgnDestroy(tmp_reg[i]); \
	} \
	if (r1) sraRgnDestroy(r1); \
	if (r0) sraRgnDestroy(r0);

int try_to_fix_resize_su(Window orig_frame, int orig_x, int orig_y, int orig_w, int orig_h,
    int x, int y, int w, int h, int try_batch) {

	int idx = lookup_win_index(orig_frame);
	sraRegionPtr r0, r1, r2, r3;
	int sx1, sy1, sw1, sh1, dx, dy;
	int bx1, by1, bw1, bh1;
	int nr = 0, *nbat = NULL;

	if (idx < 0) {
		return 0;
	}
	if (cache_list[idx].bs_x < 0 || cache_list[idx].su_time == 0.0) {
		return 0;
	}

	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
	r1 = sraRgnCreateRect(orig_x, orig_y, orig_x+orig_w, orig_y+orig_h);
	r2 = sraRgnCreateRect(x, y, x+w, y+h);

	sraRgnAnd(r1, r0);
	sraRgnAnd(r2, r0);

	if (try_batch) {
		nbat = &nr;
	}
	
	if (orig_w >= w && orig_h >= h) {

if (0) fprintf(stderr, "Shrinking resize %d  %dx%d+%d+%d -> %dx%d+%d+%d\n", idx, orig_w, orig_h, orig_x, orig_y, w, h, x, y);
		r3 = sraRgnCreateRgn(r1);
		sraRgnSubtract(r3, r2);

		sx1 = cache_list[idx].su_x;
		sy1 = cache_list[idx].su_y;
		sw1 = cache_list[idx].su_w;
		sh1 = cache_list[idx].su_h;

		dx = orig_x - sx1;
		dy = orig_y - sy1;

		cache_cr(r3, dx, dy, 0.075, 0.05, nbat);
		sraRgnDestroy(r3);

		r3 = sraRgnCreateRgn(r1);
		sraRgnAnd(r3, r2);

		dx = sx1 - orig_x;
		dy = sy1 - orig_y;
		sraRgnOffset(r3, dx, dy);

		dx = orig_x - x;
		dy = orig_y - y;
		sraRgnOffset(r3, dx, dy);

		cache_cr(r3, dx, dy, 0.075, 0.05, nbat);
		sraRgnDestroy(r3);

		if (nr) {
			batch_push(nr, -1.0);
		}

		cache_list[idx].x = x;
		cache_list[idx].y = y;
		cache_list[idx].width = w;
		cache_list[idx].height = h;

		cache_list[idx].bs_w = w;
		cache_list[idx].bs_h = h;
		cache_list[idx].su_w = w;
		cache_list[idx].su_h = h;

		cache_list[idx].bs_time = 0.0;
		/* XXX Y */
		if (0) cache_list[idx].su_time = dnow();
	} else {
if (0) fprintf(stderr, "Growing resize %d  %dx%d+%d+%d -> %dx%d+%d+%d\n", idx, orig_w, orig_h, orig_x, orig_y, w, h, x, y);

		sx1 = cache_list[idx].su_x;
		sy1 = cache_list[idx].su_y;
		sw1 = cache_list[idx].su_w;
		sh1 = cache_list[idx].su_h;

		bx1 = cache_list[idx].bs_x;
		by1 = cache_list[idx].bs_y;
		bw1 = cache_list[idx].bs_w;
		bh1 = cache_list[idx].bs_h;
		
		if (find_rect(idx, x, y, w, h)) {
			r3 = sraRgnCreateRgn(r2);
			sraRgnAnd(r3, r1);

			dx = cache_list[idx].su_x - x;
			dy = cache_list[idx].su_y - y;
	
			sraRgnOffset(r3, dx, dy);

			dx = dx - (sx1 - orig_x);
			dy = dy - (sy1 - orig_y);
			
			cache_cr(r3, dx, dy, 0.075, 0.05, nbat);
			sraRgnDestroy(r3);

			r3 = sraRgnCreateRgn(r2);
			sraRgnSubtract(r3, r1);

			dx = cache_list[idx].su_x - x;
			dy = cache_list[idx].su_y - y;
	
			sraRgnOffset(r3, dx, dy);

			cache_cr(r3, dx, dy, 0.075, 0.05, nbat);
			sraRgnDestroy(r3);

			if (nr) {
				batch_push(nr, -1.0);
			}

			cache_list[idx].bs_time = 0.0;
			/* XXX Y */
			if (0) cache_list[idx].su_time = dnow();
		}
	}
	
	sraRgnDestroy(r0);
	sraRgnDestroy(r1);
	sraRgnDestroy(r2);

	return 1;
}

int try_to_fix_su(Window win, int idx, Window above, int *nbatch, char *mode) {
	int i, idx2, n = 0, found = 0, found_above = 0; 	
	sraRegionPtr r0, r1, r2;
	Window win2;
	int x, y, w, h, on = 0;
	int x0, y0, w0, h0;
	int x1, y1, w1, h1;
	int x2, y2, w2, h2;
	int unmapped = 0;
	int moved = 0;


	if (mode && !strcmp(mode, "unmapped")) {
		unmapped = 1;
	} else if (mode && !strcmp(mode, "moved")) {
		moved = 1;
	}
	if (idx < 0) {
		return 0;
	}
if (ncdb) fprintf(stderr, "TRY_TO_FIX_SU(%d)  0x%lx  0x%lx was_unmapped=%d map_state=%s\n", idx, win, above, unmapped, MState(cache_list[idx].map_state));

	if (cache_list[idx].map_state != IsViewable && !unmapped) {
		return 0;
	}
	if (cache_list[idx].su_time == 0.0) {
		return 0;
	}
	if (cache_list[idx].bs_x < 0) {
		return 0;
	}

	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);

	x = cache_list[idx].x;
	y = cache_list[idx].y;
	w = cache_list[idx].width;
	h = cache_list[idx].height;

	r1 = sraRgnCreateRect(x, y, x+w, y+h);

	sraRgnAnd(r1, r0);

	if (sraRgnEmpty(r1)) {
		CLEAN_OUT
		return 0;
	}

	if (unmapped) {
		on = 1;
	}
	if (above == 0x1) {
		on = 1;
	}
	for (i = old_stack_n - 1; i >= 0; i--) {
		win2 = old_stack[i];
		if (win2 == above) {
if (0) fprintf(stderr, "0x%lx turn on:  0x%lx  i=%d\n", win, win2, i);
			on = 1;
			found_above = 1;
		}
		if (win2 == win) {
if (0) fprintf(stderr, "0x%lx turn off: 0x%lx  i=%d\n", win, win2, i);
			found = 1;
			on = 0;
			break;
		}
		if (! on) {
			continue;
		}
		idx2 = lookup_win_index(win2);
		if (idx2 < 0) {
			continue;
		}
		if (cache_list[idx2].map_state != IsViewable) {
			continue;
		}
		if (cache_list[idx2].bs_x < 0) {
			continue;
		}
		/* XXX Invalidate? */

		x2 = cache_list[idx2].x;
		y2 = cache_list[idx2].y;
		w2 = cache_list[idx2].width;
		h2 = cache_list[idx2].height;

		r2 = sraRgnCreateRect(x2, y2, x2+w2, y2+h2);
		sraRgnAnd(r2, r0);
		if (! sraRgnAnd(r2, r1)) {
			sraRgnDestroy(r2);
			continue;
		}

		tmp_reg[n] = r2;
		tmp_stack[n++] = idx2;
	}

	if (! found) {
		CLEAN_OUT
		return 0;
	}

	for (i = n - 1; i >= 0; i--) {
		int i2;
		r2 = sraRgnCreateRgn(tmp_reg[i]);
		for (i2 = i + 1; i2 < n; i2++)  {
			sraRgnSubtract(r2, tmp_reg[i2]);
		}
		idx2 = tmp_stack[i];
		if (!sraRgnEmpty(r2)) {
			int dx, dy;
			int dx2, dy2;

			x0 = cache_list[idx2].x;
			y0 = cache_list[idx2].y;
			w0 = cache_list[idx2].width;
			h0 = cache_list[idx2].height;

			x1 = cache_list[idx].su_x;	/* SU -> SU */
			y1 = cache_list[idx].su_y;
			w1 = cache_list[idx].su_w;
			h1 = cache_list[idx].su_h;

			x2 = cache_list[idx2].su_x;
			y2 = cache_list[idx2].su_y;
			w2 = cache_list[idx2].su_w;
			h2 = cache_list[idx2].su_h;

			dx = x2 - x0;
			dy = y2 - y0;
			sraRgnOffset(r2, dx, dy);

			dx2 = x1 - x;
			dy2 = y1 - y;
			dx = dx - dx2;
			dy = dy - dy2;
			cache_cr(r2, dx, dy, save_delay0, save_delay1, nbatch);
		}
		sraRgnDestroy(r2);
	}

	if (unmapped) {
		CLEAN_OUT
		return found_above;
	}

	for (i = n - 1; i >= 0; i--) {
		r2 = sraRgnCreateRgn(tmp_reg[i]);
		idx2 = tmp_stack[i];
		if (!sraRgnEmpty(r2)) {
			int dx, dy;
			int dx2, dy2;

			x0 = cache_list[idx2].x;
			y0 = cache_list[idx2].y;
			w0 = cache_list[idx2].width;
			h0 = cache_list[idx2].height;

			x1 = cache_list[idx].su_x;	/* BS -> SU */
			y1 = cache_list[idx].su_y;
			w1 = cache_list[idx].su_w;
			h1 = cache_list[idx].su_h;

			x2 = cache_list[idx2].bs_x;
			y2 = cache_list[idx2].bs_y;
			w2 = cache_list[idx2].bs_w;
			h2 = cache_list[idx2].bs_h;

			dx = x1 - x;
			dy = y1 - y;
			sraRgnOffset(r2, dx, dy);

			dx2 = x2 - x0;
			dy2 = y2 - y0;
			dx = dx - dx2;
			dy = dy - dy2;
			cache_cr(r2, dx, dy, save_delay0, save_delay1, nbatch);
		}
		sraRgnDestroy(r2);
	}

	CLEAN_OUT
	return found_above;
}

void idx_add_rgn(sraRegionPtr r, sraRegionPtr r0, int idx) {
	int x, y, w, h;
	sraRegionPtr rtmp;
	
	if (idx < 0) {
		return;
	}
	x = cache_list[idx].x;
	y = cache_list[idx].y;
	w = cache_list[idx].width;
	h = cache_list[idx].height;

	rtmp = sraRgnCreateRect(x, y, w, h);
	if (r0) {
		sraRgnAnd(rtmp, r0);
	}
	sraRgnOr(r, rtmp);
	sraRgnDestroy(rtmp);
}

sraRegionPtr idx_create_rgn(sraRegionPtr r0, int idx) {
	int x, y, w, h;
	sraRegionPtr rtmp;
	
	if (idx < 0) {
		return NULL;
	}
	x = cache_list[idx].x;
	y = cache_list[idx].y;
	w = cache_list[idx].width;
	h = cache_list[idx].height;

	rtmp = sraRgnCreateRect(x, y, w, h);
	if (r0) {
		sraRgnAnd(rtmp, r0);
	}
	return rtmp;
}

void scale_mark_xrootpmap(void) {
	char *dst_fb, *src_fb = main_fb;
	int dst_bpl, Bpp = bpp/8, fac = 1;
	int yn = (ncache+1) * dpy_y;
	int yfac = (ncache+2);
	int mark = 1;

	if (!scaling || !rfb_fb || rfb_fb == main_fb) {
		mark_rect_as_modified(0, yn, dpy_x, yn + dpy_y, 0);
		return;
	}

	if (cmap8to24 && cmap8to24_fb) {
		src_fb = cmap8to24_fb;
		if (scaling && depth == 8) {
			fac = 4;
		}
	}
	dst_fb = rfb_fb;
	dst_bpl = rfb_bytes_per_line;

	scale_rect(scale_fac, scaling_blend, scaling_interpolate, fac * Bpp,
	    src_fb, fac * main_bytes_per_line, dst_fb, dst_bpl, dpy_x, yfac * dpy_y,
	    scaled_x, yfac * scaled_y, 0, yn, dpy_x, yn + dpy_y, mark);
}

void set_ncache_xrootpmap(void) {
	Atom pmap, type;
	int format;
	unsigned long length, after;
	XImage *image = NULL;
	XErrorHandler old_handler;

	RAWFB_RET_VOID
#if !NO_X11
	if (!ncache) {
		return;
	}
	X_LOCK;
	old_handler = XSetErrorHandler(trap_xerror);
	trapped_xerror = 0;
	pmap = XInternAtom(dpy, "_XROOTPMAP_ID", True);

	if (use_solid_bg) {
		image = solid_image(NULL);
		if (!quiet) {
			rfbLog("set_ncache_xrootpmap: solid_image\n");
		}
	} else if (pmap != None) {
		Pixmap pixmap = None;
		unsigned char *d_pmap;

		XGetWindowProperty(dpy, rootwin, pmap, 0L, 1L, False,
		    AnyPropertyType, &type, &format, &length, &after, &d_pmap);

		if (length != 0) {
			pixmap = *((Pixmap *) d_pmap);
			if (pixmap != None) {
				image = XGetImage(dpy, pixmap, 0, 0, dpy_x, dpy_y, AllPlanes, ZPixmap);
			}
		}
		if (!quiet) {
			rfbLog("set_ncache_xrootpmap: loading background pixmap: 0x%lx\n", pixmap);
		}
	} else {
		if (!quiet) {
			rfbLog("set_ncache_xrootpmap: trying root background\n");
		}
	}
	if (image == NULL) {
		image = solid_root((char *) 0x1);
	}
	if (image != NULL) {
		char *src, *dst;
		int line;
		int pixelsize = bpp/8;
		int y1 = dpy_y * (ncache+1);

		src = image->data;
		dst = main_fb + y1 * main_bytes_per_line;
		line = 0;
		while (line++ < dpy_y) {
			memcpy(dst, src, dpy_x * pixelsize);
			src += image->bytes_per_line;
			dst += main_bytes_per_line;
		}
		XDestroyImage(image);
		scale_mark_xrootpmap();
	} else {
		int yn = (ncache+1) * dpy_y;
		zero_fb(0, yn, dpy_x, yn + dpy_y);
	}
	XSetErrorHandler(old_handler);
	X_UNLOCK;
#endif
}

#define EVLISTMAX 256
#define EV_RESET		0
#define EV_CREATE		1
#define EV_DESTROY		2
#define EV_UNMAP		3
#define EV_MAP			4
#define EV_REPARENT		5
#define EV_CONFIGURE		6
#define EV_CONFIGURE_SIZE	7
#define EV_CONFIGURE_POS	8
#define EV_CONFIGURE_STACK	9
#define EV_VISIBILITY_UNOBS	10
#define EV_VISIBILITY_OBS	11
#define EV_PROPERTY		12
#define EV_OLD_WM_MAP		13
#define EV_OLD_WM_UNMAP		14
#define EV_OLD_WM_OFF		15
#define EV_OLD_WM_NOTMAPPED	16
Window _ev_list[EVLISTMAX];
int _ev_case[EVLISTMAX];
int _ev_list_cnt;

int n_CN = 0, n_RN = 0, n_DN = 0, n_ON = 0, n_MN = 0, n_UN = 0;
int n_VN = 0, n_VN_p = 0, n_VN_u = 0, n_ST = 0, n_PN = 0, n_DC = 0;
int n_ON_sz = 0, n_ON_po = 0, n_ON_st = 0;

int ev_store(Window win, int type) {
	if (type == EV_RESET)  {
		n_CN = 0; n_RN = 0; n_DN = 0; n_ON = 0; n_MN = 0; n_UN = 0;
		n_VN = 0; n_VN_p = 0; n_VN_u = 0; n_ST = 0; n_PN = 0; n_DC = 0;
		n_ON_sz = 0; n_ON_po = 0; n_ON_st = 0;
		_ev_list_cnt = 0;
		return 1;
	}
	if (_ev_list_cnt >= EVLISTMAX) {
		return 0;
	}
	_ev_list[_ev_list_cnt] = win;
	_ev_case[_ev_list_cnt++] = type;
	return 1;
}

int ev_lookup(Window win, int type) {
	int i;
	for(i=0; i < _ev_list_cnt; i++) {
		if (_ev_list[i] == win && _ev_case[i] == type) 	{
			return 1;
		}
	}
	return 0;
}

unsigned long all_ev = SubstructureNotifyMask|StructureNotifyMask|VisibilityChangeMask;
unsigned long win_ev = StructureNotifyMask|VisibilityChangeMask;

void read_events(int *n_in) {
	int n = *n_in;
	Window win, win2;
	XEvent ev;
	
	while (xcheckmaskevent(dpy, all_ev, &Ev[n])) {
		int cfg_size = 0;
		int cfg_pos = 0;
		int cfg_stack = 0;
		int type = Ev[n].type; 
		Window w = None;

		win = Ev[n].xany.window;
		Ev_done[n] = 0;
		Ev_area[n] = 0;
		Ev_win[n] = win;
		Ev_map[n] = None;
		Ev_unmap[n] = None;
		Ev_order[n] = n;

		ev = Ev[n];

		if (type == DestroyNotify)  w = Ev[n].xcreatewindow.window;
		if (type == CreateNotify)   w = Ev[n].xdestroywindow.window;
		if (type == ReparentNotify) w = Ev[n].xreparent.window;
		if (type == UnmapNotify)    w = Ev[n].xunmap.window;
		if (type == MapNotify)      w = Ev[n].xmap.window;
		if (type == Expose)         w = Ev[n].xexpose.window;
		if (type == ConfigureNotify) w = Ev[n].xconfigure.window;
		if (type == VisibilityNotify) w = win;
		if (n == *n_in && ncdb) fprintf(stderr, "\n");
		if (1) {
			char *msg = "";
			int idx = -1, x = 0, y = 0, wd = 0, ht = 0;
			if (w != None) {
				idx = lookup_win_index(w);
				if (idx >= 0) {
					x = cache_list[idx].x;
					y = cache_list[idx].y;
					wd = cache_list[idx].width;
					ht = cache_list[idx].height;
				}
			}
			if (type == VisibilityNotify) {
				msg = VState(Ev[n].xvisibility.state);
			} else if (type == ConfigureNotify) {
				int x_new = Ev[n].xconfigure.x; 
				int y_new = Ev[n].xconfigure.y; 
				int w_new = Ev[n].xconfigure.width; 
				int h_new = Ev[n].xconfigure.height; 
				if (idx >= 0) {
					if (w_new != wd || h_new != ht) {
						msg = "change size";
						cfg_size = 1;
					} 
					if (x_new != x || y_new != y) {
						if (!strcmp(msg, "")) {
							msg = "change position";
						}
						cfg_pos = 1;
					} else if (! cfg_size) {
						msg = "change stacking";
						cfg_stack = 1;
					}
				}
			}
			
			if (ncdb) fprintf(stderr, "----- %02d inputev 0x%08lx w: 0x%08lx %04dx%04d+%04d+%04d %s  %s\n", n, win, w, wd, ht, x, y, Etype(type), msg);
		}

		if (win == rootwin) {
			if (type == CreateNotify) {
				win2 = ev.xcreatewindow.window;
				ev_store(win2, EV_CREATE);
				n++;
				n_CN++;
			} else if (type == ReparentNotify) {
				if (ev.xreparent.parent != rootwin) {
					win2 = ev.xreparent.window;
					if (win2 != rootwin) {
						ev_store(win2, EV_REPARENT);
					}
				}
				n++;
				n_RN++;
			} else if (type == PropertyNotify) {
				set_prop_atom(Ev[n].xproperty.atom);
				n++;
				n_PN++;
			} else if (type == MapNotify) {
				win2 = ev.xmap.window;
				ev_store(win2, EV_MAP);
				n++;
				n_CN++;
			} else {
				/* skip rest */
#if 0
				Window w = None;
if (type == DestroyNotify) w = Ev[n].xdestroywindow.window;
if (type == UnmapNotify)   w = Ev[n].xunmap.window;
if (type == MapNotify)     w = Ev[n].xmap.window;
if (type == Expose)        w = Ev[n].xexpose.window;
if (type == ConfigureNotify) w = Ev[n].xconfigure.window;
if (type != ConfigureNotify) fprintf(stderr, "root: skip %s  for 0x%lx\n", Etype(type), w);
#endif

			}
		} else {
			if (type == ReparentNotify) {
				ev_store(win, EV_REPARENT);
				n++;
				n_RN++;
			} else if (type == DestroyNotify) {
				ev_store(win, EV_DESTROY);
				n++;
				n_DN++;
			} else if (type == ConfigureNotify) {
				ev_store(win, EV_CONFIGURE);
				if (cfg_size) {
					ev_store(win, EV_CONFIGURE_SIZE);
					n_ON_sz++;
				}
				if (cfg_pos) {
					ev_store(win, EV_CONFIGURE_POS);
					n_ON_po++;
				}
				if (cfg_stack) {
					ev_store(win, EV_CONFIGURE_STACK);
					n_ON_st++;
				}
				n++;
				n_ON++;
			} else if (type == VisibilityNotify) {
				if (Ev[n].xvisibility.state == VisibilityUnobscured) {
					ev_store(win, EV_VISIBILITY_UNOBS);
					n_VN_u++;
				} else {
					ev_store(win, EV_VISIBILITY_OBS);
					n_VN_p++;
				}
				n++;
				n_VN++;
			} else if (type == MapNotify) {
				ev_store(win, EV_MAP);
				Ev_map[n] = win;
				n++;
				n_MN++;
			} else if (type == UnmapNotify) {
				ev_store(win, EV_UNMAP);
				Ev_unmap[n] = win;
				n++;
				n_UN++;
			} else {
				/* skip rest */
if (ncdb) fprintf(stderr, "----- skip %s\n", Etype(type));
			}
		}
		if (n >= EVMAX) {
			break;
		}
	}
	*n_in = n;
}

int try_to_synthesize_su(int force, int urgent, int *nbatch) {
	int i, idx, idx2, n = 0; 	
	sraRegionPtr r0, r1, r2;
	Window win = None;
	int x0, y0, w0, h0;
	int x1, y1, w1, h1;
	int x2, y2, w2, h2;
	int x3, y3, w3, h3;
	XWindowAttributes attr;

	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);

	snap_old();

	X_LOCK;
	for (i = old_stack_n - 1; i >= 0; i--) {
		win = old_stack[i];
		if (urgent) {	/* XXX Y resp */
			if (!valid_window(win, &attr, 1)) {
				continue;
			}
			idx = lookup_win_index(win);
			if (idx >= 0) {
				STORE(idx, win, attr);
			}
		} else {
			idx = lookup_win_index(win);
			if (idx >= 0) {
				attr.map_state = cache_list[idx].map_state;
				attr.x = cache_list[idx].x;
				attr.y = cache_list[idx].y;
				attr.width = cache_list[idx].width;
				attr.height = cache_list[idx].height;
			} else {
				attr.map_state = IsUnmapped;
				attr.x = 0;
				attr.y = 0;
				attr.width = 0;
				attr.height = 0;
			}
			
		}
		if (attr.map_state != IsViewable) {
			continue;
		}
if (0) fprintf(stderr, "win: 0x%lx %d  idx=%d\n", win, i, idx);

		x2 = attr.x;
		y2 = attr.y;
		w2 = attr.width;
		h2 = attr.height;

		r2 = sraRgnCreateRect(x2, y2, x2+w2, y2+h2);
		sraRgnAnd(r2, r0);

		tmp_reg[n] = r2;
		tmp_stack[n++] = idx;
	}
	X_UNLOCK;

	if (! n) {
		r1 = NULL;
		CLEAN_OUT
		return 0;
	}

	for (i = 0; i < n; i++) {
		int i2, cnt = 0;
		idx = tmp_stack[i];
		if (idx < 0 || cache_list[idx].bs_x < 0) {
			continue;
		}
		r1 = tmp_reg[i];
		if (r1 == NULL || sraRgnEmpty(r1)) {
			continue;
		}
		if (cache_list[idx].su_time > 0.0) {
			if (force) {
if (ncdb) fprintf(stderr, "forcing synth: 0x%lx %d\n", cache_list[idx].win, idx);
			} else {
				continue;
			}
		}
		if (ncache_xrootpmap) {
			int dx, dy;

			x0 = cache_list[idx].x;
			y0 = cache_list[idx].y;
			w0 = cache_list[idx].width;
			h0 = cache_list[idx].height;

			x1 = cache_list[idx].su_x;
			y1 = cache_list[idx].su_y;
			w1 = cache_list[idx].su_w;
			h1 = cache_list[idx].su_h;

			r2 = sraRgnCreateRgn(tmp_reg[i]);
			dx = x1 - x0;
			dy = y1 - y0;

			sraRgnOffset(r2, dx, dy);

			x2 = x0;
			y2 = y0 + (ncache+1) * dpy_y;

			dx = x1 - x2;
			dy = y1 - y2;
			cache_cr(r2, dx, dy, save_delay0, save_delay1, nbatch);
			cnt++;

			sraRgnDestroy(r2);
		}

		for (i2 = n - 1; i2 > i; i2--) {
			r2 = sraRgnCreateRgn(tmp_reg[i2]);
			if (sraRgnAnd(r2, r1)) {
				int dx, dy;
				int dx2, dy2;

				idx2 = tmp_stack[i2];
				/* XXX Y */
				if (idx2 < 0 || cache_list[idx2].bs_x < 0 || cache_list[idx2].bs_time == 0.0) {
					continue;
				}

				x0 = cache_list[idx].x;
				y0 = cache_list[idx].y;
				w0 = cache_list[idx].width;
				h0 = cache_list[idx].height;

				x1 = cache_list[idx].su_x;
				y1 = cache_list[idx].su_y;
				w1 = cache_list[idx].su_w;
				h1 = cache_list[idx].su_h;

				x2 = cache_list[idx2].x;
				y2 = cache_list[idx2].y;
				w2 = cache_list[idx2].width;
				h2 = cache_list[idx2].height;

				x3 = cache_list[idx2].bs_x;
				y3 = cache_list[idx2].bs_y;
				w3 = cache_list[idx2].bs_w;
				h3 = cache_list[idx2].bs_h;

				dx = x1 - x0;
				dy = y1 - y0;
				sraRgnOffset(r2, dx, dy);

				dx2 = x3 - x2;
				dy2 = y3 - y2;
				dx = dx - dx2;
				dy = dy - dy2;
				cache_cr(r2, dx, dy, save_delay0, save_delay1, nbatch);
				cnt++;
			}
			sraRgnDestroy(r2);
		}
		if (cnt) {
			cache_list[idx].su_time = dnow();
		}
if (ncdb) fprintf(stderr, "  try_to_synth_su: 0x%lx %d  idx=%d cnt=%d\n", win, i, idx, cnt);
	}

	r1 = NULL;
	CLEAN_OUT
	return 1;
}

static double last_vis_unobs_time = 0.0;
static double last_vis_obs_time = 0.0;

static int saw_desktop_change = 0;

void check_sched(int try_batch, int *did_sched) {
	static double last_root = 0.0;
	static double last_pixmap = 0.0;
	double refresh = 60.0;
	int i, k, valid;
	Window win;
	XWindowAttributes attr;
	double now = dnow();

	if (now > last_root + refresh) {

if (ncdb) fprintf(stderr, "\n**** checking cache_list[%d]\n\n", cache_list_num);
		block_stats();

		for(k=0; k<cache_list_num; k++) {
			valid = 0;
			win = cache_list[k].win; 
			X_LOCK;
			if (win == None) {
				;
			} else if (cache_list[k].selectinput && cache_list[k].time > now - refresh) {
				valid = 1;
			} else if (valid_window(win, &attr, 1)) {
				STORE(k, win, attr);
				if (! cache_list[k].selectinput) {
					xselectinput(win, win_ev, 0);
					CLEAR(k);
					cache_list[k].selectinput = 1;
				}
				valid = 1;
			} else {
if (ncdb) fprintf(stderr, "DELETE(%d) %dx%d+%d+%d\n", k, cache_list[k].width, cache_list[k].height, cache_list[k].x, cache_list[k].y);
				DELETE(k);
			}
			X_UNLOCK;
/* XXX Y */
			if (valid) {
				if (cache_list[k].create_cnt && cache_list[k].map_state != IsViewable && cache_list[k].map_cnt == 0) {
					if (cache_list[k].bs_x >= 0) {
if (ncdb) fprintf(stderr, "Created window never mapped: freeing(%d) 0x%lx\n", k, win);
						free_rect(k);
					}
				}
			}
		}
		last_root = dnow();
	}

	if (now > last_sched_bs + 0.30) {
		static double last_sched_vis = 0.0;
		int nr = 0, *bat = NULL;

		if (try_batch) {
			bat = &nr;
		}
		if (now < last_wireframe + 2.0) {
			for (i=0; i < NSCHED; i++) {
				sched_bs[i] = None;
			}
		}
		if (now < last_get_wm_frame_time + 1.0) {
			if (last_get_wm_frame != None) {
				int idx = lookup_win_index(last_get_wm_frame);
				if (idx >= 0) {
					if (cache_list[idx].bs_x < 0) {
						int x = cache_list[idx].x;
						int y = cache_list[idx].y;
						int w = cache_list[idx].width;
						int h = cache_list[idx].height;
						if (find_rect(idx, x, y, w, h)) {
							SCHED(last_get_wm_frame, 1);
						}
					}
				}
			}
		}
		
		for (i=0; i < NSCHED; i++) {
			if (sched_bs[i] != None) {
				int idx;
				win = sched_bs[i];	
				if (now < sched_tm[i] + 0.55) {
					continue;
				}
				if (n_MN || n_UN || n_ST || n_DC) {
					sched_tm[i] = now;
					continue;
				}
				idx = lookup_win_index(win);
				if (idx >= 0) {
					int aw = cache_list[idx].width; 
					int ah = cache_list[idx].height; 
					if (cache_list[idx].map_state != IsViewable) {
						;
					} else if (cache_list[idx].vis_state != VisibilityUnobscured) {
						;
					} else if (aw * ah < 64 * 64) {
						;
					} else {
if (ncdb) fprintf(stderr, "*SNAP BS_save: 0x%lx %d %d %d\n", win, aw, ah, cache_list[idx].map_state); 
						valid = 0;
						bs_save(idx, bat, &attr, 1, 0, &valid, 0);
						if (valid) {
							STORE(idx, win, attr);
						} else {
							DELETE(idx);
						}
					}
				} else {
if (ncdb) fprintf(stderr, "*SCHED LOOKUP FAIL: i=%d 0x%lx\n", i, win);
				}
			}
			sched_bs[i] = None;
		}
		*did_sched = 1;

		if (n_MN || n_UN || n_ST || n_DC) {
			if (last_sched_vis < now) {
				last_sched_vis += 1.0;
			}
		} else if (now > last_sched_vis + 3.0 && now > last_wireframe + 2.0) {
			static double last_vis = 0.0;
			int vis_now[32], top_now[32];
			static int vis_prev[32], freq = 0;
			int diff, nv = 32, vis_now_n = 0;
			Window win;

			freq++;

			for (i=0; i < cache_list_num; i++) {
				int ok = 0;
				int top_only = 1;
				int aw = cache_list[i].width; 
				int ah = cache_list[i].height; 
				int map_prev = cache_list[i].map_state;

				win = cache_list[i].win;

				if (saw_desktop_change) {
					top_only = 0;
				}

				if (win == None) {
					continue;
				}
				/* XXX Y resp */
				if (saw_desktop_change || freq % 5 == 0) {
					if (!valid_window(win, &attr, 1)) {
						continue;
					}
					STORE(i, win, attr);
				}
				if (!cache_list[i].valid) {
					continue;
				}
				if (cache_list[i].map_state != IsViewable) {
					continue;
				}
				if (cache_list[i].vis_state == VisibilityFullyObscured) {
					continue;
				}
				if (map_prev != IsViewable) {
					/* we hope to catch it below in the normal event processing */
					continue;
				}
				if (aw * ah < 64 * 64) {
					continue;
				}
				if (top_only) {
					if (cache_list[i].vis_state == VisibilityUnobscured) {
						ok = 1;
					} else if (!clipped(i)) {
						ok = 1;
					}
				} else {
					ok = 1;
				}
				if (ok) {
					if (vis_now_n < nv) {
						vis_now[vis_now_n] = i;
						top_now[vis_now_n++] = top_only;
					}
				}
			}
			diff = 0;
			for (k = 0; k < vis_now_n; k++) {
				if (vis_now[k] != vis_prev[k]) {
					diff = 1;
				}
			}
			if (diff == 0) {
				if (now > last_vis + 45.0) {
					diff = 1;
				}
			}
			if (diff) {
if (ncdb && vis_now_n) fprintf(stderr, "*VIS  snapshot all %.4f\n", dnowx());
				for (k = 0; k < vis_now_n; k++) {
					i = vis_now[k];
					win = cache_list[i].win;
					valid = 0;
if (ncdb) fprintf(stderr, "*VIS  BS_save: 0x%lx %d %d %d\n", win, cache_list[i].width, cache_list[i].height, cache_list[i].map_state); 
					if (now < cache_list[i].vis_unobs_time + 0.75 && now < cache_list[i].vis_obs_time + 0.75) {
						continue;
					}
					bs_save(i, bat, &attr, !top_now[k], 0, &valid, 1);
					if (valid) {
						STORE(i, win, attr);
					} else {
						DELETE(i);
					}
					vis_prev[k] = vis_now[k];
				}
				last_vis = dnow();
			}
			last_sched_vis = dnow();
			if (! n_DC) {
				saw_desktop_change = 0;
			}
			/* XXX Y */
			try_to_synthesize_su(0, 0, bat);
		}

		if (nr) {
			batch_push(nr, -1.0);
		}
		last_sched_bs = dnow();
	}
#if !NO_X11
	if (atom_XROOTPMAP_ID == None && now > last_pixmap + 5.0) {
		atom_XROOTPMAP_ID = XInternAtom(dpy, "_XROOTPMAP_ID", True);
		last_pixmap = now;
	}
#endif
	if (got_XROOTPMAP_ID > 0.0) {
if (ncdb) fprintf(stderr, "got_XROOTPMAP_ID\n");
		if (ncache_xrootpmap) {
			set_ncache_xrootpmap();
		}
		got_XROOTPMAP_ID = 0.0;
	}
}

int check_ncache(int reset, int mode) {
	static int first = 1;
	static int last_client_count = -1;
	int i, k, n; 
	int did_sched = 0;

	Window win, win2;
	XWindowAttributes attr;
	int valid;
	int try_batch = 1; /* XXX Y */
	int use_batch = 0;
	int nreg = 0, *nbatch;
	int create_cnt;
	int su_fix_cnt;
	int pixels = 0, ttot;
	int desktop_change = 0, n1, n2;
	int desktop_change_old_wm = 0;
	int missed_su_restore = 0;
	int missed_bs_restore = 0;
	sraRegionPtr r0, r;
	sraRegionPtr missed_su_restore_rgn;
	sraRegionPtr missed_bs_restore_rgn;
	sraRegionPtr unmapped_rgn;

	int nrects = 0;
	int nsave, nxsel;
	double now;

	int skipwins_n = 0;
	int skipwins_max = 256;
	Window skipwins[256];

	static char *dt_guess = NULL;
	static double dt_last = 0.0;
	int dt_gnome = 0, gnome_animation = 0;
	int dt_kde = 0;

	if (unixpw_in_progress) return -1;

#ifdef MACOSX
	if (! macosx_console) {
		RAWFB_RET(-1)
	}
	if (! screen) {
		return -1;
	}
#else
	RAWFB_RET(-1)
	if (! screen || ! dpy) {
		return -1;
	}
#endif

	now = dnow();

#ifdef NO_NCACHE
	ncache = 0;
#endif

	if (reset && (first || cache_list_len == 0)) {
		return -1;
	}

	if (ncache0) {
		if (reset) {
			;
		} else if (!client_count || !ncache || nofb) {
			static double last_purge = 0.0;
			double delay = client_count ? 0.5 : 2.0;
			if (now > last_purge + delay) {
				int c = 0;
				XEvent ev;
				X_LOCK;
				while (xcheckmaskevent(dpy, all_ev, &ev)) {
					c++;
				}
				X_UNLOCK;
				last_purge = dnow();
if (ncdb && c) fprintf(stderr, "check_ncache purged %d events\n", c); 
			}
			if (!client_count && last_client_count >= 0 &&
			    client_count != last_client_count) {
				/* this should use less RAM when no clients */
				do_new_fb(1);
			}
			last_client_count = client_count;
			return -1;
		}
	}
	last_client_count = client_count;

	if (ncache && ! ncache0) {
		ncache0 = ncache;
	}

	if (! ncache || ! ncache0) {
		return -1;
	}
	if (subwin) {
		return -1;
	}
	if (nofb) {
		return -1;
	}
	if (now < last_client + 4) {
		return -1;
	}
	if (! all_clients_initialized()) {
		/* play it safe */
		return -1;
	}



	if (reset) {
		rfbLog("check_ncache: resetting cache: %d/%d %d %d\n", cache_list_num, cache_list_len, ncache, first);
		for (i=0; i < cache_list_num; i++) {
			free_rect(i);
		}
		for (n = 1; n <= ncache; n++) {
			if (rect_reg[n] != NULL) {
				sraRgnDestroy(rect_reg[n]);
				rect_reg[n] = NULL;
			}
		}
		zero_fb(0, dpy_y, dpy_x, (ncache+1)*dpy_y);
		mark_rect_as_modified(0, dpy_y, dpy_x, (ncache+1)*dpy_y, 0);

		if (ncache_xrootpmap) {
			set_ncache_xrootpmap();
		}

		snap_old();
		return -1;
	}

	if (first) {
		int dx = 10, dy = 24, ds = 0;
		int Dx = dpy_x, Dy = dpy_y;
		first = 0;
		for (i=0; i < NRECENT; i++) {
			recent[i] = None;
		}
		for (i=0; i < NSCHED; i++) {
			sched_bs[i] = None;
		}
		rlast = 0;

		X_LOCK;
		/* event leak with client_count == 0 */
		xselectinput_rootwin |= SubstructureNotifyMask;
		XSelectInput_wr(dpy, rootwin, xselectinput_rootwin);
		X_UNLOCK;

		if (scaling) {
			Dx = scaled_x;
			Dy = scaled_y;
		}
		if (!rotating_same) {
			int t = Dx;
			Dx = Dy;
			Dy = t;
		}

		for (i = 0; i < 3; i++) {
			rfbDrawString(screen, &default8x16Font, dx, ds + Dy+1*dy,
			    "This is the Pixel buffer cache region. Your VNC Viewer is not hiding it from you.",
			    white_pixel());
			rfbDrawString(screen, &default8x16Font, dx, ds + Dy+2*dy,
			    "Try resizing your VNC Viewer so you don't see it!!",
			    white_pixel());
			rfbDrawString(screen, &default8x16Font, dx, ds + Dy+3*dy,
			    "Pay no attention to the man behind the curtain...",
			    white_pixel());
			rfbDrawString(screen, &default8x16Font, dx, ds + Dy+4*dy,
			    "To disable caching run the server with:  x11vnc -noncache ...",
			    white_pixel());
			rfbDrawString(screen, &default8x16Font, dx, ds + Dy+5*dy,
			    "If there are painting errors press 3 Alt_L's (Left \"Alt\" key) in a row to repaint the screen.",
			    white_pixel());
			rfbDrawString(screen, &default8x16Font, dx, ds + Dy+6*dy,
			    "More info:  http://www.karlrunge.com/x11vnc/#faq-client-caching",
			    white_pixel());

			ds += 11 * dy;
		}

		snapshot_cache_list(0, 100.0);
		for (i=0; i < cache_list_num; i++) {
			CLEAR(i);
		}
		for (n = 1; n <= ncache; n++) {
			rect_reg[n] = NULL;
		}

		if (ncache_xrootpmap) {
			set_ncache_xrootpmap();
		}

		snap_old();
	}

	check_zero_rects();

if (hack_val == 2) {
	block_stats();
	hack_val = 1;
}
#ifdef MACOSX
	if (macosx_console) {
		static double last_all_windows = 0.0;
		if (! macosx_checkevent(NULL)) {
			if (now > last_all_windows + 0.05) {
				macosxCGS_get_all_windows();
				last_all_windows = dnow();
			}
		}
		/* XXX Y */
		rootwin = -1;
	}
#endif

	n = 0;
	ttot = 0;

	if (dt_guess == NULL || now > dt_last + 60) {
		static char *dt_prev = NULL;
		dt_prev = dt_guess;
		dt_guess = strdup(guess_desktop());
		if (ncache_xrootpmap && dt_prev && dt_guess) {
			if (strcmp(dt_prev, dt_guess)) {
				set_ncache_xrootpmap();
			}
		}
		dt_last = now;
		if (dt_prev) {
			free(dt_prev);
		}
	}
	if (dt_guess && !strcmp(dt_guess, "gnome")) {
		dt_gnome = 1;
	} else if (dt_guess && !strcmp(dt_guess, "kde")) {
		dt_kde = 1;
	}
	if (dt_kde) {
		kde_no_animate(0);
	}

	ev_store(None, EV_RESET);

	X_LOCK;
	for (k = 1; k <= 3; k++) {
		int j, retry = 0;

		nsave = n;

		if (k > 1 && ncdb) fprintf(stderr, "read_events-%d\n", k);
		read_events(&n);

#if 0
		if (dt_gnome && (n_MN || n_UN)) {
			retry = 1;
		} else if (ncache_old_wm && n_ON_po >= 2) {
			retry = 1;
		} else if (n > nsave) {
			/* XXX Y */
			retry = 1;
		}

		if (retry) {
			int n0 = n;
			usleep(25 * 1000);
			XFlush_wr(dpy);
			read_events(&n);
			if (ncdb) fprintf(stderr, "read_events retry: %d -> %d\n", n0, n);
		}
#endif

		if (n > nsave) {
			int n0 = n;

			for (j=0; j<4; j++) {
				if (j < 2) {
					usleep(30 * 1000);
				} else {
					usleep(10 * 1000);
				}
				XFlush_wr(dpy);
				read_events(&n);
				if (ncdb) fprintf(stderr, "read_events retry: %d -> %d\n", n0, n);
				if (n == n0) {
					break;
				}
				n0 = n;
			}
		}
			
		nxsel = 0;

		/* handle creates and reparenting: */
		for (n1 = nsave; n1 < n; n1++) {
			Window win2;
			int idx;
			XEvent ev = Ev[n1];
			win = Ev_win[n1];
			if (ev.type == CreateNotify) {
				win2 = ev.xcreatewindow.window;
				if (ev_lookup(win2, EV_REPARENT) || ev_lookup(win2, EV_DESTROY)) {
					if (skipwins_n < skipwins_max) {
if (ncdb) fprintf(stderr, "SKIPWINS: CreateNotify: 0x%lx %d\n", win2, n1);
						skipwins[skipwins_n++] = win2;
					}
				} else {
					idx = lookup_win_index(win);
					if (idx < 0) {
						idx = lookup_free_index();
						if (idx < 0) {
							continue;
						}
						CLEAR(idx);
					}
if (ncdb) fprintf(stderr, "PRELOOP:  CreateNotify: 0x%lx %d valid_window\n", win2, n1);
					if (valid_window(win2, &attr, 1)) {
						STORE(idx, win2, attr);
						CLEAR(idx);
						cache_list[idx].selectinput = 1;
						cache_list[idx].create_cnt = 1;
if (ncdb) fprintf(stderr, "PRELOOP:  CreateNotify: 0x%lx %d xselectinput\n", win2, n1);
						xselectinput(win2, win_ev, 1);
						nxsel++;
					} else {
						DELETE(idx);
					}
					nxsel++;
				}
			} else if (ev.type == ReparentNotify) {
				if (ev.xreparent.parent != rootwin) {
					win2 = ev.xreparent.window;
					if (win2 != rootwin) {
						idx = lookup_win_index(win2);
if (ncdb) fprintf(stderr, "PRELOOP:  RepartNotify: 0x%lx %d idx=%d\n", win2, n1, idx);
						if (idx >= 0) {
							DELETE(idx);
						}
						if (! ev_lookup(win2, EV_CREATE)) {
							xselectinput(win2, 0, 1);
							nxsel++;
						}
					}
				}
			}
		}
		if (nxsel == 0) {
			break;
		}
	}
	X_UNLOCK;

	if (got_NET_CURRENT_DESKTOP > 0.0) {
		if (dnow() < got_NET_CURRENT_DESKTOP + 0.25) {
			if (ncdb) fprintf(stderr, "***got_NET_CURRENT_DESKTOP n=%d\n", n);
			desktop_change = 1;
			n_DC++;
		} else {
			if (ncdb) fprintf(stderr, "***got_NET_CURRENT_DESKTOP n=%d STALE\n", n);
		}
		got_NET_CURRENT_DESKTOP = 0.0;
	}

	if (n == 0) {
		check_sched(try_batch, &did_sched);
		return 0;
	}
if (ncdb) fprintf(stderr, "\n"); if (ncdb) rfbLog("IN  check_ncache() %d events.  %.4f\n", n, now - x11vnc_start);

	if (try_batch) {
		use_batch = 1;
	}

	if (rotating) {
		use_batch = 0;
	}
	if (cursor_noshape_updates_clients(screen)) {
		use_batch = 0;
	}

	if (! use_batch) {
		nbatch = NULL;
	} else {
		nreg = 0;
		nbatch = &nreg;
	}

	/* XXX Y */
	for (n1 = 0; n1 < n; n1++) {
		Window twin = Ev_map[n1];
		if (twin == None || twin == rootwin) {
			continue;
		}
		for (n2 = 0; n2 < n; n2++) {
			if (Ev_unmap[n2] == twin) {
				if (skipwins_n < skipwins_max) {
if (ncdb) fprintf(stderr, "SKIPWINS: Ev_unmap/map: 0x%lx %d\n", twin, n2);
					skipwins[skipwins_n++] = twin;
					break;
				}
			}
		}
	}

	if (!desktop_change) {
		if (skipwins_n) {
			if (n_MN + n_UN >= 2 + 2*skipwins_n) {
				desktop_change = 1;
				n_DC++;
			}
		} else {
			if (n_MN + n_UN >= 3) {
				desktop_change = 1;
				n_DC++;
			}
		}
	}
	if (ncache_old_wm) {
		int old_maps = 0; 
		int old_unmaps = 0; 
		int shifts = 0;
		for (i=0; i < n; i++) {
			XEvent ev;
			int ns, skip = 0, type, idx = -1, state, valid;
			int ik = Ev_order[i];
			int x_new, y_new, w_new, h_new;
			int x_old, y_old, w_old, h_old;
			int old_wm = 0;

			if (Ev_done[ik]) continue;
			win = Ev_win[ik];

			ev = Ev[ik];
			type = ev.type;
			if (type != ConfigureNotify) {
				continue;
			}
			if (ev_lookup(win, EV_MAP)) {
				continue;
			} else if (ev_lookup(win, EV_UNMAP)) {
				continue;
			} else if (ev_lookup(win, EV_DESTROY)) {
				continue;
			}

			idx = lookup_win_index(win);
			if (idx < 0) {
				continue;
			}
			x_new = ev.xconfigure.x; 
			y_new = ev.xconfigure.y; 
			w_new = ev.xconfigure.width; 
			h_new = ev.xconfigure.height; 

			x_old = cache_list[idx].x;
			y_old = cache_list[idx].y;
			w_old = cache_list[idx].width;
			h_old = cache_list[idx].height;

			if (w_new == w_old && h_new == h_old) {
				if (nabs(x_new - x_old) >= dpy_x || nabs(y_new - y_old) >= dpy_y) {
					sraRegionPtr r_old, r_new, r0;
					r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
					r_old = sraRgnCreateRect(x_old, y_old, x_old+w_old, y_old+h_old);
					sraRgnAnd(r_old, r0);
					r_new = sraRgnCreateRect(x_new, y_new, x_new+w_new, y_new+h_new);
					sraRgnAnd(r_new, r0);
					if (cache_list[idx].map_state != IsViewable) {
						ev_store(win, EV_OLD_WM_NOTMAPPED);
					} else if (sraRgnEmpty(r_old) && !sraRgnEmpty(r_new)) {
						old_wm = 1;
						ev_store(win, EV_OLD_WM_MAP);
						Ev_map[i] = win;
					} else if (!sraRgnEmpty(r_old) && sraRgnEmpty(r_new)) {
						ev_store(win, EV_OLD_WM_UNMAP);
						old_wm = -1;
						Ev_unmap[i] = win;
					} else {
						ev_store(win, EV_OLD_WM_OFF);
					}
					sraRgnDestroy(r_old);
					sraRgnDestroy(r_new);
					sraRgnDestroy(r0);
					shifts++;
if (ncdb) fprintf(stderr, "old_wm[%d]  +%04d+%04d  +%04d+%04d  old_wm: %d\n", i, x_old, y_old, x_new, y_new, old_wm);
				}
			}
		}
		if (shifts >= 3) {
if (ncdb) fprintf(stderr, "DESKTOP_CHANGE_OLD_WM: %d\n", shifts);
			desktop_change = 1;
			desktop_change_old_wm = 1;
		}
	}

#define SKIPUMS \
	ok = 1; \
	if (twin == None || twin == rootwin) { \
		continue; \
	} \
	for (ns = 0; ns < skipwins_n; ns++) { \
		if (skipwins[ns] == twin) { \
			ok = 0; \
			break; \
		} \
	}

	if (desktop_change) {
		Window twin;
		int ok, s, k, add, cnt, ns;

		cnt = 0;
		add = 0;
		for (i=0; i < n; i++) {
			twin = Ev_unmap[i];
			SKIPUMS
			if (ok) {
if (ncdb) fprintf(stderr, "U Ev_tmp[%d] = %d\n", cnt, i);
				Ev_tmp[cnt++] = i;
			}
		}
		for (i=0; i < n; i++) {
			twin = Ev_map[i];
			SKIPUMS
			if (ok) {
if (ncdb) fprintf(stderr, "M Ev_tmp[%d] = %d\n", cnt, i);
				Ev_tmp[cnt++] = i;
			}
		}
		for (k = 0; k < cnt; k++) {
			Ev_tmp2[k] = -1;
		}
		/* unmap from top to bottom */
		for (s = old_stack_n - 1; s >= 0; s--) {
			twin = old_stack[s];	
			if (twin == None || twin == rootwin) {
				continue;
			}
			for (k = 0; k < cnt; k++) {
				i = Ev_tmp[k];
				if (twin == Ev_unmap[i]) {
if (ncdb) fprintf(stderr, "U Ev_tmp2[%d] = %d\n", add, i);
					Ev_tmp2[add++] = i;
					break;
				}
			}
		}
		/* map from bottom to top */
		for (s = 0; s < old_stack_n; s++) {
			twin = old_stack[s];	
			if (twin == None || twin == rootwin) {
				continue;
			}
			for (k = 0; k < cnt; k++) {
				i = Ev_tmp[k];
				if (twin == Ev_map[i]) {
if (ncdb) fprintf(stderr, "M Ev_tmp2[%d] = %d\n", add, i);
					Ev_tmp2[add++] = i;
					break;
				}
			}
		}
		k = 0;
		for (i=0; i < n; i++) {
			Window wu, wm;
			int j;
			int oku = 0, okm = 0;
			wu = Ev_unmap[i];
			wm = Ev_map[i];
			ok = 0;
			if (wu != None && wu != rootwin) oku = 1;
			if (wm != None && wm != rootwin) okm = 1;
			if (!oku && !okm) {
				continue;
			}
			if (oku) {
				twin = wu;
				SKIPUMS
				if (!ok) {
					oku = 0;
				}
			}
			if (okm) {
				twin = wm;
				SKIPUMS
				if (!ok) {
					okm = 0;
				}
			}
			if (!oku && !okm) {
				continue;
			}
			j = Ev_tmp2[k++];
			if (j >= 0) {
if (ncdb) fprintf(stderr, "UM Ev_order[%d] = %d oku=%d okm=%d\n", i, j, oku, okm);
				Ev_order[i] = j;
			}
		}
	}

#if 0
	if (desktop_change) {
		Window twin;
		int ok, s, k, add, cnt, ns;

		cnt = 0;
		add = 0;
		for (i=0; i < n; i++) {
			twin = Ev_unmap[i];
			SKIPUMS
			if (ok) {
				Ev_tmp[cnt++] = i;
			}
		}
		for (k = 0; k < cnt; k++) {
			Ev_tmp2[k] = -1;
		}
		/* unmap from top to bottom */
		for (s = old_stack_n - 1; s >= 0; s--) {
			twin = old_stack[s];	
			for (k = 0; k < cnt; k++) {
				i = Ev_tmp[k];
				if (twin == Ev_unmap[i]) {
					Ev_tmp2[add++] = i;
					break;
				}
			}
		}
		k = 0;
		for (i=0; i < n; i++) {
			int j;
			twin = Ev_unmap[i];
			SKIPUMS
			if (ok) {
				j = Ev_tmp2[k++];
				if (j >= 0) {
					Ev_order[i] = j;
				}
			}
		}

		cnt = 0;
		add = 0;
		for (i=0; i < n; i++) {
			twin = Ev_map[i];
			SKIPUMS
			if (ok) {
				Ev_tmp[cnt++] = i;
			}
		}
		for (k = 0; k < cnt; k++) {
			Ev_tmp2[k] = -1;
		}
		/* map from bottom to top */
		for (s = 0; s < old_stack_n; s++) {
			twin = old_stack[s];	
			for (k = 0; k < cnt; k++) {
				i = Ev_tmp[k];
				if (twin == Ev_map[i]) {
					Ev_tmp2[add++] = i;
					break;
				}
			}
		}
		k = 0;
		for (i=0; i < n; i++) {
			int j;
			twin = Ev_map[i];
			SKIPUMS
			if (ok) {
				j = Ev_tmp2[k++];
				if (j >= 0) {
					Ev_order[i] = j;
				}
			}
		}
	}
#endif

	if (!desktop_change && (n_VN_p && !n_UN && (n_MN || n_ON_st))) {
		if (now < last_vis_unobs_time + 0.75 || now < last_vis_obs_time + 0.75) {
			;
		} else if (n_MN <= 2 && n_ON_st <= 1) {
			for (i=0; i < n; i++) {
				XEvent ev;
				int ns, skip = 0, type, idx = -1, state, valid;
				int ik = Ev_order[i];

				if (Ev_done[ik]) continue;
				win = Ev_win[ik];

				ev = Ev[ik];
				type = ev.type;
				if (type != VisibilityNotify) {
					continue;
				}

				state = ev.xvisibility.state;
				if (state == VisibilityUnobscured) {
					continue;
				}
				if (ev_lookup(win, EV_MAP)) {
					continue;
				} else if (ev_lookup(win, EV_UNMAP)) {
					continue;
				} else if (ev_lookup(win, EV_DESTROY)) {
					continue;
				}
				idx = lookup_win_index(win);

				if (idx < 0) {
					continue;
				}
				if (cache_list[idx].vis_state == VisibilityFullyObscured) {
					continue;
				}
				if (now < cache_list[idx].vis_unobs_time + 3.00 || now < cache_list[idx].vis_obs_time + 3.00) {
					continue;
				}

if (ncdb) fprintf(stderr, "----%02d: VisibilityNotify 0x%lx  %3d  (*PRELOOP*) state: %s U/P %d/%d\n", ik, win, idx, VState(state), n_VN_u, n_VN_p);
				valid = 0;
				bs_save(idx, nbatch, &attr, 1, 0, &valid, 1);
				if (valid) {
					STORE(idx, win, attr);
				} else {
					DELETE(idx);
				}

				cache_list[idx].vis_state = state;
				cache_list[idx].vis_obs_time = last_vis_obs_time = dnow();
				Ev_done[ik] = 1;
			}
		}
	}
	if (desktop_change) {
		if (ncache_dt_change) {
			if (ncdb) fprintf(stderr, "GUESSED DESKTOP CHANGE.\n");
			saw_desktop_change = 1;
		} else {
			if (ncdb) fprintf(stderr, "GUESSED DESKTOP CHANGE. Skipping.\n");
			desktop_change = 0;
		}
	}


	create_cnt = 0;
	missed_su_restore = 0;
	missed_bs_restore = 0;
	missed_su_restore_rgn = sraRgnCreate();
	missed_bs_restore_rgn = sraRgnCreate();
	r0 = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
	unmapped_rgn = sraRgnCreate();
	su_fix_cnt = 0;

for (k = 0; k < skipwins_n; k++) {
	if (ncdb) fprintf(stderr, "skipwins[%d] 0x%lx\n", k, skipwins[k]);
}

	X_LOCK;
	for (i=0; i < n; i++) {
		XEvent ev;
		int ns, skip = 0, type, idx = -1;
		int ik = Ev_order[i];

		if (Ev_done[ik]) continue;
		win = Ev_win[ik];

		ev = Ev[ik];
		type = ev.type;
		Ev_done[ik] = 1;

		win2 = win;
		if (win == rootwin) {
			if (type == CreateNotify) {
				win2 = ev.xcreatewindow.window;
			} else if (type == ReparentNotify) {
				win2 = ev.xreparent.window;
			}
		}
		for (ns = 0; ns < skipwins_n; ns++) {
			if (win2 == skipwins[ns]) {
				skip = 1;
				break;
			}
		}
		if (skip) {
if (ncdb) fprintf(stderr, "skip%02d: ** SpecialSkip   0x%lx/0x%lx type: %s\n", ik, win, win2, Etype(type));
			continue;
		}
		
		if (win == rootwin) {
			if (type == CreateNotify) {
				int x=0, y=0, w=0, h=0;
				valid = 0;
				win2 = ev.xcreatewindow.window;
				idx = lookup_win_index(win2);
				if (idx < 0) {
					continue;
				}
				if (cache_list[idx].valid) {
					valid = 1;
					x=cache_list[idx].x;
					y=cache_list[idx].y;
					w=cache_list[idx].width;
					h=cache_list[idx].height;
					if (w*h > 64 * 64 && ev_lookup(win2, EV_MAP)) {
						X_UNLOCK;
						valid = 1;
						su_save(idx, nbatch, &attr, 0, &valid, 1);
						STORE(idx, win2, attr);
						X_LOCK;
						if (! desktop_change) {
							SCHED(win2, 1) 
						}
						create_cnt++;
					}
				}
if (ncdb) fprintf(stderr, "root%02d: ** CreateNotify  0x%lx  %3d  -- %dx%d+%d+%d valid=%d\n", ik, win2, idx, w, h, x, y, valid);

			} else if (type == ReparentNotify) {
				if (ev.xreparent.parent != rootwin) {
					win2 = ev.xreparent.window;
					idx = lookup_win_index(win2);
if (ncdb) fprintf(stderr, "root%02d: ReparentNotifyRM 0x%lx  %3d\n", ik, win2, idx);
				}
			} else {
if (ncdb) fprintf(stderr, "root%02d: ** IgnoringRoot  0x%lx type: %s\n", ik, win, Etype(type));
			}
		} else {
			if (type == ConfigureNotify) {
				int x_new, y_new, w_new, h_new;
				int x_old, y_old, w_old, h_old;
				int stack_change, old_wm = 0;
				Window oabove = None;

				idx = lookup_win_index(win);

				if (idx >= 0) {
					oabove = cache_list[idx].above;
				}

if (ncdb) fprintf(stderr, "----%02d: ConfigureNotify  0x%lx  %3d  -- above: 0x%lx -> 0x%lx  %dx%d+%d+%d\n", ik, win, idx,
    oabove, ev.xconfigure.above, ev.xconfigure.width, ev.xconfigure.height, ev.xconfigure.x, ev.xconfigure.y);

				if (idx < 0) {
					continue;
				}

				x_new = ev.xconfigure.x; 
				y_new = ev.xconfigure.y; 
				w_new = ev.xconfigure.width; 
				h_new = ev.xconfigure.height; 

				x_old = cache_list[idx].x;
				y_old = cache_list[idx].y;
				w_old = cache_list[idx].width;
				h_old = cache_list[idx].height;

				if (desktop_change_old_wm) {
					if (ev_lookup(win, EV_OLD_WM_MAP)) {
						if (Ev_map[ik] == win) {
							old_wm = 1;
						} else {
							old_wm = 2;
						}
					} else if (ev_lookup(win, EV_OLD_WM_UNMAP)) {
						if (Ev_unmap[ik] == win) {
							old_wm = -1;
						} else {
							old_wm = 2;
						}
					} else if (ev_lookup(win, EV_OLD_WM_OFF)) {
						old_wm = 2;
					} else if (ev_lookup(win, EV_OLD_WM_NOTMAPPED)) {
						old_wm = 3;
					}
				}

				if (!old_wm)  {
					if (x_old != x_new || y_old != y_new) {
						/* invalidate su */
						cache_list[idx].su_time = 0.0;
if (ncdb) fprintf(stderr, "          INVALIDATE su: 0x%lx xy: +%d+%d  +%d+%d \n", win, x_old, y_old, x_new, y_new);
					}
					if (w_old != w_new || h_old != h_new) {
						/* invalidate bs */
						cache_list[idx].bs_time = 0.0;
if (ncdb) fprintf(stderr, "          INVALIDATE bs: 0x%lx wh:  %dx%d   %dx%d \n", win, w_old, h_old, w_new, h_new);
					}
				} else {
					int valid;
					X_UNLOCK;
					if (old_wm == 1) {
						/* XXX Y */
if (ncdb) fprintf(stderr, "          OLD_WM_MAP:    0x%lx wh:  %dx%d+%d+%d   %dx%d+%d+%d \n", win, w_old, h_old, x_old, y_old, w_new, h_new, x_new, y_new);
						valid = 0;
						bs_restore(idx, nbatch, NULL, &attr, 0, 0, &valid, 1);

					} else if (old_wm == -1) {
if (ncdb) fprintf(stderr, "          OLD_WM_UNMAP:  0x%lx wh:  %dx%d+%d+%d   %dx%d+%d+%d \n", win, w_old, h_old, x_old, y_old, w_new, h_new, x_new, y_new);
						valid = 1;
						su_restore(idx, nbatch, NULL, &attr, 1, 0, &valid, 1);
					} else {
if (ncdb) fprintf(stderr, "          OLD_WM_OFF::   0x%lx wh:  %dx%d+%d+%d   %dx%d+%d+%d  old_wm=%d\n", win, w_old, h_old, x_old, y_old, w_new, h_new, x_new, y_new, old_wm);
					}
					X_LOCK;
				}

				stack_change = 0;
				if (old_wm) {
					;
				} else if (cache_list[idx].above != ev.xconfigure.above) {
					stack_change = 1;
				} else if (x_new == x_old && y_new == y_old && w_new == w_old && h_new == h_old) {
					stack_change = 1;
				}
				if (stack_change) {
					int i2, ok = 1;
					for (i2=0; i2 < n; i2++)  {
						if (Ev_map[i2] == win) {
							ok = 0;
							break;
						}
					}
					if (ok) {
						if (n_MN == 0 && n_UN == 0) {
							if (su_fix_cnt > 0) {
								ok = 0;
if (ncdb) fprintf(stderr, "          CONF_IGNORE: Too many stacking changes: 0x%lx\n", win);
							}
						}
						
					}
					if (ok) {
						if (ev_lookup(ev.xconfigure.above, EV_UNMAP)) {
							if (ncdb) fprintf(stderr, "        skip try_to_fix_su for GNOME deiconify #1\n");
							if (dt_gnome) {
								gnome_animation = 1;
							}
							ok = 0;
						}
					}
					if (ok && dt_gnome) {
						if (valid_window(ev.xconfigure.above, &attr, 1)) {
							if (attr.map_state != IsViewable) {
								if (ncdb) fprintf(stderr, "        skip try_to_fix_su for GNOME deiconify #2\n");
								gnome_animation = 1;
								ok = 0;
							}
						}
					}
					if (ok) {
						int rc = try_to_fix_su(win, idx, ev.xconfigure.above, nbatch, NULL);	
						if (rc == 0 && su_fix_cnt == 0 && n_MN == 0 && n_UN == 0) {
							X_UNLOCK;
							try_to_synthesize_su(1, 1, nbatch);
							X_LOCK;
						}
						n_ST++;
						su_fix_cnt++;
					}
				}

				cache_list[idx].x = x_new;
				cache_list[idx].y = y_new;
				cache_list[idx].width = w_new;
				cache_list[idx].height = h_new;

				cache_list[idx].above = ev.xconfigure.above;
				cache_list[idx].time = dnow();

			} else if (type == VisibilityNotify) {
				int state = ev.xvisibility.state;
				idx = lookup_win_index(win);
if (ncdb) fprintf(stderr, "----%02d: VisibilityNotify 0x%lx  %3d  state: %s U/P %d/%d\n", ik, win, idx, VState(state), n_VN_u, n_VN_p);

				if (idx < 0) {
					continue;
				}
				if (desktop_change) {
					;
				} else if (macosx_console && n_VN_p == 0) {
					;	/* XXXX not working well yet with UnmapNotify ... */
				} else if (state == VisibilityUnobscured) {
					int ok = 1;
					if (ncache <= 2) {
						ok = 0;
					} else if (ev_lookup(win, EV_MAP)) {
						ok = 0;
					} else if (ev_lookup(win, EV_UNMAP)) {
						ok = 0;
					} else if (ev_lookup(win, EV_DESTROY)) {
						ok = 0;
					} else if (gnome_animation) {
						ok = 0;
					} else {
						/* this is for gnome iconify */
						int i2;
						for (i2=i+1; i2 < n; i2++) {
							int idx2, ik2 = Ev_order[i2];
							sraRegionPtr ro1, ro2;
							Window win2 = Ev_unmap[ik2];

							if (win2 == None) {
								continue;
							}
							idx2 = lookup_win_index(win2);
							if (idx2 < 0) {
								continue;
							}

							ro1 = idx_create_rgn(r0, idx);
							ro2 = idx_create_rgn(r0, idx2);

							if (sraRgnAnd(ro1, ro2)) {
								if (ncdb) fprintf(stderr, "        skip VisibilityUnobscured for GNOME iconify.\n");
								ok = 0;
							}
							sraRgnDestroy(ro1);
							sraRgnDestroy(ro2);
							if (! ok) {
								break;
							}
						}
					}
					if (ok) {
						int x2, y2, w2, h2;
						sraRegionPtr rmask = NULL;
						valid = 0;
						if (dnow() < cache_list[idx].vis_unobs_time + 3.00 && !sraRgnEmpty(unmapped_rgn)) {
							x2 = cache_list[idx].x;
							y2 = cache_list[idx].y;
							w2 = cache_list[idx].width;
							h2 = cache_list[idx].height;
							rmask = sraRgnCreateRect(x2, y2, x2+w2, y2+h2);
							sraRgnAnd(rmask, unmapped_rgn);
							if (sraRgnEmpty(rmask)) {
								sraRgnDestroy(rmask);
								rmask = NULL;
							}
						}
						if (ev_lookup(win, EV_CONFIGURE_SIZE)) {
							valid = valid_window(win, &attr, 1);
						} else {
							X_UNLOCK;
							bs_restore(idx, nbatch, rmask, &attr, 0, 1, &valid, 1);
							X_LOCK;
						}
						if (rmask != NULL) {
							sraRgnDestroy(rmask);
						}
						if (valid) {
							STORE(idx, win, attr);

							cache_list[idx].time = dnow();
							cache_list[idx].vis_cnt++;
							Ev_map[ik] = win;
							Ev_rects[nrects].x1 = cache_list[idx].x;
							Ev_rects[nrects].y1 = cache_list[idx].y;
							Ev_rects[nrects].x2 = cache_list[idx].width;
							Ev_rects[nrects].y2 = cache_list[idx].height;
							nrects++;
							SCHED(win, 1) 
						} else {
							DELETE(idx);
						}
					}
				}
				if (state == VisibilityUnobscured) {
					cache_list[idx].vis_unobs_time = last_vis_unobs_time = dnow();
				} else if (cache_list[idx].vis_state == VisibilityUnobscured) {
					cache_list[idx].vis_obs_time = last_vis_obs_time = dnow();
				}
				cache_list[idx].vis_state = state;

			} else if (type == MapNotify) {
				idx = lookup_win_index(win);
if (ncdb) fprintf(stderr, "----%02d: MapNotify        0x%lx  %3d\n", ik, win, idx);

				if (idx < 0) {
					continue;
				}

#if 0
/*
				if (cache_list[idx].map_state == IsUnmapped || desktop_change || macosx_console)
 */
#endif
				if (1) {
					X_UNLOCK;
					if (desktop_change) {
						/* XXX Y */
						int save = 1;
						sraRegionPtr r;
						if (cache_list[idx].su_time != 0.0) {
							save = 0;
						} else if (missed_su_restore) {
							r = idx_create_rgn(r0, idx);
							if (sraRgnAnd(r, missed_su_restore_rgn)) {
								save = 0;
							}
							sraRgnDestroy(r);
						} 
						if (missed_bs_restore) {
							r = idx_create_rgn(r0, idx);
							if (sraRgnAnd(r, missed_bs_restore_rgn)) {
								save = 0;
							}
							sraRgnDestroy(r);
						} 
						if (save) {
							valid = 0;
							su_save(idx, nbatch, &attr, 1, &valid, 1);
							if (valid) {
								STORE(idx, win, attr);
							}
						}
					} else {
						valid = 0;
						su_save(idx, nbatch, &attr, 0, &valid, 1);
						if (valid) {
							STORE(idx, win, attr);
						}
					}
					valid = 0;
					if (ev_lookup(win, EV_CONFIGURE_SIZE)) {
						X_LOCK;
						valid = valid_window(win, &attr, 1);
						X_UNLOCK;
						idx_add_rgn(missed_bs_restore_rgn, r0, idx);
						missed_bs_restore++;
					} else if (bs_restore(idx, nbatch, NULL, &attr, 0, 0, &valid, 1)) { /* XXX clip? */
						;
					} else {
						idx_add_rgn(missed_bs_restore_rgn, r0, idx);
						missed_bs_restore++;
					}
					if (valid) {
						STORE(idx, win, attr);
					}

					if (macosx_console) {
#ifdef MACOSX
						macosxCGS_follow_animation_win(win, -1, 1);
						if (valid_window(win, &attr, 1)) {
							STORE(idx, win, attr);
							SCHED(win, 1);
						}
						/* XXX Y */
						if (cache_list[idx].vis_state == -1)  {
							cache_list[idx].vis_state = VisibilityUnobscured;
						}
#endif
					}
					X_LOCK;
					pixels += cache_list[idx].width * cache_list[idx].height;
					cache_list[idx].time = dnow();
					cache_list[idx].map_cnt++;
					Ev_map[ik] = win;
					Ev_rects[nrects].x1 = cache_list[idx].x;
					Ev_rects[nrects].y1 = cache_list[idx].y;
					Ev_rects[nrects].x2 = cache_list[idx].width;
					Ev_rects[nrects].y2 = cache_list[idx].height;
					nrects++;

					if (! valid) {
						DELETE(idx);
					}
				}
				cache_list[idx].map_state = IsViewable;

			} else if (type == UnmapNotify) {
				int x2, y2, w2, h2;
				idx = lookup_win_index(win);
if (ncdb) fprintf(stderr, "----%02d: UnmapNotify      0x%lx  %3d\n", ik, win, idx);

				if (idx < 0) {
					continue;
				}
				if (macosx_console) {
					if (mode == 2) {
						cache_list[idx].map_state = IsViewable;
					}
				}

#if 0
/*
//				if (cache_list[idx].map_state == IsViewable || desktop_change || macosx_console)
 */
#endif
				if (1) {
					X_UNLOCK;
					if (desktop_change) {
						int save = 1;
						sraRegionPtr r;
						if (cache_list[idx].bs_time > 0.0) {
							save = 0;
						} else if (missed_su_restore) {
							r = idx_create_rgn(r0, idx);
							if (sraRgnAnd(r, missed_su_restore_rgn)) {
								save = 0;
							}
							sraRgnDestroy(r);
						} 
						if (missed_bs_restore) {
							r = idx_create_rgn(r0, idx);
							if (sraRgnAnd(r, missed_bs_restore_rgn)) {
								save = 0;
							}
							sraRgnDestroy(r);
						} 
						if (save) {
							valid = 0;
							bs_save(idx, nbatch, &attr, 1, 0, &valid, 1);
						}
					} else {
						valid = 0;
						bs_save(idx, nbatch, &attr, 1, 0, &valid, 1);
					}
					valid = 0;
					if (su_restore(idx, nbatch, NULL, &attr, 1, 0, &valid, 1)) {
						try_to_fix_su(win, idx, None, nbatch, "unmapped");	
						if (valid) {
							STORE(idx, win, attr);
						} else {
							DELETE(idx);
						}
					} else {
						idx_add_rgn(missed_su_restore_rgn, r0, idx);
						missed_su_restore++;
					}
					X_LOCK;

					pixels += cache_list[idx].width * cache_list[idx].height;
					cache_list[idx].time = dnow();
					cache_list[idx].unmap_cnt++;
					Ev_unmap[ik] = win;
					Ev_rects[nrects].x1 = cache_list[idx].x;
					Ev_rects[nrects].y1 = cache_list[idx].y;
					Ev_rects[nrects].x2 = cache_list[idx].width;
					Ev_rects[nrects].y2 = cache_list[idx].height;
					nrects++;
				}

				x2 = cache_list[idx].x;
				y2 = cache_list[idx].y;
				w2 = cache_list[idx].width;
				h2 = cache_list[idx].height;
				r = sraRgnCreateRect(x2, y2, x2+w2, y2+h2);
				sraRgnAnd(r, r0); 
				sraRgnOr(unmapped_rgn, r); 
				sraRgnDestroy(r);

				cache_list[idx].map_state = IsUnmapped;

			} else if (type == ReparentNotify) {
				if (ev.xreparent.parent != rootwin) {
					win2 = ev.xreparent.window;
					if (win2 != rootwin) {
						idx = lookup_win_index(win2);
if (ncdb) fprintf(stderr, "----%02d: ReparentNotifyRM 0x%lx  %3d\n", ik, win2, idx);
					}
				}

			} else if (type == DestroyNotify) {
				win2 = ev.xdestroywindow.window;
				idx = lookup_win_index(win2);
if (ncdb) fprintf(stderr, "----%02d: DestroyNotify    0x%lx  %3d\n", ik, win2, idx);

				if (idx >= 0) {
					DELETE(idx);
				}
			} else {
if (ncdb) fprintf(stderr, "igno%02d: ** Ignoring      0x%lx type: %s\n", ik, win, Etype(type));
			}

		}
	}
	X_UNLOCK;

	if (use_batch && nreg) {
		batch_push(nreg, -1.0);
	}
	if (nrects) {
		if (scaling) {
			push_borders(Ev_rects, nrects);
		}
	}

	check_sched(try_batch, &did_sched);

	if (n_CN || n_RN || n_DN || n_MN || n_UN || n_ST || n_DC || did_sched) {
		snap_old();
	}

	sraRgnDestroy(r0);
	sraRgnDestroy(missed_su_restore_rgn);
	sraRgnDestroy(missed_bs_restore_rgn);

if (ncdb) rfbLog("OUT check_ncache(): %.4f %.6f events: %d  pixels: %d\n", dnowx(), dnow() - now, n, pixels);
if (ncdb) fprintf(stderr, "\n");
	return pixels;
}
#endif

