| /* |
| Copyright (C) 2002-2010 Karl J. Runge <runge@karlrunge.com> |
| All rights reserved. |
| |
| This file is part of x11vnc. |
| |
| x11vnc is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or (at |
| your option) any later version. |
| |
| x11vnc is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with x11vnc; if not, write to the Free Software |
| Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA |
| or see <http://www.gnu.org/licenses/>. |
| |
| In addition, as a special exception, Karl J. Runge |
| gives permission to link the code of its release of x11vnc with the |
| OpenSSL project's "OpenSSL" library (or with modified versions of it |
| that use the same license as the "OpenSSL" library), and distribute |
| the linked executables. You must obey the GNU General Public License |
| in all respects for all of the code used other than "OpenSSL". If you |
| modify this file, you may extend this exception to your version of the |
| file, but you are not obligated to do so. If you do not wish to do |
| so, delete this exception statement from your version. |
| */ |
| |
| /* -- pointer.c -- */ |
| |
| #include "x11vnc.h" |
| #include "xwrappers.h" |
| #include "keyboard.h" |
| #include "xinerama.h" |
| #include "xrecord.h" |
| #include "win_utils.h" |
| #include "cursor.h" |
| #include "userinput.h" |
| #include "connections.h" |
| #include "cleanup.h" |
| #include "unixpw.h" |
| #include "v4l.h" |
| #include "linuxfb.h" |
| #include "uinput.h" |
| #include "scan.h" |
| #include "macosx.h" |
| #include "screen.h" |
| |
| int pointer_queued_sent = 0; |
| |
| void initialize_pointer_map(char *pointer_remap); |
| void do_button_mask_change(int mask, int button); |
| void pointer_event(int mask, int x, int y, rfbClientPtr client); |
| void initialize_pipeinput(void); |
| int check_pipeinput(void); |
| void update_x11_pointer_position(int x, int y); |
| |
| |
| static void buttonparse(int from, char **s); |
| static void update_x11_pointer_mask(int mask); |
| static void pipe_pointer(int mask, int x, int y, rfbClientPtr client); |
| |
| /* |
| * pointer event (motion and button click) handling routines. |
| */ |
| typedef struct ptrremap { |
| KeySym keysym; |
| KeyCode keycode; |
| int end; |
| int button; |
| int down; |
| int up; |
| } prtremap_t; |
| |
| #define MAX_BUTTON_EVENTS 50 |
| static prtremap_t pointer_map[MAX_BUTTONS+1][MAX_BUTTON_EVENTS]; |
| |
| /* |
| * For parsing the -buttonmap sections, e.g. "4" or ":Up+Up+Up:" |
| */ |
| static void buttonparse(int from, char **s) { |
| #if (0 && NO_X11) |
| if (!from || !s) {} |
| return; |
| #else |
| char *q; |
| int to, i; |
| int modisdown[256]; |
| |
| q = *s; |
| |
| for (i=0; i<256; i++) { |
| modisdown[i] = 0; |
| } |
| |
| if (*q == ':') { |
| /* :sym1+sym2+...+symN: format */ |
| int l = 0, n = 0; |
| char list[1000]; |
| char *t, *kp = q + 1; |
| KeyCode kcode; |
| |
| while (*(kp+l) != ':' && *(kp+l) != '\0') { |
| /* loop to the matching ':' */ |
| l++; |
| if (l >= 1000) { |
| rfbLog("buttonparse: keysym list too long: " |
| "%s\n", q); |
| break; |
| } |
| } |
| *(kp+l) = '\0'; |
| strncpy(list, kp, l); |
| list[l] = '\0'; |
| rfbLog("remap button %d using \"%s\"\n", from, list); |
| |
| /* loop over tokens separated by '+' */ |
| t = strtok(list, "+"); |
| while (t) { |
| KeySym ksym; |
| unsigned int ui; |
| int i; |
| if (n >= MAX_BUTTON_EVENTS - 20) { |
| rfbLog("buttonparse: too many button map " |
| "events: %s\n", list); |
| break; |
| } |
| if (sscanf(t, "0x%x", &ui) == 1) { |
| ksym = (KeySym) ui; /* hex value */ |
| } else { |
| X_LOCK; |
| ksym = XStringToKeysym(t); /* string value */ |
| X_UNLOCK; |
| } |
| if (ksym == NoSymbol) { |
| /* see if Button<N> "keysym" was used: */ |
| if (sscanf(t, "Button%d", &i) == 1) { |
| rfbLog(" event %d: button %d\n", |
| from, n+1, i); |
| if (i == 0) i = -1; /* bah */ |
| pointer_map[from][n].keysym = NoSymbol; |
| pointer_map[from][n].keycode = NoSymbol; |
| pointer_map[from][n].button = i; |
| pointer_map[from][n].end = 0; |
| pointer_map[from][n].down = 0; |
| pointer_map[from][n].up = 0; |
| } else { |
| rfbLog("buttonparse: ignoring unknown " |
| "keysym: %s\n", t); |
| n--; |
| } |
| } else if (dpy) { |
| /* |
| * XXX may not work with -modtweak or -xkb |
| */ |
| char *str; |
| X_LOCK; |
| #if NO_X11 |
| kcode = NoSymbol; |
| #else |
| kcode = XKeysymToKeycode(dpy, ksym); |
| #endif |
| |
| pointer_map[from][n].keysym = ksym; |
| pointer_map[from][n].keycode = kcode; |
| pointer_map[from][n].button = 0; |
| pointer_map[from][n].end = 0; |
| if (! ismodkey(ksym) ) { |
| /* do both down then up */ |
| pointer_map[from][n].down = 1; |
| pointer_map[from][n].up = 1; |
| } else { |
| if (modisdown[kcode]) { |
| pointer_map[from][n].down = 0; |
| pointer_map[from][n].up = 1; |
| modisdown[kcode] = 0; |
| } else { |
| pointer_map[from][n].down = 1; |
| pointer_map[from][n].up = 0; |
| modisdown[kcode] = 1; |
| } |
| } |
| str = XKeysymToString(ksym); |
| rfbLog(" event %d: keysym %s (0x%x) -> " |
| "keycode 0x%x down=%d up=%d\n", n+1, |
| str ? str : "null", ksym, kcode, |
| pointer_map[from][n].down, |
| pointer_map[from][n].up); |
| X_UNLOCK; |
| } |
| t = strtok(NULL, "+"); |
| n++; |
| } |
| |
| /* we must release any modifiers that are still down: */ |
| for (i=0; i<256; i++) { |
| kcode = (KeyCode) i; |
| if (n >= MAX_BUTTON_EVENTS) { |
| rfbLog("buttonparse: too many button map " |
| "events: %s\n", list); |
| break; |
| } |
| if (modisdown[kcode]) { |
| pointer_map[from][n].keysym = NoSymbol; |
| pointer_map[from][n].keycode = kcode; |
| pointer_map[from][n].button = 0; |
| pointer_map[from][n].end = 0; |
| pointer_map[from][n].down = 0; |
| pointer_map[from][n].up = 1; |
| modisdown[kcode] = 0; |
| n++; |
| } |
| } |
| |
| /* advance the source pointer position */ |
| (*s) += l+2; |
| } else { |
| /* single digit format */ |
| char str[2]; |
| str[0] = *q; |
| str[1] = '\0'; |
| |
| to = atoi(str); |
| if (to < 1) { |
| rfbLog("skipping invalid remap button \"%d\" for button" |
| " %d from string \"%s\"\n", |
| to, from, str); |
| } else { |
| rfbLog("remap button %d using \"%s\"\n", from, str); |
| rfbLog(" button: %d -> %d\n", from, to); |
| pointer_map[from][0].keysym = NoSymbol; |
| pointer_map[from][0].keycode = NoSymbol; |
| pointer_map[from][0].button = to; |
| pointer_map[from][0].end = 0; |
| pointer_map[from][0].down = 0; |
| pointer_map[from][0].up = 0; |
| } |
| /* advance the source pointer position */ |
| (*s)++; |
| } |
| #endif /* NO_X11 */ |
| } |
| |
| /* |
| * process the -buttonmap string |
| */ |
| void initialize_pointer_map(char *pointer_remap) { |
| unsigned char map[MAX_BUTTONS]; |
| int i, k; |
| /* |
| * This routine counts the number of pointer buttons on the X |
| * server (to avoid problems, even crashes, if a client has more |
| * buttons). And also initializes any pointer button remapping |
| * from -buttonmap option. |
| */ |
| |
| if (!raw_fb_str) { |
| #if NO_X11 |
| num_buttons = 5; |
| #else |
| X_LOCK; |
| num_buttons = XGetPointerMapping(dpy, map, MAX_BUTTONS); |
| X_UNLOCK; |
| rfbLog("The X server says there are %d mouse buttons.\n", num_buttons); |
| #endif |
| } else { |
| num_buttons = 5; |
| rfbLog("Manually set num_buttons to: %d\n", num_buttons); |
| } |
| |
| if (num_buttons < 0) { |
| num_buttons = 0; |
| } |
| |
| /* FIXME: should use info in map[] */ |
| for (i=1; i<= MAX_BUTTONS; i++) { |
| for (k=0; k < MAX_BUTTON_EVENTS; k++) { |
| pointer_map[i][k].end = 1; |
| } |
| pointer_map[i][0].keysym = NoSymbol; |
| pointer_map[i][0].keycode = NoSymbol; |
| pointer_map[i][0].button = i; |
| pointer_map[i][0].end = 0; |
| pointer_map[i][0].down = 0; |
| pointer_map[i][0].up = 0; |
| } |
| |
| if (pointer_remap && *pointer_remap != '\0') { |
| /* -buttonmap, format is like: 12-21=2 */ |
| char *p, *q, *remap = strdup(pointer_remap); |
| int n; |
| |
| if ((p = strchr(remap, '=')) != NULL) { |
| /* undocumented max button number */ |
| n = atoi(p+1); |
| *p = '\0'; |
| if (n < num_buttons || num_buttons == 0) { |
| num_buttons = n; |
| } else { |
| rfbLog("warning: increasing number of mouse " |
| "buttons from %d to %d\n", num_buttons, n); |
| num_buttons = n; |
| } |
| } |
| if ((q = strchr(remap, '-')) != NULL) { |
| /* |
| * The '-' separates the 'from' and 'to' lists, |
| * then it is kind of like tr(1). |
| */ |
| char str[2]; |
| int from; |
| |
| rfbLog("remapping pointer buttons using string:\n"); |
| rfbLog(" \"%s\"\n", remap); |
| |
| p = remap; |
| q++; |
| i = 0; |
| str[1] = '\0'; |
| while (*p != '-') { |
| str[0] = *p; |
| from = atoi(str); |
| buttonparse(from, &q); |
| p++; |
| } |
| } |
| free(remap); |
| } |
| } |
| |
| /* |
| * Send a pointer position event to the X server. |
| */ |
| void update_x11_pointer_position(int x, int y) { |
| #if NO_X11 |
| RAWFB_RET_VOID |
| if (!x || !y) {} |
| return; |
| #else |
| int rc; |
| |
| RAWFB_RET_VOID |
| |
| X_LOCK; |
| if (!always_inject && cursor_x == x && cursor_y == y) { |
| ; |
| } else if (use_xwarppointer) { |
| /* |
| * off_x and off_y not needed with XWarpPointer since |
| * window is used: |
| */ |
| XWarpPointer(dpy, None, window, 0, 0, 0, 0, x + coff_x, |
| y + coff_y); |
| } else { |
| XTestFakeMotionEvent_wr(dpy, scr, x + off_x + coff_x, |
| y + off_y + coff_y, CurrentTime); |
| } |
| X_UNLOCK; |
| |
| if (cursor_x != x || cursor_y != y) { |
| last_pointer_motion_time = dnow(); |
| } |
| |
| cursor_x = x; |
| cursor_y = y; |
| |
| /* record the x, y position for the rfb screen as well. */ |
| cursor_position(x, y); |
| |
| /* change the cursor shape if necessary */ |
| rc = set_cursor(x, y, get_which_cursor()); |
| cursor_changes += rc; |
| |
| last_event = last_input = last_pointer_input = time(NULL); |
| #endif /* NO_X11 */ |
| } |
| |
| void do_button_mask_change(int mask, int button) { |
| #if NO_X11 |
| if (!mask || !button) {} |
| return; |
| #else |
| int mb, k, i = button-1; |
| |
| /* |
| * this expands to any pointer_map button -> keystrokes |
| * remappings. Usually just k=0 and we send one button event. |
| */ |
| for (k=0; k < MAX_BUTTON_EVENTS; k++) { |
| int bmask = (mask & (1<<i)); |
| |
| if (pointer_map[i+1][k].end) { |
| break; |
| } |
| |
| if (pointer_map[i+1][k].button) { |
| /* send button up or down */ |
| |
| mb = pointer_map[i+1][k].button; |
| if ((num_buttons && mb > num_buttons) || mb < 1) { |
| rfbLog("ignoring mouse button out of " |
| "bounds: %d>%d mask: 0x%x -> 0x%x\n", |
| mb, num_buttons, button_mask, mask); |
| continue; |
| } |
| if (debug_pointer) { |
| rfbLog("pointer(): sending button %d" |
| " %s (event %d)\n", mb, bmask |
| ? "down" : "up", k+1); |
| } |
| XTestFakeButtonEvent_wr(dpy, mb, (mask & (1<<i)) |
| ? True : False, CurrentTime); |
| } else { |
| /* send keysym up or down */ |
| KeyCode key = pointer_map[i+1][k].keycode; |
| int up = pointer_map[i+1][k].up; |
| int down = pointer_map[i+1][k].down; |
| |
| if (! bmask) { |
| /* do not send keysym on button up */ |
| continue; |
| } |
| if (debug_pointer && dpy) { |
| char *str = XKeysymToString(XKeycodeToKeysym( |
| dpy, key, 0)); |
| rfbLog("pointer(): sending button %d " |
| "down as keycode 0x%x (event %d)\n", |
| i+1, key, k+1); |
| rfbLog(" down=%d up=%d keysym: " |
| "%s\n", down, up, str ? str : "null"); |
| } |
| if (down) { |
| XTestFakeKeyEvent_wr(dpy, key, True, |
| CurrentTime); |
| } |
| if (up) { |
| XTestFakeKeyEvent_wr(dpy, key, False, |
| CurrentTime); |
| } |
| } |
| } |
| #endif /* NO_X11 */ |
| } |
| |
| /* |
| * Send a pointer button event to the X server. |
| */ |
| static void update_x11_pointer_mask(int mask) { |
| #if NO_X11 |
| last_event = last_input = last_pointer_input = time(NULL); |
| |
| RAWFB_RET_VOID |
| if (!mask) {} |
| return; |
| #else |
| int snapped = 0, xr_mouse = 1, i; |
| last_event = last_input = last_pointer_input = time(NULL); |
| |
| RAWFB_RET_VOID |
| |
| if (mask != button_mask) { |
| last_pointer_click_time = dnow(); |
| } |
| |
| if (nofb) { |
| xr_mouse = 0; |
| } else if (!strcmp(scroll_copyrect, "never")) { |
| xr_mouse = 0; |
| } else if (!strcmp(scroll_copyrect, "keys")) { |
| xr_mouse = 0; |
| } else if (skip_cr_when_scaling("scroll")) { |
| xr_mouse = 0; |
| } else if (xrecord_skip_button(mask, button_mask)) { |
| xr_mouse = 0; |
| } |
| |
| if (mask && use_xrecord && ! xrecording && xr_mouse) { |
| static int px, py, x, y, w, h, got_wm_frame; |
| static XWindowAttributes attr; |
| Window frame = None, mwin = None; |
| int skip = 0; |
| |
| if (!button_mask) { |
| X_LOCK; |
| if (get_wm_frame_pos(&px, &py, &x, &y, &w, &h, |
| &frame, &mwin)) { |
| got_wm_frame = 1; |
| if (debug_scroll > 1) fprintf(stderr, "wm_win: 0x%lx\n", mwin); |
| if (mwin != None) { |
| if (!valid_window(mwin, &attr, 1)) { |
| mwin = None; |
| } |
| } |
| } else { |
| got_wm_frame = 0; |
| } |
| X_UNLOCK; |
| } |
| if (got_wm_frame) { |
| if (wireframe && near_wm_edge(x, y, w, h, px, py)) { |
| /* step out of wireframe's way */ |
| skip = 1; |
| } else { |
| int ok = 0; |
| int btn4 = (1<<3); |
| int btn5 = (1<<4); |
| |
| if (near_scrollbar_edge(x, y, w, h, px, py)) { |
| ok = 1; |
| } |
| if (mask & (btn4|btn5)) { |
| /* scroll wheel mouse */ |
| ok = 1; |
| } |
| if (mwin != None) { |
| /* skinny internal window */ |
| int w = attr.width; |
| int h = attr.height; |
| if (h > 10 * w || w > 10 * h) { |
| if (debug_scroll > 1) fprintf(stderr, "internal scrollbar: %dx%d\n", w, h); |
| ok = 1; |
| } |
| } |
| if (! ok) { |
| skip = 1; |
| } |
| } |
| } |
| |
| if (! skip) { |
| xrecord_watch(1, SCR_MOUSE); |
| snapshot_stack_list(0, 0.50); |
| snapped = 1; |
| if (button_mask) { |
| xrecord_set_by_mouse = 1; |
| } else { |
| update_stack_list(); |
| xrecord_set_by_mouse = 2; |
| } |
| } |
| } |
| |
| if (mask && !button_mask) { |
| /* button down, snapshot the stacking list before flushing */ |
| if (wireframe && !wireframe_in_progress && |
| strcmp(wireframe_copyrect, "never")) { |
| if (! snapped) { |
| snapshot_stack_list(0, 0.0); |
| } |
| } |
| } |
| |
| X_LOCK; |
| |
| /* look for buttons that have be clicked or released: */ |
| for (i=0; i < MAX_BUTTONS; i++) { |
| if ( (button_mask & (1<<i)) != (mask & (1<<i)) ) { |
| if (debug_pointer) { |
| rfbLog("pointer(): mask change: mask: 0x%x -> " |
| "0x%x button: %d\n", button_mask, mask,i+1); |
| } |
| do_button_mask_change(mask, i+1); /* button # is i+1 */ |
| } |
| } |
| |
| X_UNLOCK; |
| |
| /* |
| * Remember the button state for next time and also for the |
| * -nodragging case: |
| */ |
| button_mask_prev = button_mask; |
| button_mask = mask; |
| #endif /* NO_X11 */ |
| } |
| |
| /* for -pipeinput */ |
| |
| |
| static void pipe_pointer(int mask, int x, int y, rfbClientPtr client) { |
| int can_input = 0, uid = 0; |
| allowed_input_t input; |
| ClientData *cd = (ClientData *) client->clientData; |
| char hint[MAX_BUTTONS * 20]; |
| |
| if (pipeinput_int == PIPEINPUT_VID) { |
| v4l_pointer_command(mask, x, y, client); |
| } else if (pipeinput_int == PIPEINPUT_CONSOLE) { |
| console_pointer_command(mask, x, y, client); |
| } else if (pipeinput_int == PIPEINPUT_UINPUT) { |
| uinput_pointer_command(mask, x, y, client); |
| } else if (pipeinput_int == PIPEINPUT_MACOSX) { |
| macosx_pointer_command(mask, x, y, client); |
| } else if (pipeinput_int == PIPEINPUT_VNC) { |
| vnc_reflect_send_pointer(x, y, mask); |
| } |
| if (pipeinput_fh == NULL) { |
| return; |
| } |
| |
| if (! view_only) { |
| get_allowed_input(client, &input); |
| if (input.motion || input.button) { |
| can_input = 1; /* XXX distinguish later */ |
| } |
| } |
| |
| if (cd) { |
| uid = cd->uid; |
| } |
| if (! can_input) { |
| uid = -uid; |
| } |
| |
| hint[0] = '\0'; |
| if (mask == button_mask) { |
| strcat(hint, "None"); |
| } else { |
| int i, old, newb, m = 1, cnt = 0; |
| for (i=0; i<MAX_BUTTONS; i++) { |
| char s[20]; |
| |
| old = button_mask & m; |
| newb = mask & m; |
| m = m << 1; |
| |
| if (old == newb) { |
| continue; |
| } |
| if (hint[0] != '\0') { |
| strcat(hint, ","); |
| } |
| if (newb && ! old) { |
| sprintf(s, "ButtonPress-%d", i+1); |
| cnt++; |
| } else if (! newb && old) { |
| sprintf(s, "ButtonRelease-%d", i+1); |
| cnt++; |
| } |
| strcat(hint, s); |
| } |
| if (! cnt) { |
| strcpy(hint, "None"); |
| } |
| } |
| |
| fprintf(pipeinput_fh, "Pointer %d %d %d %d %s\n", uid, x, y, |
| mask, hint); |
| fflush(pipeinput_fh); |
| check_pipeinput(); |
| } |
| |
| /* |
| * Actual callback from libvncserver when it gets a pointer event. |
| * This may queue pointer events rather than sending them immediately |
| * to the X server. (see update_x11_pointer*()) |
| */ |
| void pointer_event(int mask, int x, int y, rfbClientPtr client) { |
| allowed_input_t input; |
| int sent = 0, buffer_it = 0; |
| double now; |
| |
| if (threads_drop_input) { |
| return; |
| } |
| |
| if (mask >= 0) { |
| got_pointer_calls++; |
| } |
| |
| if (debug_pointer && mask >= 0) { |
| static int show_motion = -1; |
| static double last_pointer = 0.0; |
| double tnow, dt; |
| static int last_x, last_y; |
| if (show_motion == -1) { |
| if (getenv("X11VNC_DB_NOMOTION")) { |
| show_motion = 0; |
| } else { |
| show_motion = 1; |
| } |
| } |
| dtime0(&tnow); |
| tnow -= x11vnc_start; |
| dt = tnow - last_pointer; |
| last_pointer = tnow; |
| if (show_motion) { |
| rfbLog("# pointer(mask: 0x%x, x:%4d, y:%4d) " |
| "dx: %3d dy: %3d dt: %.4f t: %.4f\n", mask, x, y, |
| x - last_x, y - last_y, dt, tnow); |
| } |
| last_x = x; |
| last_y = y; |
| } |
| |
| if (unixpw_in_progress) { |
| return; |
| } |
| |
| get_allowed_input(client, &input); |
| |
| if (rotating) { |
| rotate_coords_inverse(x, y, &x, &y, -1, -1); |
| } |
| |
| if (scaling) { |
| /* map from rfb size to X11 size: */ |
| x = ((double) x / scaled_x) * dpy_x; |
| x = nfix(x, dpy_x); |
| y = ((double) y / scaled_y) * dpy_y; |
| y = nfix(y, dpy_y); |
| } |
| |
| INPUT_LOCK; |
| |
| if ((pipeinput_fh != NULL || pipeinput_int) && mask >= 0) { |
| pipe_pointer(mask, x, y, client); /* MACOSX here. */ |
| if (! pipeinput_tee) { |
| if (! view_only || raw_fb) { /* raw_fb hack */ |
| got_user_input++; |
| got_pointer_input++; |
| last_pointer_client = client; |
| last_pointer_time = dnow(); |
| last_event = last_input = last_pointer_input = time(NULL); |
| } |
| if (input.motion) { |
| /* raw_fb hack track button state */ |
| button_mask_prev = button_mask; |
| button_mask = mask; |
| } |
| if (!view_only && (input.motion || input.button)) { |
| last_rfb_ptr_injected = dnow(); |
| } |
| INPUT_UNLOCK; |
| return; |
| } |
| } |
| |
| if (view_only) { |
| INPUT_UNLOCK; |
| return; |
| } |
| |
| now = dnow(); |
| |
| if (mask >= 0) { |
| /* |
| * mask = -1 is a special case call from scan_for_updates() |
| * to flush the event queue; there is no real pointer event. |
| */ |
| if (! input.motion && ! input.button) { |
| INPUT_UNLOCK; |
| return; |
| } |
| |
| got_user_input++; |
| got_pointer_input++; |
| last_pointer_client = client; |
| |
| last_pointer_time = now; |
| last_rfb_ptr_injected = dnow(); |
| |
| if (blackout_ptr && blackouts) { |
| int b, ok = 1; |
| /* see if it goes into the blacked out region */ |
| for (b=0; b < blackouts; b++) { |
| if (x < blackr[b].x1 || x > blackr[b].x2) { |
| continue; |
| } |
| if (y < blackr[b].y1 || y > blackr[b].y2) { |
| continue; |
| } |
| /* x1 <= x <= x2 and y1 <= y <= y2 */ |
| ok = 0; |
| break; |
| } |
| if (! ok) { |
| if (debug_pointer) { |
| rfbLog("pointer(): blackout_ptr skipping " |
| "x=%d y=%d in rectangle %d,%d %d,%d\n", x, y, |
| blackr[b].x1, blackr[b].y1, |
| blackr[b].x2, blackr[b].y2); |
| } |
| INPUT_UNLOCK; |
| return; |
| } |
| } |
| } |
| |
| /* |
| * The following is hopefully an improvement wrt response during |
| * pointer user input (window drags) for the threaded case. |
| * See check_user_input() for the more complicated things we do |
| * in the non-threaded case. |
| */ |
| if ((use_threads && pointer_mode != 1) || pointer_flush_delay > 0.0) { |
| # define NEV 32 |
| /* storage for the event queue */ |
| static int nevents = 0; |
| static int ev[NEV][3]; |
| int i; |
| /* timer things */ |
| static double dt = 0.0, tmr = 0.0, maxwait = 0.4; |
| |
| if (pointer_flush_delay > 0.0) { |
| maxwait = pointer_flush_delay; |
| } |
| if (mask >= 0) { |
| if (fb_copy_in_progress || pointer_flush_delay > 0.0) { |
| buffer_it = 1; |
| } |
| } |
| |
| POINTER_LOCK; |
| |
| /* |
| * If the framebuffer is being copied in another thread |
| * (scan_for_updates()), we will queue up to 32 pointer |
| * events for later. The idea is by delaying these input |
| * events, the screen is less likely to change during the |
| * copying period, and so will give rise to less window |
| * "tearing". |
| * |
| * Tearing is not completely eliminated because we do |
| * not suspend work in the other libvncserver threads. |
| * Maybe that is a possibility with a mutex... |
| */ |
| if (buffer_it) { |
| /* |
| * mask = -1 is an all-clear signal from |
| * scan_for_updates(). |
| * |
| * dt is a timer in seconds; we only queue for so long. |
| */ |
| dt += dtime(&tmr); |
| |
| if (nevents < NEV && dt < maxwait) { |
| i = nevents++; |
| ev[i][0] = mask; |
| ev[i][1] = x; |
| ev[i][2] = y; |
| if (! input.button) { |
| ev[i][0] = -1; |
| } |
| if (! input.motion) { |
| ev[i][1] = -1; |
| ev[i][2] = -1; |
| } |
| if (debug_pointer) { |
| rfbLog("pointer(): deferring event %d" |
| " %.4f\n", i, tmr - x11vnc_start); |
| } |
| POINTER_UNLOCK; |
| INPUT_UNLOCK; |
| return; |
| } |
| } |
| |
| /* time to send the queue */ |
| for (i=0; i<nevents; i++) { |
| int sent = 0; |
| if (mask < 0 && client != NULL) { |
| /* hack to only push the latest event */ |
| if (i < nevents - 1) { |
| if (debug_pointer) { |
| rfbLog("- skip deferred event:" |
| " %d\n", i); |
| } |
| continue; |
| } |
| } |
| if (debug_pointer) { |
| rfbLog("pointer(): sending event %d %.4f\n", |
| i+1, dnowx()); |
| } |
| if (ev[i][1] >= 0) { |
| update_x11_pointer_position(ev[i][1], ev[i][2]); |
| sent = 1; |
| } |
| if (ev[i][0] >= 0) { |
| update_x11_pointer_mask(ev[i][0]); |
| sent = 1; |
| } |
| |
| if (sent) { |
| pointer_queued_sent++; |
| } |
| } |
| if (nevents && dt > maxwait) { |
| if (dpy) { /* raw_fb hack */ |
| if (mask < 0) { |
| if (debug_pointer) { |
| rfbLog("pointer(): calling XFlush " |
| "%.4f\n", dnowx()); |
| } |
| X_LOCK; |
| XFlush_wr(dpy); |
| X_UNLOCK; |
| } |
| } |
| } |
| nevents = 0; /* reset everything */ |
| dt = 0.0; |
| dtime0(&tmr); |
| |
| POINTER_UNLOCK; |
| } |
| if (mask < 0) { /* -1 just means flush the event queue */ |
| if (debug_pointer) { |
| rfbLog("pointer(): flush only. %.4f\n", |
| dnowx()); |
| } |
| INPUT_UNLOCK; |
| return; |
| } |
| |
| /* update the X display with the event: */ |
| if (input.motion) { |
| update_x11_pointer_position(x, y); |
| sent = 1; |
| } |
| if (input.button) { |
| if (mask != button_mask) { |
| button_change_x = cursor_x; |
| button_change_y = cursor_y; |
| } |
| update_x11_pointer_mask(mask); |
| sent = 1; |
| } |
| |
| if (! dpy) { |
| ; |
| } else if (nofb && sent) { |
| /* |
| * nofb is for, e.g. Win2VNC, where fastest pointer |
| * updates are desired. |
| */ |
| X_LOCK; |
| XFlush_wr(dpy); |
| X_UNLOCK; |
| } else if (buffer_it) { |
| if (debug_pointer) { |
| rfbLog("pointer(): calling XFlush+" |
| "%.4f\n", dnowx()); |
| } |
| X_LOCK; |
| XFlush_wr(dpy); |
| X_UNLOCK; |
| } |
| INPUT_UNLOCK; |
| } |
| |
| void initialize_pipeinput(void) { |
| char *p = NULL; |
| |
| if (pipeinput_fh != NULL) { |
| rfbLog("closing pipeinput stream: %p\n", pipeinput_fh); |
| pclose(pipeinput_fh); |
| pipeinput_fh = NULL; |
| } |
| |
| pipeinput_tee = 0; |
| if (pipeinput_opts) { |
| free(pipeinput_opts); |
| pipeinput_opts = NULL; |
| } |
| |
| if (! pipeinput_str) { |
| return; |
| } |
| |
| /* look for options: tee, reopen, ... */ |
| if (strstr(pipeinput_str, "UINPUT") == pipeinput_str) { |
| ; |
| } else { |
| p = strchr(pipeinput_str, ':'); |
| } |
| if (p != NULL) { |
| char *str, *opt, *q; |
| int got = 0; |
| *p = '\0'; |
| str = strdup(pipeinput_str); |
| opt = strdup(pipeinput_str); |
| *p = ':'; |
| q = strtok(str, ","); |
| while (q) { |
| if (!strcmp(q, "key") || !strcmp(q, "keycodes")) { |
| got = 1; |
| } |
| if (!strcmp(q, "reopen")) { |
| got = 1; |
| } |
| if (!strcmp(q, "tee")) { |
| pipeinput_tee = 1; |
| got = 1; |
| } |
| q = strtok(NULL, ","); |
| } |
| if (got) { |
| pipeinput_opts = opt; |
| } else { |
| free(opt); |
| } |
| free(str); |
| p++; |
| } else { |
| p = pipeinput_str; |
| } |
| if (0) fprintf(stderr, "initialize_pipeinput: %s -- %s\n", pipeinput_str, p); |
| |
| if (!strcmp(p, "VID")) { |
| pipeinput_int = PIPEINPUT_VID; |
| return; |
| } else if (strstr(p, "CONSOLE") == p) { |
| int tty = 0, n; |
| char dev[32]; |
| if (sscanf(p, "CONSOLE%d", &n) == 1) { |
| tty = n; |
| } |
| sprintf(dev, "/dev/tty%d", tty); |
| pipeinput_cons_fd = open(dev, O_WRONLY); |
| if (pipeinput_cons_fd >= 0) { |
| rfbLog("pipeinput: using linux console: %s\n", dev); |
| if (pipeinput_cons_dev) { |
| free(pipeinput_cons_dev); |
| } |
| pipeinput_cons_dev = strdup(dev); |
| pipeinput_int = PIPEINPUT_CONSOLE; |
| } else { |
| rfbLog("pipeinput: could not open: %s\n", dev); |
| rfbLogPerror("open"); |
| rfbLog("You may need to be root to open %s.\n", dev); |
| rfbLog("\n"); |
| } |
| return; |
| } else if (strstr(p, "UINPUT") == p) { |
| char *q = strchr(p, ':'); |
| if (q) { |
| parse_uinput_str(q+1); |
| } |
| pipeinput_int = PIPEINPUT_UINPUT; |
| initialize_uinput(); |
| return; |
| } else if (strstr(p, "MACOSX") == p) { |
| pipeinput_int = PIPEINPUT_MACOSX; |
| return; |
| } else if (strstr(p, "VNC") == p) { |
| pipeinput_int = PIPEINPUT_VNC; |
| return; |
| } |
| |
| set_child_info(); |
| /* pipeinput */ |
| if (no_external_cmds || !cmd_ok("pipeinput")) { |
| rfbLogEnable(1); |
| rfbLog("cannot run external commands in -nocmds mode:\n"); |
| rfbLog(" \"%s\"\n", p); |
| rfbLog(" exiting.\n"); |
| clean_up_exit(1); |
| } |
| rfbLog("pipeinput: starting: \"%s\"...\n", p); |
| close_exec_fds(); |
| pipeinput_fh = popen(p, "w"); |
| |
| if (! pipeinput_fh) { |
| rfbLog("popen(\"%s\", \"w\") failed.\n", p); |
| rfbLogPerror("popen"); |
| rfbLog("Disabling -pipeinput mode.\n"); |
| return; |
| } |
| |
| fprintf(pipeinput_fh, "%s", |
| "# \n" |
| "# Format of the -pipeinput stream:\n" |
| "# --------------------------------\n" |
| "#\n" |
| "# Lines like these beginning with '#' are to be ignored.\n" |
| "#\n" |
| "# Pointer events (mouse motion and button clicks) come in the form:\n" |
| "#\n" |
| "#\n" |
| "# Pointer <client#> <x> <y> <mask> <hint>\n" |
| "#\n" |
| "#\n" |
| "# The <client#> is a decimal integer uniquely identifying the client\n" |
| "# that generated the event. If it is negative that means this event\n" |
| "# would have been discarded since the client was viewonly.\n" |
| "#\n" |
| "# <x> and <y> are decimal integers reflecting the position on the screen\n" |
| "# the event took place at.\n" |
| "#\n" |
| "# <mask> is the button mask indicating the button press state, as normal\n" |
| "# 0 means no buttons pressed, 1 means button 1 is down 3 (11) means buttons\n" |
| "# 1 and 2 are down, etc.\n" |
| "#\n" |
| "# <hint> is a string containing no spaces and may be ignored.\n" |
| "# It contains some interpretation about what has happened.\n" |
| "# It can be:\n" |
| "#\n" |
| "# None (nothing to report)\n" |
| "# ButtonPress-N (this event will cause button-N to be pressed) \n" |
| "# ButtonRelease-N (this event will cause button-N to be released) \n" |
| "#\n" |
| "# if two more more buttons change state in one event they are listed\n" |
| "# separated by commas.\n" |
| "#\n" |
| "# One might parse a Pointer line with:\n" |
| "#\n" |
| "# int client, x, y, mask; char hint[100];\n" |
| "# sscanf(line, \"Pointer %d %d %d %d %s\", &client, &x, &y, &mask, hint);\n" |
| "#\n" |
| "#\n" |
| "# Keysym events (keyboard presses and releases) come in the form:\n" |
| "#\n" |
| "#\n" |
| "# Keysym <client#> <down> <keysym#> <keysym-name> <hint>\n" |
| "#\n" |
| "#\n" |
| "# The <client#> is as with Pointer.\n" |
| "#\n" |
| "# <down> is a decimal either 1 or 0 indicating KeyPress or KeyRelease,\n" |
| "# respectively.\n" |
| "#\n" |
| "# <keysym#> is a decimal integer incidating the Keysym of the event.\n" |
| "#\n" |
| "# <keysym-name> is the corresponding Keysym name.\n" |
| "#\n" |
| "# See the file /usr/include/X11/keysymdef.h for the mappings.\n" |
| "# You basically remove the leading 'XK_' prefix from the macro name in\n" |
| "# that file to get the Keysym name.\n" |
| "#\n" |
| "# One might parse a Keysym line with:\n" |
| "#\n" |
| "# int client, down, keysym; char name[100], hint[100];\n" |
| "# sscanf(line, \"Keysym %d %d %d %s %s\", &client, &down, &keysym, name, hint);\n" |
| "#\n" |
| "# The <hint> value is currently just None, KeyPress, or KeyRelease.\n" |
| "#\n" |
| "# In the future <hint> will provide a hint for the sequence of KeyCodes\n" |
| "# (i.e. keyboard scancodes) that x11vnc would inject to an X display to\n" |
| "# simulate the Keysym.\n" |
| "#\n" |
| "# You see, some Keysyms will require more than one injected Keycode to\n" |
| "# generate the symbol. E.g. the Keysym \"ampersand\" going down usually\n" |
| "# requires a Shift key going down, then the key with the \"&\" on it going\n" |
| "# down, and, perhaps, the Shift key going up (that is how x11vnc does it).\n" |
| "#\n" |
| "# The Keysym => Keycode(s) stuff gets pretty messy. Hopefully the Keysym\n" |
| "# info will be enough for most purposes (having identical keyboards on\n" |
| "# both sides helps).\n" |
| "#\n" |
| "# Parsing example for perl:\n" |
| "#\n" |
| "# while (<>) {\n" |
| "# chomp;\n" |
| "# if (/^Pointer/) {\n" |
| "# my ($p, $client, $x, $y, $mask, $hint) = split(' ', $_, 6);\n" |
| "# do_pointer($client, $x, $y, $mask, $hint);\n" |
| "# } elsif (/^Keysym/) {\n" |
| "# my ($k, $client, $down, $keysym, $name, $hint) = split(' ', $_, 6);\n" |
| "# do_keysym($client, $down, $keysym, $name, $hint);\n" |
| "# }\n" |
| "# }\n" |
| "#\n" |
| "#\n" |
| "# Here comes your stream. The following token will always indicate the\n" |
| "# end of this informational text:\n" |
| "# END_OF_TOP\n" |
| ); |
| fflush(pipeinput_fh); |
| if (raw_fb_str) { |
| /* the pipe program may actually create the fb */ |
| sleep(1); |
| } |
| } |
| |
| int check_pipeinput(void) { |
| if (! pipeinput_fh) { |
| return 1; |
| } |
| if (ferror(pipeinput_fh)) { |
| rfbLog("pipeinput pipe has ferror. %p\n", pipeinput_fh); |
| |
| if (pipeinput_opts && strstr(pipeinput_opts, "reopen")) { |
| rfbLog("restarting -pipeinput pipe...\n"); |
| initialize_pipeinput(); |
| if (pipeinput_fh) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } else { |
| rfbLog("closing -pipeinput pipe...\n"); |
| pclose(pipeinput_fh); |
| pipeinput_fh = NULL; |
| return 0; |
| } |
| } |
| return 1; |
| } |
| |
| |