diff options
Diffstat (limited to 'x11vnc/userinput.c')
-rw-r--r-- | x11vnc/userinput.c | 4293 |
1 files changed, 4293 insertions, 0 deletions
diff --git a/x11vnc/userinput.c b/x11vnc/userinput.c new file mode 100644 index 0000000..75f0fa4 --- /dev/null +++ b/x11vnc/userinput.c @@ -0,0 +1,4293 @@ +/* -- userinput.c -- */ + +#include "x11vnc.h" +#include "xwrappers.h" +#include "xdamage.h" +#include "xrecord.h" +#include "xinerama.h" +#include "win_utils.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" + +/* + * 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); +void 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); + + +static void get_client_regions(int *req, int *mod, int *cpy, int *num) ; +static void do_copyregion(sraRegionPtr region, int dx, int dy) ; +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); +static int push_scr_ev(double *age, int type, int bdpush, int bdx, int bdy, + int bdskinny); +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 frame, int x, int y, int w, int h, int dx, int dy, + int *obscured, sraRegionPtr extra_clip, double max_wait); +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); + + +/* + * 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) { + Window r, c; + XWindowAttributes attr; + Bool ret; + int rootx, rooty, wx, wy; + unsigned int mask; + + ret = XQueryPointer(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; +} + +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; + + 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; + } + 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; +} + +/* +WIREFRAME_PARMS "0xff,2,0,30+6+6+6,Alt,0.05+0.3+2.0,8" +shade,linewidth,percent,T+B+L+R,mods,t1+t2+t3+t4 + */ +#define LW_MAX 8 +static unsigned long wireframe_shade; +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)) { + 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; + } + } + 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; + static saveline_t *save[4]; + static int first = 1, len = 0; + int max = dpy_x > dpy_y ? dpy_x : dpy_y; + int sz, lw = wireframe_lw; + unsigned long shade = wireframe_shade; + int color = 0; + unsigned short us; + unsigned long ul; + + if (clipshift) { + x -= coff_x; + y -= coff_y; + } + /* no subwin for wireframe */ + + 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; + sz = (LW_MAX+1)*len*pixelsize; + save[i]->data = (char *) malloc(sz); + + /* + * 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 = main_fb + yu*main_bytes_per_line + + 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 (restore) { + return; + } + +if (0) fprintf(stderr, " DrawBox: %dx%d+%d+%d\n", w, h, x, y); + + /* + * 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 = main_fb + yu*main_bytes_per_line + + 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 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); +} + +#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"); \ +} + +static int push_scr_ev(double *age, int type, int bdpush, int bdx, int bdy, + int bdskinny) { + 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; + + /* 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\n", ypad, scr_ev[0].dy); + + 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 (try_copyrect(frame, x, y, w, h, dx, dy, &obscured, + tmpregion, waittime)) { + last_scroll_type = type; + dtime0(&last_scroll_event); + + do_fb_push++; + urgent_update = 1; + sraRgnDestroy(tmpregion); + +PUSH_TEST(0); + + } else { + 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); + 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 (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); + 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. + */ +static void do_copyregion(sraRegionPtr region, int dx, int dy) { + sraRectangleIterator *iter; + sraRect rect; + int Bpp = bpp/8; + int x1, y1, x2, y2, w, stride; + int sx1, sy1, sx2, sy2, sdx, sdy; + int req, mod, cpy, ncli; + char *dst, *src; + + last_copyrect = dnow(); + + if (!scaling || rfb_fb == main_fb) { + /* normal case */ + get_client_regions(&req, &mod, &cpy, &ncli); +if (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 (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 */ + stride = dpy_x * Bpp; + + iter = sraRgnGetReverseIterator(region, dx < 0, dy < 0); + while(sraRgnIteratorNext(iter, &rect)) { + int j; + + x1 = rect.x1; + y1 = rect.y1; + x2 = rect.x2; + y2 = rect.y2; + + w = (x2 - x1)*Bpp; + dst = main_fb + y1*stride + x1*Bpp; + src = main_fb + (y1-dy)*stride + (x1-dx)*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; + } + } + + 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; + + rfbDoCopyRect(screen, sx1, sy1, sx2, sy2, sdx, sdy); + } + sraRgnReleaseIterator(iter); +} + +void fb_push(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, dnow() - x11vnc_start); + } + +} + +void fb_push_wait(double max_wait, int flags) { + double tm, dt = 0.0; + int req, mod, cpy, ncli; + + 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) { + break; + } + + usleep(1000); + fb_push(); + dt += dtime(&tm); + } +} + +/* + * 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; + + 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(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(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); + 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 scroll_wheel = 0; + int btn4 = (1<<3); + int btn5 = (1<<4); + + 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; + } 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); + 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(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(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(dpy); + XFlush(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; + + if (! use_xrecord) { + 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; \ + } + +static int try_copyrect(Window frame, int x, int y, int w, int h, int dx, int dy, + int *obscured, sraRegionPtr extra_clip, double max_wait) { + + 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 + + get_client_regions(&req, &mod, &cpy, &ncli); + if (cpy) { + /* one is still pending... try to force it out: */ + fb_push_wait(max_wait, 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(0) > dt_bad_check + 5) { + char *dt = guess_desktop(); + if (!strcmp(dt, "kde")) { + dt_bad = 1; + } else if (!strcmp(dt, "xfce")) { + dt_bad = 1; + } else { + dt_bad = 0; + } + dt_bad_check = time(0); + } + + if (clipshift) { + x -= coff_x; + y -= coff_y; + } + if (subwin) { + x -= off_x; + y -= off_y; + } + + 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); + } + } + do_copyregion(rect, dx, dy); + 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; + 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); + + 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 (swin == frame) { + if (db2) { + saw_me = 1; fprintf(stderr, " ----------\n"); + } else { + break; + } + } +#if 0 +fprintf(stderr, "bo: %d/%lx\n", k, swin); +#endif + + /* skip some unwanted cases: */ + if (swin == None) { + continue; + } + if (swin < 10) { + ; /* 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 (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); + do_copyregion(shifted_region, dx, dy); + 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 (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); + 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; +} + +/* + * 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, orig_frame; + 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; + int already_down = 0, win_gone = 0, win_unmapped = 0; + double spin = 0.0, tm, last_ptr, last_draw; + int frame_changed = 0, drew_box = 0, got_2nd_pointer = 0; + int special_t1 = 0, break_reason = 0; + static double first_dt_ave = 0.0; + static int first_dt_cnt = 0; + static time_t last_save_stacklist = 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 (nofb) { + return 0; + } + if (subwin) { + return 0; /* don't even bother for -id case */ + } + if (! button_mask) { + return 0; /* no button pressed down */ + } + if (!use_threads && !got_pointer_input) { + return 0; /* need ptr input, e.g. button down, motion */ + } + +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: 0x%lx\n", frame); + X_UNLOCK; + return 0; + } + X_UNLOCK; +if (db) fprintf(stderr, "a: %d wf: %.3f A: %d\n", w*h, wireframe_frac, (dpy_x*dpy_y)); + + /* + * 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, orig_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) { +if (db) fprintf(stderr, "INTERIOR\n"); + 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 (! 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); + } + + g = got_pointer_input; + + while (1) { + + X_LOCK; + XFlush(dpy); + X_UNLOCK; + + /* try do induce/waitfor some more user input */ + if (use_threads) { + usleep(1000); + } else if (drew_box) { + rfbPE(1000); + } else { + rfbCFD(1000); + } + + 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; + } + } + + /* see if some pointer input occurred: */ + if (got_pointer_input > g) { +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; + + X_LOCK; + XFlush(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: 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 */ + drawit = 1; + } else if (w != box_w || h != box_h) { + /* resize since last */ + drawit = 1; + } + if (drawit) { + /* + * check time (to avoid too much + * animations on slow machines + * or links). + */ + if (spin > last_draw + min_draw || + ! drew_box) { + draw_box(x, y, w, h, 0); + drew_box = 1; + rfbPE(1000); + last_draw = spin; + } + } + 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. + */ + if (! button_mask) { +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; + return 0; + } + + /* remove the wireframe */ + draw_box(0, 0, 0, 0, 1); + + 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) { + ; + } else if (dx == 0 && dy == 0) { + ; + } else { + int spin_ms = (int) (spin * 1000 * 1000); + int obscured, sent_copyrect = 0; + + /* + * 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; + } + + /* try to flush the wireframe removal: */ + fb_push_wait(0.1, FB_COPY|FB_MOD); + + /* try to send a clipped copyrect of translation: */ + sent_copyrect = try_copyrect(frame, x, y, w, h, dx, dy, + &obscured, NULL, 0.15); + +if (db) fprintf(stderr, "send_copyrect: %d\n", sent_copyrect); + if (sent_copyrect) { + /* try to push the changes to viewers: */ + if (! obscured) { + fb_push_wait(0.1, FB_COPY); + } else { + /* no diff for now... */ + fb_push_wait(0.1, FB_COPY); + } + if (scaling) { + 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); + 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(0); + } else { + stack_list_num = 0; + } + } + + /* final push (for -nowirecopyrect) */ + rfbPE(1000); + wireframe_in_progress = 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; + } + + 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(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(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(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(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)) ) { + sent += cl->framebufferUpdateMessagesSent; + } + 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(0); + + 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(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(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 (raw_fb && ! dpy) return 0; /* raw_fb hack */ + + 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++; + X_LOCK; + XFlush(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++; + 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; +} + + |