/* Copyright (C) 2002-2010 Karl J. Runge 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 . 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 "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< 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< 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< " "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= 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= 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 \n" "#\n" "#\n" "# The 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" "# and are decimal integers reflecting the position on the screen\n" "# the event took place at.\n" "#\n" "# 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" "# 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 \n" "#\n" "#\n" "# The is as with Pointer.\n" "#\n" "# is a decimal either 1 or 0 indicating KeyPress or KeyRelease,\n" "# respectively.\n" "#\n" "# is a decimal integer incidating the Keysym of the event.\n" "#\n" "# 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 value is currently just None, KeyPress, or KeyRelease.\n" "#\n" "# In the future 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; }