/* -- xevents.c -- */ #include "x11vnc.h" #include "xwrappers.h" #include "xkb_bell.h" #include "xrandr.h" #include "xdamage.h" #include "xrecord.h" #include "selection.h" #include "keyboard.h" #include "cursor.h" #include "gui.h" #include "connections.h" #include "unixpw.h" /* XXX CHECK BEFORE RELEASE */ int grab_buster = 0; int sync_tod_delay = 3; void initialize_vnc_connect_prop(void); void initialize_x11vnc_remote_prop(void); void spawn_grab_buster(void); void sync_tod_with_servertime(void); void check_keycode_state(void); void check_autorepeat(void); void check_xevents(void); void xcut_receive(char *text, int len, rfbClientPtr cl); static void initialize_xevents(void); static void print_xevent_bases(void); static void get_prop(char *str, int len, Atom prop); static void bust_grab(int reset); static int process_watch(char *str, int parent, int db); static void grab_buster_watch(int parent, char *dstr); void initialize_vnc_connect_prop(void) { vnc_connect_str[0] = '\0'; vnc_connect_prop = XInternAtom(dpy, "VNC_CONNECT", False); } void initialize_x11vnc_remote_prop(void) { x11vnc_remote_str[0] = '\0'; x11vnc_remote_prop = XInternAtom(dpy, "X11VNC_REMOTE", False); } static void initialize_xevents(void) { static int did_xselect_input = 0; static int did_xcreate_simple_window = 0; static int did_vnc_connect_prop = 0; static int did_x11vnc_remote_prop = 0; static int did_xfixes = 0; static int did_xdamage = 0; static int did_xrandr = 0; if ((watch_selection || vnc_connect) && !did_xselect_input) { /* * register desired event(s) for notification. * PropertyChangeMask is for CUT_BUFFER0 changes. * XXX: does this cause a flood of other stuff? */ X_LOCK; XSelectInput(dpy, rootwin, PropertyChangeMask); X_UNLOCK; did_xselect_input = 1; } if (watch_selection && !did_xcreate_simple_window) { /* create fake window for our selection ownership, etc */ X_LOCK; selwin = XCreateSimpleWindow(dpy, rootwin, 0, 0, 1, 1, 0, 0, 0); X_UNLOCK; did_xcreate_simple_window = 1; } if (xrandr && !did_xrandr) { initialize_xrandr(); did_xrandr = 1; } if (vnc_connect && !did_vnc_connect_prop) { initialize_vnc_connect_prop(); did_vnc_connect_prop = 1; } if (vnc_connect && !did_x11vnc_remote_prop) { initialize_x11vnc_remote_prop(); did_x11vnc_remote_prop = 1; } if (run_gui_pid > 0) { kill(run_gui_pid, SIGUSR1); run_gui_pid = 0; } if (xfixes_present && use_xfixes && !did_xfixes) { initialize_xfixes(); did_xfixes = 1; } if (xdamage_present && !did_xdamage) { initialize_xdamage(); did_xdamage = 1; } } static void print_xevent_bases(void) { fprintf(stderr, "X event bases: xkb=%d, xtest=%d, xrandr=%d, " "xfixes=%d, xdamage=%d, xtrap=%d\n", xkb_base_event_type, xtest_base_event_type, xrandr_base_event_type, xfixes_base_event_type, xdamage_base_event_type, xtrap_base_event_type); fprintf(stderr, " MapNotify=%d, ClientMsg=%d PropNotify=%d " "SelNotify=%d, SelRequest=%d\n", MappingNotify, ClientMessage, PropertyNotify, SelectionNotify, SelectionRequest); fprintf(stderr, " SelClear=%d, Expose=%d\n", SelectionClear, Expose); } static void get_prop(char *str, int len, Atom prop) { Atom type; int format, slen, dlen, i; unsigned long nitems = 0, bytes_after = 0; unsigned char* data = NULL; for (i=0; i len) { /* too big */ XFree(data); break; } memcpy(str+slen, data, dlen); slen += dlen; str[slen] = '\0'; XFree(data); } } while (bytes_after > 0); } static void bust_grab(int reset) { static int bust_count = 0; static time_t last_bust = 0; time_t now = time(0); KeyCode key; int button, x, y, nb; if (now > last_bust + 180) { bust_count = 0; } if (reset) { bust_count = 0; return; } x = 0; y = 0; button = 0; key = NoSymbol; nb = 8; if (bust_count >= 3 * nb) { fprintf(stderr, "too many bust_grab's %d for me\n", bust_count); exit(0); } if (bust_count % nb == 0) { button = 1; } else if (bust_count % nb == 1) { button = 1; } else if (bust_count % nb == 2) { key = XKeysymToKeycode(dpy, XK_Escape); } else if (bust_count % nb == 3) { button = 3; } else if (bust_count % nb == 4) { key = XKeysymToKeycode(dpy, XK_space); } else if (bust_count % nb == 5) { x = bust_count * 23; y = bust_count * 17; } else if (bust_count % nb == 5) { button = 2; } else if (bust_count % nb == 6) { key = XKeysymToKeycode(dpy, XK_a); } if (key == NoSymbol) { key = XKeysymToKeycode(dpy, XK_a); if (key == NoSymbol) { button = 1; } } bust_count++; if (button) { /* try button press+release */ fprintf(stderr, "**bust_grab: button%d %.4f\n", button, dnowx()); XTestFakeButtonEvent_wr(dpy, button, True, CurrentTime); XFlush(dpy); usleep(50 * 1000); XTestFakeButtonEvent_wr(dpy, button, False, CurrentTime); } else if (x > 0) { /* try button motion*/ int scr = DefaultScreen(dpy); fprintf(stderr, "**bust_grab: x=%d y=%d %.4f\n", x, y, dnowx()); XTestFakeMotionEvent_wr(dpy, scr, x, y, CurrentTime); XFlush(dpy); usleep(50 * 1000); /* followed by button press */ button = 1; fprintf(stderr, "**bust_grab: button%d\n", button); XTestFakeButtonEvent_wr(dpy, button, True, CurrentTime); XFlush(dpy); usleep(50 * 1000); XTestFakeButtonEvent_wr(dpy, button, False, CurrentTime); } else { /* try Escape or Space press+release */ fprintf(stderr, "**bust_grab: keycode: %d %.4f\n", (int) key, dnowx()); XTestFakeKeyEvent_wr(dpy, key, True, CurrentTime); XFlush(dpy); usleep(50 * 1000); XTestFakeKeyEvent_wr(dpy, key, False, CurrentTime); } XFlush(dpy); last_bust = time(0); } typedef struct _grabwatch { int pid; int tick; unsigned long time; time_t change; } grabwatch_t; #define GRABWATCH 16 static int grab_buster_delay = 20; static pid_t grab_buster_pid = 0; static int grab_npids = 1; static int process_watch(char *str, int parent, int db) { int i, pid, ticker, npids; char diff[128]; unsigned long xtime; static grabwatch_t watches[GRABWATCH]; static int first = 1; time_t now = time(0); static time_t last_bust = 0; int too_long, problems = 0; if (first) { for (i=0; i < GRABWATCH; i++) { watches[i].pid = 0; watches[i].tick = 0; watches[i].time = 0; watches[i].change = 0; } first = 0; } /* record latest value of prop */ if (str && *str != '\0') { if (sscanf(str, "%d/%d/%lu/%s", &pid, &ticker, &xtime, diff) == 4) { int got = -1, free = -1; if (db) fprintf(stderr, "grab_buster %d - %d - %lu - %s" "\n", pid, ticker, xtime, diff); if (pid == parent && !strcmp(diff, "QUIT")) { /* that's it. */ return 0; } if (pid == 0 || ticker == 0 || xtime == 0) { /* bad prop read. */ goto badtickerstr; } for (i=0; i < GRABWATCH; i++) { if (watches[i].pid == pid) { got = i; break; } if (free == -1 && watches[i].pid == 0) { free = i; } } if (got == -1) { if (free == -1) { /* bad news */; free = GRABWATCH - 1; } watches[free].pid = pid; watches[free].tick = ticker; watches[free].time = xtime; watches[free].change = now; if (db) fprintf(stderr, "grab_buster free slot: %d\n", free); } else { if (db) fprintf(stderr, "grab_buster got slot: %d\n", got); if (watches[got].tick != ticker) { watches[got].change = now; } if (watches[got].time != xtime) { watches[got].change = now; } watches[got].tick = ticker; watches[got].time = xtime; } } else { if (db) fprintf(stderr, "grab_buster bad prop str: %s\n", str); } } badtickerstr: too_long = grab_buster_delay; if (too_long < 3 * sync_tod_delay) { too_long = 3 * sync_tod_delay; } npids = 0; for (i=0; i < GRABWATCH; i++) { if (watches[i].pid) { npids++; } } grab_npids = npids; if (npids > 4) { npids = 4; } /* now check everyone we are tracking */ for (i=0; i < GRABWATCH; i++) { int fac = 1; if (!watches[i].pid) { continue; } if (watches[i].change == 0) { watches[i].change = now; /* just to be sure */ continue; } pid = watches[i].pid; if (pid != parent) { fac = 2; } if (npids > 0) { fac *= npids; } if (now > watches[i].change + fac*too_long) { int process_alive = 1; fprintf(stderr, "grab_buster: problem with pid: " "%d - %d/%d/%d\n", pid, (int) now, (int) watches[i].change, too_long); if (kill((pid_t) pid, 0) != 0) { if (1 || errno == ESRCH) { process_alive = 0; } } if (!process_alive) { watches[i].pid = 0; watches[i].tick = 0; watches[i].time = 0; watches[i].change = 0; fprintf(stderr, "grab_buster: pid gone: %d\n", pid); if (pid == parent) { /* that's it */ return 0; } } else { int sleep = sync_tod_delay * 1000 * 1000; bust_grab(0); problems++; last_bust = now; usleep(1 * sleep); break; } } } if (!problems) { bust_grab(1); } return 1; } static void grab_buster_watch(int parent, char *dstr) { Atom ticker_atom = None; int sleep = sync_tod_delay * 921 * 1000; char propval[200]; int ev, er, maj, min; int db = 0; if (grab_buster > 1) { db = 1; } /* overwrite original dpy, we let orig connection sit unused. */ dpy = XOpenDisplay(dstr); if (!dpy) { fprintf(stderr, "grab_buster_watch: could not reopen: %s\n", dstr); return; } rfbLogEnable(0); /* check for XTEST, etc, and then disable grabs for us */ if (! XTestQueryExtension_wr(dpy, &ev, &er, &maj, &min)) { xtest_present = 0; } else { xtest_present = 1; } if (! XETrapQueryExtension_wr(dpy, &ev, &er, &maj)) { xtrap_present = 0; } else { xtrap_present = 1; } if (! xtest_present && ! xtrap_present) { fprintf(stderr, "grab_buster_watch: no grabserver " "protection on display: %s\n", dstr); return; } disable_grabserver(dpy, 0); usleep(3 * sleep); ticker_atom = XInternAtom(dpy, "X11VNC_TICKER", False); if (! ticker_atom) { fprintf(stderr, "grab_buster_watch: no ticker atom\n"); return; } while(1) { int slp = sleep; if (grab_npids > 1) { slp = slp / 8; } usleep(slp); usleep((int) (0.60 * rfac() * slp)); if (kill((pid_t) parent, 0) != 0) { break; } get_prop(propval, 128, ticker_atom); if (db) fprintf(stderr, "got_prop: %s\n", propval); if (!process_watch(propval, parent, db)) { break; } } } void spawn_grab_buster(void) { #if LIBVNCSERVER_HAVE_FORK pid_t pid; int parent = (int) getpid(); char *dstr = strdup(DisplayString(dpy)); XCloseDisplay(dpy); dpy = NULL; if ((pid = fork()) > 0) { grab_buster_pid = pid; if (! quiet) { rfbLog("grab_buster pid is: %d\n", (int) pid); } } else if (pid == -1) { fprintf(stderr, "spawn_grab_buster: could not fork\n"); rfbLogPerror("fork"); } else { signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGTERM, SIG_DFL); grab_buster_watch(parent, dstr); exit(0); } dpy = XOpenDisplay(dstr); if (!dpy) { rfbLog("failed to reopen display %s in spawn_grab_buster\n", dstr); exit(1); } #endif } void sync_tod_with_servertime(void) { static Atom ticker_atom = None; XEvent xev; char diff[128]; static int seq = 0; static unsigned long xserver_ticks = 1; int i, db = 0; if (! ticker_atom) { ticker_atom = XInternAtom(dpy, "X11VNC_TICKER", False); } if (! ticker_atom) { return; } XSync(dpy, False); while (XCheckTypedEvent(dpy, PropertyNotify, &xev)) { ; } snprintf(diff, 128, "%d/%08d/%lu/%.6f", (int) getpid(), seq++, xserver_ticks, servertime_diff); XChangeProperty(dpy, rootwin, ticker_atom, XA_STRING, 8, PropModeReplace, (unsigned char *) diff, strlen(diff)); XSync(dpy, False); for (i=0; i < 10; i++) { int k, got = 0; for (k=0; k < 5; k++) { while (XCheckTypedEvent(dpy, PropertyNotify, &xev)) { if (xev.xproperty.atom == ticker_atom) { double stime; xserver_ticks = xev.xproperty.time; stime = (double) xev.xproperty.time; stime = stime/1000.0; servertime_diff = dnow() - stime; if (db) rfbLog("set servertime_diff: " "%.6f\n", servertime_diff); got = 1; } } } if (got) { break; } usleep(1000); } } void check_keycode_state(void) { static time_t last_check = 0; int delay = 10, noinput = 3; time_t now = time(0); if (! client_count) { return; } if (raw_fb && ! dpy) return; /* raw_fb hack */ if (unixpw_in_progress) return; /* * periodically update our model of the keycode_state[] * by correlating with the Xserver. wait for a pause in * keyboard input to be on the safe side. the idea here * is to remove stale keycode state, not to be perfectly * in sync with the Xserver at every instant of time. */ if (now > last_check + delay && now > last_keyboard_input + noinput) { init_track_keycode_state(); last_check = now; } } void check_autorepeat(void) { static time_t last_check = 0; time_t now = time(0); int autorepeat_is_on, autorepeat_initially_on, idle_timeout = 300; static int idle_reset = 0; if (! no_autorepeat || ! client_count) { return; } if (now <= last_check + 1) { return; } if (unixpw_in_progress) return; last_check = now; autorepeat_is_on = get_autorepeat_state(); autorepeat_initially_on = get_initial_autorepeat_state(); if (view_only) { if (! autorepeat_is_on) { autorepeat(1, 1); } return; } if (now > last_keyboard_input + idle_timeout) { /* autorepeat should be on when idle */ if (! autorepeat_is_on && autorepeat_initially_on) { static time_t last_msg = 0; static int cnt = 0; if (now > last_msg + idle_timeout && cnt++ < 5) { rfbLog("idle keyboard: turning X autorepeat" " back on.\n"); last_msg = now; } autorepeat(1, 1); idle_reset = 1; } } else { if (idle_reset) { static time_t last_msg = 0; static int cnt = 0; if (now > last_msg + idle_timeout && cnt++ < 5) { rfbLog("active keyboard: turning X autorepeat" " off.\n"); last_msg = now; } autorepeat(0, 1); idle_reset = 0; } else if (no_repeat_countdown && autorepeat_is_on) { int n = no_repeat_countdown - 1; if (n >= 0) { rfbLog("Battling with something for " "-norepeat!! (%d resets left)\n", n); } else { rfbLog("Battling with something for " "-norepeat!!\n"); } if (no_repeat_countdown > 0) { no_repeat_countdown--; } autorepeat(1, 0); autorepeat(0, 0); } } } /* * This routine is periodically called to check for selection related * and other X11 events and respond to them as needed. */ void check_xevents(void) { XEvent xev; int tmp, have_clients = 0; static int sent_some_sel = 0; static time_t last_request = 0; static time_t last_call = 0; static time_t last_bell = 0; static time_t last_init_check = 0; static time_t last_sync = 0; static time_t last_time_sync = 0; time_t now = time(0); if (raw_fb && ! dpy) return; /* raw_fb hack */ if (unixpw_in_progress) return; if (now > last_init_check+1) { last_init_check = now; initialize_xevents(); } if (screen && screen->clientHead) { have_clients = 1; } X_LOCK; /* * There is a bug where we have to wait before sending text to * the client... so instead of sending right away we wait a * the few seconds. */ if (have_clients && watch_selection && !sent_some_sel && now > last_client + sel_waittime) { if (XGetSelectionOwner(dpy, XA_PRIMARY) == None) { cutbuffer_send(); } sent_some_sel = 1; } if (! have_clients) { /* * If we don't have clients we can miss the X server * going away until a client connects. */ static time_t last_X_ping = 0; if (now > last_X_ping + 5) { last_X_ping = now; XGetSelectionOwner(dpy, XA_PRIMARY); } } if (now > last_call+1) { /* we only check these once a second or so. */ int n = 0; while (XCheckTypedEvent(dpy, MappingNotify, &xev)) { XRefreshKeyboardMapping((XMappingEvent *) &xev); n++; } if (n && use_modifier_tweak) { X_UNLOCK; initialize_modtweak(); X_LOCK; } if (xtrap_base_event_type) { int base = xtrap_base_event_type; while (XCheckTypedEvent(dpy, base, &xev)) { ; } } if (xtest_base_event_type) { int base = xtest_base_event_type; while (XCheckTypedEvent(dpy, base, &xev)) { ; } } /* * we can get ClientMessage from our XSendEvent() call in * selection_request(). */ while (XCheckTypedEvent(dpy, ClientMessage, &xev)) { ; } } /* check for CUT_BUFFER0 and VNC_CONNECT changes: */ if (XCheckTypedEvent(dpy, PropertyNotify, &xev)) { if (xev.type == PropertyNotify) { if (xev.xproperty.atom == XA_CUT_BUFFER0) { /* * Go retrieve CUT_BUFFER0 and send it. * * set_cutbuffer is a flag to try to avoid * processing our own cutbuffer changes. */ if (have_clients && watch_selection && ! set_cutbuffer) { cutbuffer_send(); sent_some_sel = 1; } set_cutbuffer = 0; } else if (vnc_connect && vnc_connect_prop != None && xev.xproperty.atom == vnc_connect_prop) { /* * Go retrieve VNC_CONNECT string. */ read_vnc_connect_prop(0); } else if (vnc_connect && x11vnc_remote_prop != None && xev.xproperty.atom == x11vnc_remote_prop) { /* * Go retrieve X11VNC_REMOTE string. */ read_x11vnc_remote_prop(0); } } } /* do this now that we have just cleared PropertyNotify */ tmp = 0; if (rfac() < 0.6) { tmp = 1; } if (now > last_time_sync + sync_tod_delay + tmp) { sync_tod_with_servertime(); last_time_sync = now; } #if LIBVNCSERVER_HAVE_LIBXRANDR if (xrandr) { check_xrandr_event("check_xevents"); } #endif #if LIBVNCSERVER_HAVE_LIBXFIXES if (xfixes_present && use_xfixes && xfixes_base_event_type) { if (XCheckTypedEvent(dpy, xfixes_base_event_type + XFixesCursorNotify, &xev)) { got_xfixes_cursor_notify++; } } #endif /* check for our PRIMARY request notification: */ if (watch_primary) { if (XCheckTypedEvent(dpy, SelectionNotify, &xev)) { if (xev.type == SelectionNotify && xev.xselection.requestor == selwin && xev.xselection.selection == XA_PRIMARY && xev.xselection.property != None && xev.xselection.target == XA_STRING) { /* go retrieve PRIMARY and check it */ if (now > last_client + sel_waittime || sent_some_sel) { selection_send(&xev); } } } if (now > last_request) { /* * Every second or two, request PRIMARY, unless we * already own it or there is no owner or we have * no clients. * TODO: even at this low rate we should look into * and performance problems in odds cases, etc. */ last_request = now; if (! own_selection && have_clients && XGetSelectionOwner(dpy, XA_PRIMARY) != None) { XConvertSelection(dpy, XA_PRIMARY, XA_STRING, XA_STRING, selwin, CurrentTime); } } } if (own_selection) { /* we own PRIMARY, see if someone requested it: */ if (XCheckTypedEvent(dpy, SelectionRequest, &xev)) { if (xev.type == SelectionRequest && xev.xselectionrequest.selection == XA_PRIMARY) { selection_request(&xev); } } /* we own PRIMARY, see if we no longer own it: */ if (XCheckTypedEvent(dpy, SelectionClear, &xev)) { if (xev.type == SelectionClear && xev.xselectionclear.selection == XA_PRIMARY) { own_selection = 0; if (xcut_str) { free(xcut_str); xcut_str = NULL; } } } } if (watch_bell || now > last_bell+1) { last_bell = now; check_bell_event(); } if (tray_request != None) { static time_t last_tray_request = 0; if (now > last_tray_request + 2) { last_tray_request = now; if (tray_embed(tray_request, tray_unembed)) { tray_window = tray_request; tray_request = None; } } } #ifndef DEBUG_XEVENTS #define DEBUG_XEVENTS 1 #endif #if DEBUG_XEVENTS if (debug_xevents) { static time_t last_check = 0; static time_t reminder = 0; static int freq = 0; if (! freq) { if (getenv("X11VNC_REMINDER_RATE")) { freq = atoi(getenv("X11VNC_REMINDER_RATE")); } else { freq = 300; } } if (now > last_check + 1) { int ev_type_max = 300, ev_size = 400; XEvent xevs[400]; int i, tot = XEventsQueued(dpy, QueuedAlready); if (reminder == 0 || (tot && now > reminder + freq)) { print_xevent_bases(); reminder = now; } last_check = now; if (tot) { fprintf(stderr, "Total events queued: %d\n", tot); } for (i=1; i= ev_size) { break; } } if (n) { fprintf(stderr, " %d%s events of type" " %d queued\n", n, (n >= ev_size) ? "+" : "", i); } for (k=n-1; k >= 0; k--) { XPutBackEvent(dpy, xevs+k); } } } } #endif if (now > last_sync + 1200) { /* kludge for any remaining event leaks */ int bugout = use_xdamage ? 500 : 50; int qlen, i; if (last_sync != 0) { qlen = XEventsQueued(dpy, QueuedAlready); if (qlen >= bugout) { rfbLog("event leak: %d queued, " " calling XSync(dpy, True)\n", qlen); rfbLog(" for diagnostics run: 'x11vnc -R" " debug_xevents:1'\n"); XSync(dpy, True); } } last_sync = now; /* clear these, we don't want any events on them */ if (rdpy_ctrl) { qlen = XEventsQueued(rdpy_ctrl, QueuedAlready); for (i=0; i