summaryrefslogtreecommitdiffstats
path: root/x11vnc/x11vnc.c
diff options
context:
space:
mode:
Diffstat (limited to 'x11vnc/x11vnc.c')
-rw-r--r--x11vnc/x11vnc.c6742
1 files changed, 6742 insertions, 0 deletions
diff --git a/x11vnc/x11vnc.c b/x11vnc/x11vnc.c
new file mode 100644
index 0000000..144b280
--- /dev/null
+++ b/x11vnc/x11vnc.c
@@ -0,0 +1,6742 @@
+/*
+ * x11vnc.c: a VNC server for X displays.
+ *
+ * Copyright (c) 2002-2003 Karl J. Runge <runge@karlrunge.com>
+ * All rights reserved.
+ *
+ * This 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.
+ *
+ * This software 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 this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ *
+ *
+ * This program is based heavily on the following programs:
+ *
+ * the originial x11vnc.c in libvncserver (Johannes E. Schindelin)
+ * krfb, the KDE desktopsharing project (Tim Jansen)
+ * x0rfbserver, the original native X vnc server (Jens Wagner)
+ *
+ * The primary goal of this program is to create a portable and simple
+ * command-line server utility that allows a VNC viewer to connect to an
+ * actual X display (as the above do). The only non-standard dependency
+ * of this program is the static library libvncserver.a (although in
+ * some environments libjpeg.so may not be readily available and needs
+ * to be installed, it may be found at ftp://ftp.uu.net/graphics/jpeg/).
+ * To increase portability it is written in plain C.
+ *
+ * The next goal is to improve performance and interactive response.
+ * The algorithm currently used here to achieve this is that of krfb
+ * (based on x0rfbserver algorithm). Additional heuristics are also
+ * applied (currently there are a bit too many of these...)
+ *
+ * To build:
+ *
+ * Obtain the libvncserver package (http://libvncserver.sourceforge.net).
+ * As of 12/2002 this version of x11vnc.c is contained in the libvncserver
+ * CVS tree and released in version 0.5.
+ *
+ * gcc should be used on all platforms. To build a threaded version put
+ * "-D_REENTRANT -DX11VNC_THREADED" in the environment variable CFLAGS
+ * or CPPFLAGS (e.g. before running the libvncserver configure). The
+ * threaded mode is a bit more responsive, but can be unstable.
+ *
+ * Known shortcomings:
+ *
+ * The screen updates are good, but of course not perfect since the X
+ * display must be continuously polled and read for changes (as opposed to
+ * receiving a change callback from the X server, if that were generally
+ * possible...). So, e.g., opaque moves and similar window activity
+ * can be very painful; one has to modify one's behavior a bit.
+ *
+ * General audio at the remote display is lost unless one separately
+ * sets up some audio side-channel such as esd.
+ *
+ * It does not appear possible to query the X server for the current
+ * cursor shape. We can use XTest to compare cursor to current window's
+ * cursor, but we cannot extract what the cursor is...
+ *
+ * Nevertheless, the current *position* of the remote X mouse pointer
+ * is shown with the -mouse option. Further, if -mouseX or -X is used, a
+ * trick is done to at least show the root window cursor vs non-root cursor.
+ * (perhaps some heuristic can be done to further distinguish cases...)
+ *
+ * With -mouse there are occasionally some repainting errors involving
+ * big areas near the cursor. The mouse painting is in general a bit
+ * ragged and not very pleasant.
+ *
+ * Windows using visuals other than the default X visual may have
+ * their colors messed up. When using 8bpp indexed color, the colormap
+ * is attempted to be followed, but may become out of date. Use the
+ * -flashcmap option to have colormap flashing as the pointer moves
+ * windows with private colormaps (slow). Displays with mixed depth 8 and
+ * 24 visuals will incorrect display the non-default one.
+ *
+ * Feature -id <windowid> can be picky: it can crash for things like the
+ * window not sufficiently mapped into server memory, use of -mouse, etc.
+ * SaveUnders menus, popups, etc will not be seen.
+ *
+ * Occasionally, a few tile updates can be missed leaving a patch of
+ * color that needs to be refreshed.
+ *
+ * There seems to be a serious bug with simultaneous clients when
+ * threaded, currently the only workaround in this case is -nothreads.
+ *
+ */
+
+#include <unistd.h>
+#include <signal.h>
+#include <sys/utsname.h>
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XShm.h>
+#include <X11/extensions/XTest.h>
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include <rfb/rfb.h>
+#include <rfb/rfbregion.h>
+
+#ifdef LIBVNCSERVER_HAVE_XKEYBOARD
+#include <X11/XKBlib.h>
+#endif
+
+/*
+ * Temporary kludge: to run with -xinerama define the following macro
+ * and be sure to link with * -lXinerama (e.g. LDFLAGS=-lXinerama before
+ * configure). Support for this is being added to libvncserver 'configure.ac'
+ * so it will all be done automatically.
+
+#define LIBVNCSERVER_HAVE_LIBXINERAMA
+ */
+#ifdef LIBVNCSERVER_HAVE_LIBXINERAMA
+#include <X11/extensions/Xinerama.h>
+#endif
+
+/* date +'"lastmod: %Y-%m-%d";' */
+char lastmod[] = "lastmod: 2004-05-21";
+
+
+/* X and rfb framebuffer */
+Display *dpy = 0;
+Visual *visual;
+Window window, rootwin;
+int scr;
+int bpp, depth;
+int button_mask = 0;
+int dpy_x, dpy_y;
+int off_x, off_y;
+int subwin = 0;
+int indexed_colour = 0;
+
+XImage *tile;
+XImage **tile_row; /* for all possible row runs */
+XImage *scanline;
+XImage *fullscreen;
+int fs_factor = 0;
+
+XShmSegmentInfo *tile_row_shm; /* for all possible row runs */
+XShmSegmentInfo scanline_shm;
+XShmSegmentInfo fullscreen_shm;
+
+rfbScreenInfoPtr screen;
+rfbCursorPtr cursor;
+int bytes_per_line;
+
+/* size of the basic tile unit that is polled for changes: */
+int tile_x = 32;
+int tile_y = 32;
+int ntiles, ntiles_x, ntiles_y;
+
+/* arrays that indicate changed or checked tiles. */
+unsigned char *tile_has_diff, *tile_tried;
+
+/* blacked-out region things */
+typedef struct bout {
+ int x1, y1, x2, y2;
+} blackout_t;
+typedef struct tbout {
+ blackout_t bo[10]; /* hardwired max rectangles. */
+ int cover;
+ int count;
+} tile_blackout_t;
+blackout_t black[100]; /* hardwired max blackouts */
+int blackouts = 0;
+tile_blackout_t *tile_blackout;
+
+
+typedef struct tile_change_region {
+ /* start and end lines, along y, of the changed area inside a tile. */
+ unsigned short first_line, last_line;
+ /* info about differences along edges. */
+ unsigned short left_diff, right_diff;
+ unsigned short top_diff, bot_diff;
+} region_t;
+
+/* array to hold the tiles region_t-s. */
+region_t *tile_region;
+
+typedef struct hint {
+ /* location x, y, height, and width of a change-rectangle */
+ /* (grows as adjacent horizontal tiles are glued together) */
+ int x, y, w, h;
+} hint_t;
+
+/* array to hold the hints: */
+hint_t *hint_list;
+
+/* various command line options */
+
+int shared = 0; /* share vnc display. */
+char *allow_list = NULL; /* for -allow and -localhost */
+char *accept_cmd = NULL; /* for -accept */
+char *gone_cmd = NULL; /* for -gone */
+int view_only = 0; /* clients can only watch. */
+int inetd = 0; /* spawned from inetd(1) */
+int connect_once = 1; /* disconnect after first connection session. */
+int flash_cmap = 0; /* follow installed colormaps */
+int force_indexed_color = 0; /* whether to force indexed color for 8bpp */
+
+int use_modifier_tweak = 0; /* use the altgr_keyboard modifier tweak */
+char *remap_file = NULL; /* user supplied remapping file or list */
+int nofb = 0; /* do not send any fb updates */
+
+char *blackout_string = NULL; /* -blackout */
+int xinerama = 0; /* -xinerama */
+
+char *client_connect = NULL; /* strings for -connect option */
+char *client_connect_file = NULL;
+int vnc_connect = 0; /* -vncconnect option */
+
+int local_cursor = 1; /* whether the viewer draws a local cursor */
+int cursor_pos = 0; /* cursor position updates -cursorpos */
+int show_mouse = 0; /* display a cursor for the real mouse */
+int use_xwarppointer = 0; /* use XWarpPointer instead of XTestFake... */
+int show_root_cursor = 0; /* show X when on root background */
+int show_dragging = 1; /* process mouse movement events */
+int watch_bell = 1; /* watch for the bell using XKEYBOARD */
+
+int old_pointer = 0; /* use the old way of updating the pointer */
+int single_copytile = 0; /* use the old way copy_tiles() */
+
+int using_shm = 1; /* whether mit-shm is used */
+int flip_byte_order = 0; /* sometimes needed when using_shm = 0 */
+/*
+ * waitms is the msec to wait between screen polls. Not too old h/w shows
+ * poll times of 10-35ms, so maybe this value cuts the idle load by 2 or so.
+ */
+int waitms = 30;
+int defer_update = 30; /* rfbDeferUpdateTime ms to wait before sends. */
+int defer_update_nofb = 6; /* defer a shorter time under -nofb */
+
+int screen_blank = 60; /* number of seconds of no activity to throttle */
+ /* down the screen polls. zero to disable. */
+int take_naps = 0;
+int naptile = 3; /* tile change threshold per poll to take a nap */
+int napfac = 4; /* time = napfac*waitms, cut load with extra waits */
+int napmax = 1500; /* longest nap in ms. */
+int ui_skip = 10; /* see watchloop. negative means ignore input */
+
+/* for -visual override */
+VisualID visual_id = (VisualID) 0;
+int visual_depth = 0;
+
+int nap_ok = 0, nap_diff_count = 0;
+time_t last_event, last_input, last_client = 0;
+
+/* tile heuristics: */
+double fs_frac = 0.75; /* threshold tile fraction to do fullscreen updates. */
+int use_hints = 1; /* use the krfb scheme of gluing tiles together. */
+int tile_fuzz = 2; /* tolerance for suspecting changed tiles touching */
+ /* a known changed tile. */
+int grow_fill = 3; /* do the grow islands heuristic with this width. */
+int gaps_fill = 4; /* do a final pass to try to fill gaps between tiles. */
+
+/* scan pattern jitter from x0rfbserver */
+#define NSCAN 32
+int scanlines[NSCAN] = {
+ 0, 16, 8, 24, 4, 20, 12, 28,
+ 10, 26, 18, 2, 22, 6, 30, 14,
+ 1, 17, 9, 25, 7, 23, 15, 31,
+ 19, 3, 27, 11, 29, 13, 5, 21
+};
+int count = 0; /* indicates which scan pattern we are on */
+
+int cursor_x, cursor_y; /* x and y from the viewer(s) */
+int got_user_input = 0;
+int got_pointer_input = 0;
+int got_keyboard_input = 0;
+int scan_in_progress = 0;
+int fb_copy_in_progress = 0;
+int client_count = 0;
+int shut_down = 0;
+int sigpipe = 1; /* 0=skip, 1=ignore, 2=exit */
+
+int debug_pointer = 0;
+int debug_keyboard = 0;
+
+int quiet = 0;
+double dtime(double *);
+int all_clients_initialized(void);
+
+void zero_fb(int, int, int, int);
+
+#if defined(LIBVNCSERVER_X11VNC_THREADED) && ! defined(X11VNC_THREADED)
+#define X11VNC_THREADED
+#endif
+
+#if defined(LIBVNCSERVER_HAVE_LIBPTHREAD) && defined(X11VNC_THREADED)
+ int use_threads = 1;
+#else
+ int use_threads = 0;
+#endif
+
+/* XXX usleep(3) is not thread safe on some older systems... */
+struct timeval _mysleep;
+#define usleep2(x) \
+ _mysleep.tv_sec = (x) / 1000000; \
+ _mysleep.tv_usec = (x) % 1000000; \
+ select(0, NULL, NULL, NULL, &_mysleep);
+#if !defined(X11VNC_USLEEP)
+#undef usleep
+#define usleep usleep2
+#endif
+
+/*
+ * Not sure why... but when threaded we have to mutex our X11 calls to
+ * avoid XIO crashes.
+ */
+MUTEX(x11Mutex);
+#define X_LOCK LOCK(x11Mutex)
+#define X_UNLOCK UNLOCK(x11Mutex)
+#define X_INIT INIT_MUTEX(x11Mutex)
+
+/*
+ * Exiting and error handling:
+ */
+void shm_clean(XShmSegmentInfo *, XImage *);
+void shm_delete(XShmSegmentInfo *);
+
+int exit_flag = 0;
+void clean_up_exit (int ret) {
+ int i;
+ exit_flag = 1;
+
+ /* remove the shm areas: */
+ shm_clean(&scanline_shm, scanline);
+ shm_clean(&fullscreen_shm, fullscreen);
+
+ for(i=1; i<=ntiles_x; i++) {
+ shm_clean(&tile_row_shm[i], tile_row[i]);
+ if (single_copytile && i >= single_copytile) {
+ break;
+ }
+ }
+
+ X_LOCK;
+ XTestDiscard(dpy);
+ X_UNLOCK;
+
+ fflush(stderr);
+ exit(ret);
+}
+
+/*
+ * General problem handler
+ */
+void interrupted (int sig) {
+ int i;
+ if (exit_flag) {
+ exit_flag++;
+ if (use_threads) {
+ usleep2(250 * 1000);
+ } else if (exit_flag <= 2) {
+ return;
+ }
+ exit(4);
+ }
+ exit_flag++;
+ if (sig == 0) {
+ fprintf(stderr, "caught X11 error:\n");
+ } else {
+ fprintf(stderr, "caught signal: %d\n", sig);
+ }
+ /*
+ * to avoid deadlock, etc, just delete the shm areas and
+ * leave the X stuff hanging.
+ */
+ shm_delete(&scanline_shm);
+ shm_delete(&fullscreen_shm);
+
+ /*
+ * Here we have to clean up quite a few shm areas for all
+ * the possible tile row runs (40 for 1280), not as robust
+ * as one might like... sometimes need to run ipcrm(1).
+ */
+ for(i=1; i<=ntiles_x; i++) {
+ shm_delete(&tile_row_shm[i]);
+ if (single_copytile && i >= single_copytile) {
+ break;
+ }
+ }
+ if (sig) {
+ exit(2);
+ }
+}
+
+XErrorHandler Xerror_def;
+XIOErrorHandler XIOerr_def;
+int Xerror(Display *d, XErrorEvent *error) {
+ X_UNLOCK;
+ interrupted(0);
+ return (*Xerror_def)(d, error);
+}
+int XIOerr(Display *d) {
+ X_UNLOCK;
+ interrupted(0);
+ return (*XIOerr_def)(d);
+}
+
+void set_signals(void) {
+ signal(SIGHUP, interrupted);
+ signal(SIGINT, interrupted);
+ signal(SIGQUIT, interrupted);
+ signal(SIGABRT, interrupted);
+ signal(SIGTERM, interrupted);
+ signal(SIGBUS, interrupted);
+ signal(SIGSEGV, interrupted);
+ signal(SIGFPE, interrupted);
+
+ if (sigpipe == 1) {
+#ifdef SIG_IGN
+ signal(SIGPIPE, SIG_IGN);
+#endif
+ } else if (sigpipe == 2) {
+ rfbLog("set_signals: will exit on SIGPIPE\n");
+ signal(SIGPIPE, interrupted);
+ }
+
+ X_LOCK;
+ Xerror_def = XSetErrorHandler(Xerror);
+ XIOerr_def = XSetIOErrorHandler(XIOerr);
+ X_UNLOCK;
+}
+
+int run_user_command(char *, rfbClientPtr);
+
+int accepted_client = 0;
+void client_gone(rfbClientPtr client) {
+
+ client_count--;
+ rfbLog("client_count: %d\n", client_count);
+
+ if (gone_cmd) {
+ rfbLog("client_gone: using cmd for: %s\n", client->host);
+ run_user_command(gone_cmd, client);
+ }
+
+ if (inetd) {
+ rfbLog("viewer exited.\n");
+ clean_up_exit(0);
+ }
+ if (connect_once) {
+ /*
+ * This non-exit is done for a bad passwd to be consistent
+ * with our RFB_CLIENT_REFUSE behavior in new_client() (i.e.
+ * we disconnect after 1 successful connection).
+ */
+ if (client->state == RFB_PROTOCOL_VERSION ||
+ client->state == RFB_AUTHENTICATION && accepted_client) {
+ rfbLog("connect_once: bad password or early "
+ "disconnect.\n");
+ rfbLog("connect_once: waiting for next connection.\n");
+ accepted_client = 0;
+ return;
+ }
+
+ rfbLog("viewer exited.\n");
+ clean_up_exit(0);
+ }
+}
+
+/*
+ * Simple routine to limit access via string compare. A power user will
+ * want to compile libvncserver with libwrap support and use /etc/hosts.allow.
+ */
+int check_access(char *addr) {
+ int allowed = 0;
+ char *p, *list;
+
+ if (allow_list == NULL || *allow_list == '\0') {
+ return 1;
+ }
+ if (addr == NULL || *addr == '\0') {
+ rfbLog("check_access: denying empty host IP address string.\n");
+ return 0;
+ }
+
+ list = strdup(allow_list);
+ p = strtok(list, ",");
+ while (p) {
+ char *q = strstr(addr, p);
+ if (q == addr) {
+ rfbLog("check_access: client %s matches pattern %s\n",
+ addr, p);
+ allowed = 1;
+
+ } else if(!strcmp(p,"localhost") && !strcmp(addr,"127.0.0.1")) {
+ allowed = 1;
+ }
+ p = strtok(NULL, ",");
+ }
+ free(list);
+ return allowed;
+}
+
+/*
+ * x11vnc's first (and only) visible widget: accept/reject dialog window.
+ * We go through this pain to avoid dependency on libXt.
+ */
+
+int ugly_accept_window(char *addr, int X, int Y, int timeout, char *mode) {
+
+#define t2x2_width 16
+#define t2x2_height 16
+static char t2x2_bits[] = {
+ 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff,
+ 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33,
+ 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33};
+
+ Window awin;
+ GC gc;
+ XSizeHints hints;
+ XGCValues values;
+ static XFontStruct *font_info = NULL;
+ static Pixmap ico = 0;
+ unsigned long valuemask = 0;
+ static char dash_list[] = {20, 40};
+ int list_length = sizeof(dash_list);
+
+ Atom wm_protocols;
+ Atom wm_delete_window;
+
+ XEvent ev;
+ long evmask = ExposureMask | KeyPressMask | ButtonPressMask
+ | StructureNotifyMask;
+ double waited = 0.0;
+
+ /* strings and geometries y/n */
+ KeyCode key_y, key_n, key_v;
+ char strh[100];
+ char str1_b[] = "To accept: press \"y\" or click the \"Yes\" button";
+ char str2_b[] = "To reject: press \"n\" or click the \"No\" button";
+ char str3_b[] = "View only: press \"v\" or click the \"View\" button";
+ char str1_m[] = "To accept: click the \"Yes\" button";
+ char str2_m[] = "To reject: click the \"No\" button";
+ char str3_m[] = "View only: click the \"View\" button";
+ char str1_k[] = "To accept: press \"y\"";
+ char str2_k[] = "To reject: press \"n\"";
+ char str3_k[] = "View only: press \"v\"";
+ char *str1, *str2, *str3;
+ char str_y[] = "Yes";
+ char str_n[] = "No";
+ char str_v[] = "View";
+ int x, y, w = 345, h = 150, ret = 0;
+ int X_sh = 20, Y_sh = 30, dY = 20;
+ int Ye_x = 20, Ye_y = 0, Ye_w = 45, Ye_h = 20;
+ int No_x = 75, No_y = 0, No_w = 45, No_h = 20;
+ int Vi_x = 130, Vi_y = 0, Vi_w = 45, Vi_h = 20;
+
+ if (!strcmp(mode, "mouse_only")) {
+ str1 = str1_m;
+ str2 = str2_m;
+ str3 = str3_m;
+ } else if (!strcmp(mode, "key_only")) {
+ str1 = str1_k;
+ str2 = str2_k;
+ str3 = str3_k;
+ h -= dY;
+ } else {
+ str1 = str1_b;
+ str2 = str2_b;
+ str3 = str3_b;
+ }
+ if (view_only) {
+ h -= dY;
+ }
+
+ if (X < -dpy_x) {
+ x = (dpy_x - w)/2; /* large negative: center */
+ if (x < 0) x = 0;
+ } else if (X < 0) {
+ x = dpy_x + X - w; /* from lower right */
+ } else {
+ x = X; /* from upper left */
+ }
+
+ if (Y < -dpy_y) {
+ y = (dpy_y - h)/2;
+ if (y < 0) y = 0;
+ } else if (Y < 0) {
+ y = dpy_y + Y - h;
+ } else {
+ y = Y;
+ }
+
+ X_LOCK;
+
+ awin = XCreateSimpleWindow(dpy, window, x, y, w, h, 4,
+ BlackPixel(dpy, scr), WhitePixel(dpy, scr));
+
+ wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False);
+ wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+ XSetWMProtocols(dpy, awin, &wm_delete_window, 1);
+
+ if (! ico) {
+ ico = XCreateBitmapFromData(dpy, awin, t2x2_bits, t2x2_width,
+ t2x2_height);
+ }
+
+ hints.flags = PPosition | PSize | PMinSize;
+ hints.x = x;
+ hints.y = y;
+ hints.width = w;
+ hints.height = h;
+ hints.min_width = w;
+ hints.min_height = h;
+
+ XSetStandardProperties(dpy, awin, "new x11vnc client", "x11vnc query",
+ ico, NULL, 0, &hints);
+
+ XSelectInput(dpy, awin, evmask);
+
+ if (! font_info && (font_info = XLoadQueryFont(dpy, "fixed")) == NULL) {
+ rfbLog("ugly_accept_window: cannot locate font fixed.\n");
+ X_UNLOCK;
+ clean_up_exit(1);
+ }
+
+ gc = XCreateGC(dpy, awin, valuemask, &values);
+ XSetFont(dpy, gc, font_info->fid);
+ XSetForeground(dpy, gc, BlackPixel(dpy, scr));
+ XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);
+ XSetDashes(dpy, gc, 0, dash_list, list_length);
+
+ XMapWindow(dpy, awin);
+ XFlush(dpy);
+
+ sprintf(strh, "x11vnc: accept connection from %s?", addr);
+ key_y = XKeysymToKeycode(dpy, XStringToKeysym("y"));
+ key_n = XKeysymToKeycode(dpy, XStringToKeysym("n"));
+ key_v = XKeysymToKeycode(dpy, XStringToKeysym("v"));
+
+ while (1) {
+ int out = -1, x, y, tw, k;
+
+ if (XCheckWindowEvent(dpy, awin, evmask, &ev)) {
+ ; /* proceed to handling */
+ } else if (XCheckTypedEvent(dpy, ClientMessage, &ev)) {
+ ; /* proceed to handling */
+ } else {
+ int ms = 100; /* sleep a bit */
+ usleep(ms * 1000);
+ waited += ((double) ms)/1000.;
+ if (timeout && (int) waited >= timeout) {
+ rfbLog("accept_client: popup timed out after "
+ "%d seconds.\n", timeout);
+ out = 0;
+ ev.type = 0;
+ } else {
+ continue;
+ }
+ }
+
+ switch(ev.type) {
+ case Expose:
+ while (XCheckTypedEvent(dpy, Expose, &ev)) {
+ ;
+ }
+ k=0;
+
+ /* instructions */
+ XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY,
+ strh, strlen(strh));
+ XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY,
+ str1, strlen(str1));
+ XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY,
+ str2, strlen(str2));
+ if (! view_only) {
+ XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY,
+ str3, strlen(str3));
+ }
+
+ if (!strcmp(mode, "key_only")) {
+ break;
+ }
+
+ /* buttons */
+ Ye_y = Y_sh+k*dY;
+ No_y = Y_sh+k*dY;
+ Vi_y = Y_sh+k*dY;
+ XDrawRectangle(dpy, awin, gc, Ye_x, Ye_y, Ye_w, Ye_h);
+ XDrawRectangle(dpy, awin, gc, No_x, No_y, No_w, No_h);
+ if (! view_only) {
+ XDrawRectangle(dpy, awin, gc, Vi_x, Vi_y,
+ Vi_w, Vi_h);
+ }
+
+ tw = XTextWidth(font_info, str_y, strlen(str_y));
+ tw = (Ye_w - tw)/2;
+ if (tw < 0) tw = 1;
+ XDrawString(dpy, awin, gc, Ye_x+tw, Ye_y+Ye_h-5,
+ str_y, strlen(str_y));
+
+ tw = XTextWidth(font_info, str_n, strlen(str_n));
+ tw = (No_w - tw)/2;
+ if (tw < 0) tw = 1;
+ XDrawString(dpy, awin, gc, No_x+tw, No_y+No_h-5,
+ str_n, strlen(str_n));
+
+ if (! view_only) {
+ tw = XTextWidth(font_info, str_v,
+ strlen(str_v));
+ tw = (Vi_w - tw)/2;
+ if (tw < 0) tw = 1;
+ XDrawString(dpy, awin, gc, Vi_x+tw, Vi_y+Vi_h-5,
+ str_v, strlen(str_v));
+ }
+
+ break;
+
+ case ClientMessage:
+ if (ev.xclient.message_type == wm_protocols &&
+ ev.xclient.data.l[0] == wm_delete_window) {
+ out = 0;
+ }
+ break;
+
+ case ButtonPress:
+ x = ev.xbutton.x;
+ y = ev.xbutton.y;
+ if (!strcmp(mode, "key_only")) {
+ ;
+ } else if (x > No_x && x < No_x+No_w && y > No_y
+ && y < No_y+No_h) {
+ out = 0;
+ } else if (x > Ye_x && x < Ye_x+Ye_w && y > Ye_y
+ && y < Ye_y+Ye_h) {
+ out = 1;
+ } else if (! view_only && x > Vi_x && x < Vi_x+Vi_w
+ && y > Vi_y && y < Vi_y+Ye_h) {
+ out = 2;
+ }
+ break;
+
+ case KeyPress:
+ if (!strcmp(mode, "mouse_only")) {
+ ;
+ } else if (ev.xkey.keycode == key_y) {
+ out = 1;
+ } else if (ev.xkey.keycode == key_n) {
+ out = 0;
+ } else if (! view_only && ev.xkey.keycode == key_v) {
+ out = 2;
+ }
+ break;
+ default:
+ break;
+ }
+ if (out != -1) {
+ ret = out;
+ XUnmapWindow(dpy, awin);
+ XFreeGC(dpy, gc);
+ XDestroyWindow(dpy, awin);
+ XFlush(dpy);
+ break;
+ }
+ }
+ X_UNLOCK;
+
+ return ret;
+}
+
+/*
+ * utility to run a user supplied command setting some RFB_ env vars.
+ * used by, e.g., accept_client() and client_gone()
+ */
+int run_user_command(char *cmd, rfbClientPtr client) {
+ char *dpystr = DisplayString(dpy);
+ static char *display_env = NULL;
+ static char env_rfb_client_id[100];
+ static char env_rfb_client_ip[100];
+ static char env_rfb_client_port[100];
+ static char env_rfb_x11vnc_pid[100];
+ char *addr = client->host;
+ int rc, fromlen, fromport;
+ struct sockaddr_in from;
+
+ if (addr == NULL || addr[0] == '\0') {
+ addr = "unknown-host";
+ }
+
+ /* set RFB_CLIENT_ID to semi unique id for command to use */
+ sprintf(env_rfb_client_id, "RFB_CLIENT_ID=%d", (int) client);
+ putenv(env_rfb_client_id);
+
+ /* set RFB_CLIENT_IP to IP addr for command to use */
+ sprintf(env_rfb_client_ip, "RFB_CLIENT_IP=%s", addr);
+ putenv(env_rfb_client_ip);
+
+ /* set RFB_X11VNC_PID to our pid for command to use */
+ sprintf(env_rfb_x11vnc_pid, "RFB_X11VNC_PID=%d", (int) getpid());
+ putenv(env_rfb_x11vnc_pid);
+
+ /* set RFB_CLIENT_PORT to peer port for command to use */
+ fromlen = sizeof(from);
+ memset(&from, 0, sizeof(from));
+ fromport = -1;
+ if (!getpeername(client->sock, (struct sockaddr *)&from, &fromlen)) {
+ fromport = ntohs(from.sin_port);
+ }
+ sprintf(env_rfb_client_port, "RFB_CLIENT_PORT=%d", fromport);
+ putenv(env_rfb_client_port);
+
+ /*
+ * Better set DISPLAY to the one we are polling, if they
+ * want something trickier, they can handle on their own
+ * via environment, etc. XXX really should save/restore old.
+ */
+ if (display_env == NULL) {
+ display_env = (char *) malloc(strlen(dpystr)+10);
+ }
+ sprintf(display_env, "DISPLAY=%s", dpystr);
+ putenv(display_env);
+
+ rfbLog("running command:\n");
+ rfbLog(" %s\n", cmd);
+
+ rc = system(cmd);
+
+ if (rc >= 256) {
+ rc = rc/256;
+ }
+ rfbLog("command returned: %d\n", rc);
+
+ sprintf(env_rfb_client_id, "RFB_CLIENT_ID=");
+ putenv(env_rfb_client_id);
+
+ sprintf(env_rfb_client_ip, "RFB_CLIENT_IP=");
+ putenv(env_rfb_client_ip);
+
+ sprintf(env_rfb_client_port, "RFB_CLIENT_PORT=");
+ putenv(env_rfb_client_port);
+
+ sprintf(env_rfb_x11vnc_pid, "RFB_X11VNC_PID=");
+ putenv(env_rfb_x11vnc_pid);
+
+ return rc;
+}
+
+/*
+ * process a "yes:0,no:*,view:3" type action list comparing to command
+ * return code rc. * means the default action with no other match.
+ */
+int action_match(char *action, int rc) {
+ char *p, *q, *s = strdup(action);
+ int cases[4], i, result;
+ char *labels[4];
+
+ labels[1] = "yes";
+ labels[2] = "no";
+ labels[3] = "view";
+
+ rfbLog("accept_client: process action line: %s\n",
+ action);
+
+ for (i=1; i <= 3; i++) {
+ cases[i] = -2;
+ }
+
+ p = strtok(s, ",");
+ while (p) {
+ if ((q = strchr(p, ':')) != NULL) {
+ int in, k;
+ *q = '\0';
+ q++;
+ if (strstr(p, "yes") == p) {
+ k = 1;
+ } else if (strstr(p, "no") == p) {
+ k = 2;
+ } else if (strstr(p, "view") == p) {
+ k = 3;
+ } else {
+ rfbLog("bad action line: %s\n", action);
+ clean_up_exit(1);
+ }
+ if (*q == '*') {
+ cases[k] = -1;
+ } else if (sscanf(q, "%d", &in) == 1) {
+ if (in < 0) {
+ rfbLog("bad action line: %s\n", action);
+ clean_up_exit(1);
+ }
+ cases[k] = in;
+ } else {
+ rfbLog("bad action line: %s\n", action);
+ clean_up_exit(1);
+ }
+ } else {
+ rfbLog("bad action line: %s\n", action);
+ clean_up_exit(1);
+ }
+ p = strtok(NULL, ",");
+ }
+ free(s);
+
+ result = -1;
+ for (i=1; i <= 3; i++) {
+ if (cases[i] == -1) {
+ rfbLog("accept_client: default action is case=%d %s\n",
+ i, labels[i]);
+ result = i;
+ break;
+ }
+ }
+ if (result == -1) {
+ rfbLog("accept_client: no default action\n");
+ }
+ for (i=1; i <= 3; i++) {
+ if (cases[i] >= 0 && cases[i] == rc) {
+ rfbLog("accept_client: matched action is case=%d %s\n",
+ i, labels[i]);
+ result = i;
+ break;
+ }
+ }
+ if (result < 0) {
+ rfbLog("no action match: %s rc=%d set to no\n", action, rc);
+ result = 2;
+ }
+ return result;
+}
+
+/*
+ * Simple routine to prompt the user on the X display whether an incoming
+ * client should be allowed to connect or not. If a gui is involved it
+ * will be running in the environment/context of the X11 DISPLAY.
+ *
+ * The command supplied via -accept is run as is (i.e. no string
+ * substitution) with the RFB_CLIENT_IP environment variable set to the
+ * incoming client's numerical IP address.
+ *
+ * If the external command exits with 0 the client is accepted, otherwise
+ * the client is rejected.
+ *
+ * Some builtins are provided:
+ *
+ * xmessage: use homebrew xmessage(1) for the external command.
+ * popup: use internal X widgets for prompting.
+ *
+ */
+int accept_client(rfbClientPtr client) {
+
+ char xmessage[200], *cmd = NULL;
+ char *addr = client->host;
+ char *action = NULL;
+
+ if (accept_cmd == NULL) {
+ return 1; /* no command specified, so we accept */
+ }
+
+ if (addr == NULL || addr[0] == '\0') {
+ addr = "unknown-host";
+ }
+
+ if (strstr(accept_cmd, "popup") == accept_cmd) {
+ /* use our builtin popup button */
+
+ /* (popup|popupkey|popupmouse)[+-X+-Y][:timeout] */
+
+ int ret, timeout = 120;
+ int x = -64000, y = -64000;
+ char *p, *mode;
+
+ /* extract timeout */
+ if ((p = strchr(accept_cmd, ':')) != NULL) {
+ int in;
+ if (sscanf(p+1, "%d", &in) == 1) {
+ timeout = in;
+ }
+ }
+ /* extract geometry */
+ if ((p = strpbrk(accept_cmd, "+-")) != NULL) {
+ int x1, y1;
+ if (sscanf(p, "+%d+%d", &x1, &y1) == 2) {
+ x = x1;
+ y = y1;
+ } else if (sscanf(p, "+%d-%d", &x1, &y1) == 2) {
+ x = x1;
+ y = -y1;
+ } else if (sscanf(p, "-%d+%d", &x1, &y1) == 2) {
+ x = -x1;
+ y = y1;
+ } else if (sscanf(p, "-%d-%d", &x1, &y1) == 2) {
+ x = -x1;
+ y = -y1;
+ }
+ }
+
+ /* find mode: mouse, key, or both */
+ if (strstr(accept_cmd, "popupmouse") == accept_cmd) {
+ mode = "mouse_only";
+ } else if (strstr(accept_cmd, "popupkey") == accept_cmd) {
+ mode = "key_only";
+ } else {
+ mode = "both";
+ }
+
+ rfbLog("accept_client: using builtin popup for: %s\n", addr);
+ if ((ret = ugly_accept_window(addr, x, y, timeout, mode))) {
+ if (ret == 2) {
+ rfbLog("accept_client: viewonly: %s\n", addr);
+ client->viewOnly = TRUE;
+ }
+ rfbLog("accept_client: popup accepted: %s\n", addr);
+ return 1;
+ } else {
+ rfbLog("accept_client: popup rejected: %s\n", addr);
+ return 0;
+ }
+
+ } else if (!strcmp(accept_cmd, "xmessage")) {
+ /* make our own command using xmessage(1) */
+
+ if (view_only) {
+ sprintf(xmessage, "xmessage -buttons yes:0,no:2 -center"
+ " 'x11vnc: accept connection from %s?'", addr);
+ } else {
+ sprintf(xmessage, "xmessage -buttons yes:0,no:2,"
+ "view-only:3 -center" " 'x11vnc: accept connection"
+ " from %s?'", addr);
+ action = "yes:0,no:*,view:3";
+ }
+ cmd = xmessage;
+
+ } else {
+ /* use the user supplied command: */
+
+ cmd = accept_cmd;
+
+ /* extract any action prefix: yes:N,no:M,view:K */
+ if (strstr(accept_cmd, "yes:") == accept_cmd) {
+ char *p;
+ if ((p = strpbrk(accept_cmd, " \t")) != NULL) {
+ int i;
+ cmd = p;
+ p = accept_cmd;
+ for (i=0; i<200; i++) {
+ if (*p == ' ' || *p == '\t') {
+ xmessage[i] = '\0';
+ break;
+ }
+ xmessage[i] = *p;
+ p++;
+ }
+ xmessage[200-1] = '\0';
+ action = xmessage;
+ }
+ }
+ }
+
+ if (cmd) {
+ int rc;
+
+ rfbLog("accept_client: using cmd for: %s\n", addr);
+ rc = run_user_command(cmd, client);
+
+ if (action) {
+ int result;
+
+ if (rc < 0) {
+ rfbLog("accept_client: cannot use negative "
+ "rc: %d, action %s\n", rc, action);
+ result = 2;
+ } else {
+ result = action_match(action, rc);
+ }
+
+ if (result == 1) {
+ rc = 0;
+ } else if (result == 2) {
+ rc = 1;
+ } else if (result == 3) {
+ rc = 0;
+ rfbLog("accept_client: viewonly: %s\n", addr);
+ client->viewOnly = TRUE;
+ } else {
+ rc = 1; /* NOTREACHED */
+ }
+ }
+
+ if (rc == 0) {
+ rfbLog("accept_client: accepted: %s\n", addr);
+ return 1;
+ } else {
+ rfbLog("accept_client: rejected: %s\n", addr);
+ return 0;
+ }
+ } else {
+ rfbLog("accept_client: no command, rejecting %s\n", addr);
+ return 0;
+ }
+
+ return 0; /* NOTREACHED */
+}
+
+/*
+ * For the -connect <file> option: periodically read the file looking for
+ * a connect string. If one is found set client_connect to it.
+ */
+void check_connect_file(char *file) {
+ FILE *in;
+ char line[512], host[512];
+ static int first_warn = 1, truncate_ok = 1;
+ static time_t last_time = 0;
+ time_t now = time(0);
+
+ if (now - last_time < 1) {
+ /* check only once a second */
+ return;
+ }
+ last_time = now;
+
+ if (! truncate_ok) {
+ /* check if permissions changed */
+ if (access(file, W_OK) == 0) {
+ truncate_ok = 1;
+ } else {
+ return;
+ }
+ }
+
+ in = fopen(file, "r");
+ if (in == NULL) {
+ if (first_warn) {
+ rfbLog("check_connect_file: fopen failure: %s\n", file);
+ perror("fopen");
+ first_warn = 0;
+ }
+ return;
+ }
+
+ if (fgets(line, 512, in) != NULL) {
+ if (sscanf(line, "%s", host) == 1) {
+ if (strlen(host) > 0) {
+ client_connect = strdup(host);
+ rfbLog("read connect file: %s\n", host);
+ }
+ }
+ }
+ fclose(in);
+
+ /* truncate file */
+ in = fopen(file, "w");
+ if (in != NULL) {
+ fclose(in);
+ } else {
+ /* disable if we cannot truncate */
+ rfbLog("check_connect_file: could not truncate %s, "
+ "disabling checking.\n", file);
+ truncate_ok = 0;
+ }
+}
+
+/* Do a reverse connect for a single "host" or "host:port" */
+int do_reverse_connect(char *str) {
+ rfbClientPtr cl;
+ char *host, *p;
+ int port = 5500, len = strlen(str);
+
+ if (len < 1) {
+ return 0;
+ }
+ if (len > 512) {
+ rfbLog("reverse_connect: string too long: %d bytes\n", len);
+ return 0;
+ }
+
+ /* copy in to host */
+ host = (char *) malloc((size_t) len+1);
+ if (! host) {
+ rfbLog("reverse_connect: could not malloc string %d\n", len);
+ return 0;
+ }
+ strncpy(host, str, len);
+ host[len] = '\0';
+
+ /* extract port, if any */
+ if ((p = strchr(host, ':')) != NULL) {
+ port = atoi(p+1);
+ *p = '\0';
+ }
+
+ cl = rfbReverseConnection(screen, host, port);
+ free(host);
+
+ if (cl == NULL) {
+ rfbLog("reverse_connect: %s failed\n", str);
+ return 0;
+ } else {
+ rfbLog("reverse_connect: %s/%s OK\n", str, cl->host);
+ return 1;
+ }
+}
+
+void rfbPE(rfbScreenInfoPtr scr, long us) {
+ if (! use_threads) {
+ return rfbProcessEvents(scr, us);
+ }
+}
+
+/* break up comma separated list of hosts and call do_reverse_connect() */
+
+void reverse_connect(char *str) {
+ char *p, *tmp = strdup(str);
+ int sleep_between_host = 300;
+ int sleep_min = 1500, sleep_max = 4500, n_max = 5;
+ int n, tot, t, dt = 100, cnt = 0;
+
+ p = strtok(tmp, ",");
+ while (p) {
+ if ((n = do_reverse_connect(p)) != 0) {
+ rfbPE(screen, -1);
+ }
+ cnt += n;
+
+ p = strtok(NULL, ",");
+ if (p) {
+ t = 0;
+ while (t < sleep_between_host) {
+ usleep(dt * 1000);
+ rfbPE(screen, -1);
+ t += dt;
+ }
+ }
+ }
+ free(tmp);
+
+ if (cnt == 0) {
+ return;
+ }
+
+ /*
+ * XXX: we need to process some of the initial handshaking
+ * events, otherwise the client can get messed up (why??)
+ * so we send rfbProcessEvents() all over the place.
+ */
+
+ n = cnt;
+ if (n >= n_max) {
+ n = n_max;
+ }
+ t = sleep_max - sleep_min;
+ tot = sleep_min + ((n-1) * t) / (n_max-1);
+
+ t = 0;
+ while (t < tot) {
+ rfbPE(screen, -1);
+ usleep(dt * 1000);
+ t += dt;
+ }
+}
+
+/* check if client_connect has been set, if so make the reverse connections. */
+
+void send_client_connect() {
+ if (client_connect != NULL) {
+ reverse_connect(client_connect);
+ free(client_connect);
+ client_connect = NULL;
+ }
+}
+
+/* string for the VNC_CONNECT property */
+#define VNC_CONNECT_MAX 512
+char vnc_connect_str[VNC_CONNECT_MAX+1];
+
+/* monitor the various input methods */
+void check_connect_inputs() {
+
+ /* flush any already set: */
+ send_client_connect();
+
+ /* connect file: */
+ if (client_connect_file != NULL) {
+ check_connect_file(client_connect_file);
+ }
+ send_client_connect();
+
+ /* VNC_CONNECT property (vncconnect program) */
+ if (vnc_connect && *vnc_connect_str != '\0') {
+ client_connect = strdup(vnc_connect_str);
+ vnc_connect_str[0] = '\0';
+ }
+ send_client_connect();
+}
+
+/*
+ * libvncserver callback for when a new client connects
+ */
+enum rfbNewClientAction new_client(rfbClientPtr client) {
+ last_event = last_input = time(0);
+
+ if (inetd) {
+ /*
+ * Set this so we exit as soon as connection closes,
+ * otherwise client_gone is only called after RFB_CLIENT_ACCEPT
+ */
+ client->clientGoneHook = client_gone;
+ }
+
+ if (connect_once) {
+ if (screen->rfbDontDisconnect && screen->rfbNeverShared) {
+ if (! shared && accepted_client) {
+ rfbLog("denying additional client: %s\n",
+ client->host);
+ return(RFB_CLIENT_REFUSE);
+ }
+ }
+ }
+ if (! check_access(client->host)) {
+ rfbLog("denying client: %s does not match %s\n", client->host,
+ allow_list ? allow_list : "(null)" );
+ return(RFB_CLIENT_REFUSE);
+ }
+ if (! accept_client(client)) {
+ rfbLog("denying client: %s local user rejected connection.\n",
+ client->host);
+ rfbLog("denying client: accept_cmd=\"%s\"\n",
+ accept_cmd ? accept_cmd : "(null)" );
+ return(RFB_CLIENT_REFUSE);
+ }
+
+ if (view_only) {
+ client->clientData = (void *) -1;
+ client->viewOnly = TRUE;
+ } else {
+ client->clientData = (void *) 0;
+ }
+
+ client->clientGoneHook = client_gone;
+ client_count++;
+
+ accepted_client = 1;
+ last_client = time(0);
+
+ return(RFB_CLIENT_ACCEPT);
+}
+
+/*
+ * For tweaking modifiers wrt the Alt-Graph key, etc.
+ */
+#define LEFTSHIFT 1
+#define RIGHTSHIFT 2
+#define ALTGR 4
+char mod_state = 0;
+
+char modifiers[0x100];
+KeyCode keycodes[0x100], left_shift_code, right_shift_code, altgr_code;
+
+void initialize_modtweak() {
+ KeySym key, *keymap;
+ int i, j, minkey, maxkey, syms_per_keycode;
+
+ memset(modifiers, -1, sizeof(modifiers));
+
+ XDisplayKeycodes(dpy, &minkey, &maxkey);
+
+ keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1),
+ &syms_per_keycode);
+
+ /* handle alphabetic char with only one keysym (no upper + lower) */
+ for (i = minkey; i <= maxkey; i++) {
+ KeySym lower, upper;
+ /* 2nd one */
+ key = keymap[(i - minkey) * syms_per_keycode + 1];
+ if (key != NoSymbol) {
+ continue;
+ }
+ /* 1st one */
+ key = keymap[(i - minkey) * syms_per_keycode + 0];
+ if (key == NoSymbol) {
+ continue;
+ }
+ XConvertCase(key, &lower, &upper);
+ if (lower != upper) {
+ keymap[(i - minkey) * syms_per_keycode + 0] = lower;
+ keymap[(i - minkey) * syms_per_keycode + 1] = upper;
+ }
+ }
+ for (i = minkey; i <= maxkey; i++) {
+ for (j = 0; j < syms_per_keycode; j++) {
+ key = keymap[ (i - minkey) * syms_per_keycode + j ];
+ if ( key >= ' ' && key < 0x100
+ && i == XKeysymToKeycode(dpy, key) ) {
+ keycodes[key] = i;
+ modifiers[key] = j;
+ }
+ }
+ }
+
+ left_shift_code = XKeysymToKeycode(dpy, XK_Shift_L);
+ right_shift_code = XKeysymToKeycode(dpy, XK_Shift_R);
+ altgr_code = XKeysymToKeycode(dpy, XK_Mode_switch);
+
+ XFree ((void *) keymap);
+}
+
+/*
+ * The following is for an experimental -remap option to allow the user
+ * to remap keystrokes. It is currently confusing wrt modifiers...
+ */
+typedef struct keyremap {
+ KeySym before;
+ KeySym after;
+ int isbutton;
+ struct keyremap *next;
+} keyremap_t;
+
+keyremap_t *keyremaps = NULL;
+
+void initialize_remap(char *infile) {
+ FILE *in;
+ char *p, *q, line[256], str1[256], str2[256];
+ int i;
+ KeySym ksym1, ksym2;
+ keyremap_t *remap, *current;
+
+ in = fopen(infile, "r");
+ if (in == NULL) {
+ /* assume cmd line key1-key2,key3-key4 */
+ if (! strchr(infile, '-') || (in = tmpfile()) == NULL) {
+ rfbLog("remap: cannot open: %s\n", infile);
+ perror("fopen");
+ clean_up_exit(1);
+ }
+ p = infile;
+ while (*p) {
+ if (*p == '-') {
+ fprintf(in, " ");
+ } else if (*p == ',') {
+ fprintf(in, "\n");
+ } else {
+ fprintf(in, "%c", *p);
+ }
+ p++;
+ }
+ fprintf(in, "\n");
+ fflush(in);
+ rewind(in);
+ }
+ while (fgets(line, 256, in) != NULL) {
+ int blank = 1, isbtn = 0;
+ p = line;
+ while (*p) {
+ if (! isspace(*p)) {
+ blank = 0;
+ break;
+ }
+ p++;
+ }
+ if (blank) {
+ continue;
+ }
+ if (strchr(line, '#')) {
+ continue;
+ }
+ if ( (q = strchr(line, '-')) != NULL) {
+ /* allow Keysym1-Keysym2 notation */
+ *q = ' ';
+ }
+
+ if (sscanf(line, "%s %s", str1, str2) != 2) {
+ rfbLog("remap: bad line: %s\n", line);
+ fclose(in);
+ clean_up_exit(1);
+ }
+ if (sscanf(str1, "0x%x", &i) == 1) {
+ ksym1 = (KeySym) i;
+ } else {
+ ksym1 = XStringToKeysym(str1);
+ }
+ if (sscanf(str2, "0x%x", &i) == 1) {
+ ksym2 = (KeySym) i;
+ } else {
+ ksym2 = XStringToKeysym(str2);
+ }
+ if (ksym2 == NoSymbol) {
+ int i;
+ if (sscanf(str2, "Button%d", &i) == 1) {
+ ksym2 = (KeySym) i;
+ isbtn = 1;
+ }
+ }
+ if (ksym1 == NoSymbol || ksym2 == NoSymbol) {
+ rfbLog("warning: skipping bad remap line: %s", line);
+ continue;
+ }
+ remap = (keyremap_t *) malloc((size_t) sizeof(keyremap_t));
+ remap->before = ksym1;
+ remap->after = ksym2;
+ remap->isbutton = isbtn;
+ remap->next = NULL;
+ rfbLog("remapping: (%s, 0x%x) -> (%s, 0x%x) isbtn=%d\n", str1,
+ ksym1, str2, ksym2, isbtn);
+ if (keyremaps == NULL) {
+ keyremaps = remap;
+ } else {
+ current->next = remap;
+
+ }
+ current = remap;
+ }
+ fclose(in);
+}
+
+void DebugXTestFakeKeyEvent(Display* dpy, KeyCode key, Bool down, time_t cur_time)
+{
+ if (debug_keyboard) {
+ rfbLog("XTestFakeKeyEvent(dpy, keycode=0x%x \"%s\", %s)\n",
+ key, XKeysymToString(XKeycodeToKeysym(dpy, key, 0)),
+ down ? "down":"up");
+ }
+ XTestFakeKeyEvent(dpy, key, down, cur_time);
+}
+
+/*
+ * This is to allow debug_keyboard option trap everything:
+ */
+#define XTestFakeKeyEvent DebugXTestFakeKeyEvent
+
+void tweak_mod(signed char mod, rfbBool down) {
+ rfbBool is_shift = mod_state & (LEFTSHIFT|RIGHTSHIFT);
+ Bool dn = (Bool) down;
+
+ if (mod < 0) {
+ if (debug_keyboard) {
+ rfbLog("tweak_mod: Skip: down=%d mod=0x%x\n", down,
+ (int) mod);
+ }
+ return;
+ }
+ if (debug_keyboard) {
+ rfbLog("tweak_mod: Start: down=%d mod=0x%x mod_state=0x%x"
+ " is_shift=%d\n", down, (int) mod, (int) mod_state,
+ is_shift);
+ }
+
+ X_LOCK;
+ if (is_shift && mod != 1) {
+ if (mod_state & LEFTSHIFT) {
+ XTestFakeKeyEvent(dpy, left_shift_code, !dn, CurrentTime);
+ }
+ if (mod_state & RIGHTSHIFT) {
+ XTestFakeKeyEvent(dpy, right_shift_code, !dn, CurrentTime);
+ }
+ }
+ if ( ! is_shift && mod == 1 ) {
+ XTestFakeKeyEvent(dpy, left_shift_code, dn, CurrentTime);
+ }
+ if ( altgr_code && (mod_state & ALTGR) && mod != 2 ) {
+ XTestFakeKeyEvent(dpy, altgr_code, !dn, CurrentTime);
+ }
+ if ( altgr_code && ! (mod_state & ALTGR) && mod == 2 ) {
+ XTestFakeKeyEvent(dpy, altgr_code, dn, CurrentTime);
+ }
+ X_UNLOCK;
+ if (debug_keyboard) {
+ rfbLog("tweak_mod: Finish: down=%d mod=0x%x mod_state=0x%x"
+ " is_shift=%d\n", down, (int) mod, (int) mod_state,
+ is_shift);
+ }
+}
+
+static void modifier_tweak_keyboard(rfbBool down, rfbKeySym keysym,
+ rfbClientPtr client) {
+ KeyCode k;
+ int tweak = 0;
+ if (debug_keyboard) {
+ rfbLog("modifier_tweak_keyboard: %s keysym=0x%x\n",
+ down ? "down" : "up", (int) keysym);
+ }
+
+ if (view_only) {
+ return;
+ }
+ if (client->viewOnly) {
+ return;
+ }
+
+#define ADJUSTMOD(sym, state) \
+ if (keysym == sym) { \
+ if (down) { \
+ mod_state |= state; \
+ } else { \
+ mod_state &= ~state; \
+ } \
+ }
+
+ ADJUSTMOD(XK_Shift_L, LEFTSHIFT)
+ ADJUSTMOD(XK_Shift_R, RIGHTSHIFT)
+ ADJUSTMOD(XK_Mode_switch, ALTGR)
+
+ if ( down && keysym >= ' ' && keysym < 0x100 ) {
+ tweak = 1;
+ tweak_mod(modifiers[keysym], True);
+ k = keycodes[keysym];
+ } else {
+ X_LOCK;
+ k = XKeysymToKeycode(dpy, (KeySym) keysym);
+ X_UNLOCK;
+ }
+ if (debug_keyboard) {
+ rfbLog("modifier_tweak_keyboard: KeySym 0x%x \"%s\" -> "
+ "KeyCode 0x%x%s\n", (int) keysym, XKeysymToString(keysym),
+ (int) k, k ? "" : " *ignored*");
+ }
+ if ( k != NoSymbol ) {
+ X_LOCK;
+ XTestFakeKeyEvent(dpy, k, (Bool) down, CurrentTime);
+ X_UNLOCK;
+ }
+
+ if ( tweak ) {
+ tweak_mod(modifiers[keysym], False);
+ }
+}
+
+/*
+ * key event handler. See the above functions for contortions for
+ * running under -modtweak.
+ */
+rfbClientPtr last_keyboard_client = NULL;
+int num_buttons = -1;
+
+static void keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) {
+ KeyCode k;
+ int isbutton = 0;
+
+ if (debug_keyboard) {
+ X_LOCK;
+ rfbLog("keyboard(%s, 0x%x \"%s\")\n", down ? "down":"up",
+ (int) keysym, XKeysymToString(keysym));
+ X_UNLOCK;
+ }
+
+ if (view_only) {
+ return;
+ }
+ if (client->viewOnly) {
+ return;
+ }
+ last_keyboard_client = client;
+
+ if (keyremaps) {
+ keyremap_t *remap = keyremaps;
+ while (remap != NULL) {
+ if (remap->before == keysym) {
+ keysym = remap->after;
+ isbutton = remap->isbutton;
+ if (debug_keyboard) {
+ X_LOCK;
+ rfbLog("keyboard(): remapping keysym: "
+ "0x%x \"%s\" -> 0x%x \"%s\"\n",
+ (int) remap->before,
+ XKeysymToString(remap->before),
+ (int) remap->after,
+ remap->isbutton ? "button" :
+ XKeysymToString(remap->after));
+ X_UNLOCK;
+ }
+ break;
+ }
+ remap = remap->next;
+ }
+ }
+
+ if (isbutton) {
+ int button = (int) keysym;
+ if (! down) {
+ return; /* nothing to send */
+ }
+ if (debug_keyboard) {
+ rfbLog("keyboard(): remapping keystroke to button %d"
+ " click\n", button);
+ }
+ if (button < 1 || button > num_buttons) {
+ rfbLog("keyboard(): ignoring mouse button out of "
+ "bounds: %d\n", button);
+ return;
+ }
+ X_LOCK;
+ XTestFakeButtonEvent(dpy, button, True, CurrentTime);
+ XTestFakeButtonEvent(dpy, button, False, CurrentTime);
+ XFlush(dpy);
+ X_UNLOCK;
+ return;
+ }
+
+ if (use_modifier_tweak) {
+ modifier_tweak_keyboard(down, keysym, client);
+ X_LOCK;
+ XFlush(dpy);
+ X_UNLOCK;
+ return;
+ }
+
+ X_LOCK;
+
+ k = XKeysymToKeycode(dpy, (KeySym) keysym);
+
+ if (debug_keyboard) {
+ rfbLog("keyboard(): KeySym 0x%x \"%s\" -> KeyCode 0x%x%s\n",
+ (int) keysym, XKeysymToString(keysym), (int) k,
+ k ? "" : " *ignored*");
+ }
+
+ if ( k != NoSymbol ) {
+ XTestFakeKeyEvent(dpy, k, (Bool) down, CurrentTime);
+ XFlush(dpy);
+
+ last_event = last_input = time(0);
+ got_user_input++;
+ got_keyboard_input++;
+ }
+
+ X_UNLOCK;
+}
+
+/*
+ * pointer event handling routines.
+ */
+typedef struct ptrremap {
+ KeySym keysym;
+ KeyCode keycode;
+ int end;
+ int button;
+ int down;
+ int up;
+} prtremap_t;
+
+MUTEX(pointerMutex);
+#define MAX_BUTTONS 5
+#define MAX_BUTTON_EVENTS 50
+prtremap_t pointer_map[MAX_BUTTONS+1][MAX_BUTTON_EVENTS];
+char *pointer_remap = NULL;
+void update_pointer(int, int, int);
+
+
+/* based on IsModifierKey in Xutil.h */
+#define ismodkey(keysym) \
+ ((((KeySym)(keysym) >= XK_Shift_L) && ((KeySym)(keysym) <= XK_Hyper_R)))
+
+
+/*
+ * For parsing the -buttonmap sections, e.g. "4" or ":Up+Up+Up:"
+ */
+void buttonparse(int from, char **s) {
+ 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;
+ int i;
+ if (n >= MAX_BUTTON_EVENTS - 20) {
+ rfbLog("buttonparse: too many button map "
+ "events: %s\n", list);
+ break;
+ }
+ if (sscanf(t, "0x%x", &i) == 1) {
+ ksym = (KeySym) i; /* hex value */
+ } else {
+ ksym = XStringToKeysym(t); /* string value */
+ }
+ 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 {
+ kcode = XKeysymToKeycode(dpy, ksym);
+
+ 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;
+ }
+ }
+ rfbLog(" event %d: keysym %s (0x%x) -> "
+ "keycode 0x%x down=%d up=%d\n", n+1,
+ XKeysymToString(ksym), ksym, kcode,
+ pointer_map[from][n].down,
+ pointer_map[from][n].up);
+ }
+ 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)++;
+ }
+}
+
+void initialize_pointer_map(void) {
+ 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.
+ */
+
+ X_LOCK;
+ num_buttons = XGetPointerMapping(dpy, map, MAX_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) {
+ /* -buttonmap, format is like: 12-21=2 */
+ char *p, *q, *remap = 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++;
+ }
+ }
+ }
+ X_UNLOCK;
+}
+
+/*
+ * Actual callback from libvncserver when it gets a pointer event.
+ */
+rfbClientPtr last_pointer_client = NULL;
+
+static void pointer(int mask, int x, int y, rfbClientPtr client) {
+
+ if (debug_pointer && mask >= 0) {
+ rfbLog("pointer(mask: 0x%x, x:%4d, y:%4d)\n", mask, x, y);
+ }
+
+ if (view_only) {
+ return;
+ }
+ if (client->viewOnly) {
+ return;
+ }
+
+ 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.
+ */
+ got_user_input++;
+ got_pointer_input++;
+ last_pointer_client = client;
+ }
+
+ /*
+ * 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 && ! old_pointer) {
+# define NEV 32
+ /* storage for the event queue */
+ static int mutex_init = 0;
+ 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 (! mutex_init) {
+ INIT_MUTEX(pointerMutex);
+ mutex_init = 1;
+ }
+
+ LOCK(pointerMutex);
+
+ /*
+ * 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 (fb_copy_in_progress && mask >= 0) {
+ /*
+ * 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;
+ UNLOCK(pointerMutex);
+ if (debug_pointer) {
+ rfbLog("pointer(): deferring event "
+ "%d\n", i);
+ }
+ return;
+ }
+ }
+
+ /* time to send the queue */
+ for (i=0; i<nevents; i++) {
+ if (debug_pointer) {
+ rfbLog("pointer(): sending event %d\n", i+1);
+ }
+ update_pointer(ev[i][0], ev[i][1], ev[i][2]);
+ }
+ if (nevents && dt > maxwait) {
+ X_LOCK;
+ XFlush(dpy);
+ X_UNLOCK;
+ }
+ nevents = 0; /* reset everything */
+ tmr = 0.0;
+ dt = 0.0;
+ dtime(&tmr);
+
+ UNLOCK(pointerMutex);
+ }
+ if (mask < 0) { /* -1 just means flush the event queue */
+ if (debug_pointer > 1) {
+ rfbLog("pointer(): flush only.\n");
+ }
+ return;
+ }
+
+ /* update the X display with the event: */
+ update_pointer(mask, x, y);
+}
+
+/*
+ * Send a pointer event to the X server.
+ */
+
+void update_pointer(int mask, int x, int y) {
+ int i, mb;
+
+ X_LOCK;
+
+ if (! use_xwarppointer) {
+ XTestFakeMotionEvent(dpy, scr, x+off_x, y+off_y, CurrentTime);
+ } else {
+ XWarpPointer(dpy, None, window, 0, 0, 0, 0, x+off_x, y+off_y);
+ }
+
+ cursor_x = x;
+ cursor_y = y;
+
+ last_event = last_input = time(0);
+
+ for (i=0; i < MAX_BUTTONS; i++) {
+ /* look for buttons that have be clicked or released: */
+ if ( (button_mask & (1<<i)) != (mask & (1<<i)) ) {
+ int k;
+ if (debug_pointer) {
+ rfbLog("pointer(): mask change: mask: 0x%x -> "
+ "0x%x button: %d\n", button_mask, mask,i+1);
+ }
+ 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) {
+ /* sent 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(dpy, mb, (mask & (1<<i))
+ ? True : False, CurrentTime);
+ } else {
+ /* sent 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) {
+ 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,
+ XKeysymToString(XKeycodeToKeysym(
+ dpy, key, 0)));
+ }
+ if (down) {
+ XTestFakeKeyEvent(dpy, key, True,
+ CurrentTime);
+ }
+ if (up) {
+ XTestFakeKeyEvent(dpy, key, False,
+ CurrentTime);
+ }
+ }
+ }
+ }
+ }
+
+ if (nofb) {
+ /*
+ * nofb is for, e.g. Win2VNC, where fastest pointer
+ * updates are desired.
+ */
+ XFlush(dpy);
+ }
+
+ X_UNLOCK;
+
+ /*
+ * Remember the button state for next time and also for the
+ * -nodragging case:
+ */
+ button_mask = mask;
+}
+
+/*
+ * Bell event handling. Requires XKEYBOARD extension.
+ */
+#ifdef LIBVNCSERVER_HAVE_XKEYBOARD
+
+int xkb_base_event_type;
+
+void initialize_watch_bell() {
+ int ir, reason;
+ if (! XkbSelectEvents(dpy, XkbUseCoreKbd, XkbBellNotifyMask,
+ XkbBellNotifyMask) ) {
+ if (! quiet) {
+ fprintf(stderr, "warning: disabling bell.\n");
+ }
+ watch_bell = 0;
+ return;
+ }
+ if (! XkbOpenDisplay(DisplayString(dpy), &xkb_base_event_type, &ir,
+ NULL, NULL, &reason) ) {
+ if (! quiet) {
+ fprintf(stderr, "warning: disabling bell.\n");
+ }
+ watch_bell = 0;
+ }
+}
+
+/*
+ * We call this periodically to process any bell events that have
+ * taken place.
+ */
+void watch_bell_event() {
+ XEvent xev;
+ XkbAnyEvent *xkb_ev;
+ int got_bell = 0;
+
+ if (! watch_bell) {
+ return;
+ }
+
+ X_LOCK;
+ if (! XCheckTypedEvent(dpy, xkb_base_event_type , &xev)) {
+ X_UNLOCK;
+ return;
+ }
+ xkb_ev = (XkbAnyEvent *) &xev;
+ if (xkb_ev->xkb_type == XkbBellNotify) {
+ got_bell = 1;
+ }
+ X_UNLOCK;
+
+ if (got_bell) {
+ if (! all_clients_initialized()) {
+ rfbLog("watch_bell_event: not sending bell: "
+ "uninitialized clients\n");
+ } else {
+ rfbSendBell(screen);
+ }
+ }
+}
+#else
+void watch_bell_event() {}
+#endif
+
+
+/*
+ * Selection/Cutbuffer/Clipboard handlers.
+ */
+
+int watch_selection = 1; /* normal selection/cutbuffer maintenance */
+int watch_primary = 1; /* more dicey, poll for changes in PRIMARY */
+int own_selection = 0; /* whether we currently own PRIMARY or not */
+int set_cutbuffer = 0; /* to avoid bouncing the CutText right back */
+int sel_waittime = 15; /* some seconds to skip before first send */
+Window selwin; /* special window for our selection */
+
+/*
+ * This is where we keep our selection: the string sent TO us from VNC
+ * clients, and the string sent BY us to requesting X11 clients.
+ */
+char *xcut_string = NULL;
+
+/*
+ * Our callbacks instruct us to check for changes in the cutbuffer
+ * and PRIMARY selection on the local X11 display.
+ *
+ * We store the new cutbuffer and/or PRIMARY selection data in this
+ * constant sized array selection_str[].
+ * TODO: check if malloc does not cause performance issues (esp. WRT
+ * SelectionNotify handling).
+ */
+#define PROP_MAX (131072L)
+char selection_str[PROP_MAX+1];
+
+/*
+ * An X11 (not VNC) client on the local display has requested the selection
+ * from us (because we are the current owner).
+ *
+ * n.b.: our caller already has the X_LOCK.
+ */
+void selection_request(XEvent *ev) {
+ XSelectionEvent notify_event;
+ XSelectionRequestEvent *req_event;
+ unsigned int length;
+ unsigned char *data;
+#ifndef XA_LENGTH
+ unsigned long XA_LENGTH = XInternAtom(dpy, "LENGTH", True);
+#endif
+
+ req_event = &(ev->xselectionrequest);
+ notify_event.type = SelectionNotify;
+ notify_event.display = req_event->display;
+ notify_event.requestor = req_event->requestor;
+ notify_event.selection = req_event->selection;
+ notify_event.target = req_event->target;
+ notify_event.time = req_event->time;
+
+ if (req_event->property == None) {
+ notify_event.property = req_event->target;
+ } else {
+ notify_event.property = req_event->property;
+ }
+ if (xcut_string) {
+ length = strlen(xcut_string);
+ } else {
+ length = 0;
+ }
+
+
+ if (ev->xselectionrequest.target == XA_LENGTH) {
+ /* length request */
+
+ XChangeProperty(ev->xselectionrequest.display,
+ ev->xselectionrequest.requestor,
+ ev->xselectionrequest.property,
+ ev->xselectionrequest.target, 32, PropModeReplace,
+ (unsigned char *) &length, sizeof(unsigned int));
+
+ } else {
+ /* data request */
+
+ data = (unsigned char *)xcut_string;
+
+ XChangeProperty(ev->xselectionrequest.display,
+ ev->xselectionrequest.requestor,
+ ev->xselectionrequest.property,
+ ev->xselectionrequest.target, 8, PropModeReplace,
+ data, length);
+ }
+
+ XSendEvent(req_event->display, req_event->requestor, False, 0,
+ (XEvent *)&notify_event);
+
+ XFlush(dpy);
+}
+
+int all_clients_initialized() {
+ rfbClientIteratorPtr iter;
+ rfbClientPtr cl;
+ int ok = 1;
+
+ iter = rfbGetClientIterator(screen);
+ while( (cl = rfbClientIteratorNext(iter)) ) {
+ if (cl->state != RFB_NORMAL) {
+ ok = 0;
+ break;
+ }
+ }
+ rfbReleaseClientIterator(iter);
+
+ return ok;
+}
+
+/*
+ * CUT_BUFFER0 property on the local display has changed, we read and
+ * store it and send it out to any connected VNC clients.
+ *
+ * n.b.: our caller already has the X_LOCK.
+ */
+void cutbuffer_send() {
+ Atom type;
+ int format, slen, dlen;
+ unsigned long nitems = 0, bytes_after = 0;
+ unsigned char* data = NULL;
+
+ selection_str[0] = '\0';
+ slen = 0;
+
+ /* read the property value into selection_str: */
+ do {
+ if (XGetWindowProperty(dpy, DefaultRootWindow(dpy),
+ XA_CUT_BUFFER0, nitems/4, PROP_MAX/16, False,
+ AnyPropertyType, &type, &format, &nitems, &bytes_after,
+ &data) == Success) {
+
+ dlen = nitems * (format/8);
+ if (slen + dlen > PROP_MAX) {
+ /* too big */
+ rfbLog("warning: truncating large CUT_BUFFER0"
+ " selection > %d bytes.\n", PROP_MAX);
+ XFree(data);
+ break;
+ }
+ memcpy(selection_str+slen, data, dlen);
+ slen += dlen;
+ selection_str[slen] = '\0';
+ XFree(data);
+ }
+ } while (bytes_after > 0);
+
+ selection_str[PROP_MAX] = '\0';
+
+ if (! all_clients_initialized()) {
+ rfbLog("cutbuffer_send: no send: uninitialized clients\n");
+ return; /* some clients initializing, cannot send */
+ }
+
+ /* now send it to any connected VNC clients (rfbServerCutText) */
+ rfbSendServerCutText(screen, selection_str, strlen(selection_str));
+}
+
+/*
+ * "callback" for our SelectionNotify polling. We try to determine if
+ * the PRIMARY selection has changed (checking length and first CHKSZ bytes)
+ * and if it has we store it and send it off to any connected VNC clients.
+ *
+ * n.b.: our caller already has the X_LOCK.
+ *
+ * TODO: if we were willing to use libXt, we could perhaps get selection
+ * timestamps to speed up the checking... XtGetSelectionValue().
+ */
+#define CHKSZ 32
+void selection_send(XEvent *ev) {
+ Atom type;
+ int format, slen, dlen, oldlen, newlen, toobig = 0;
+ static int err = 0, sent_one = 0;
+ char before[CHKSZ], after[CHKSZ];
+ unsigned long nitems = 0, bytes_after = 0;
+ unsigned char* data = NULL;
+
+ /*
+ * remember info about our last value of PRIMARY (or CUT_BUFFER0)
+ * so we can check for any changes below.
+ */
+ oldlen = strlen(selection_str);
+ strncpy(before, selection_str, CHKSZ);
+
+ selection_str[0] = '\0';
+ slen = 0;
+
+ /* read in the current value of PRIMARY: */
+ do {
+ if (XGetWindowProperty(dpy, ev->xselection.requestor,
+ ev->xselection.property, nitems/4, PROP_MAX/16, True,
+ AnyPropertyType, &type, &format, &nitems, &bytes_after,
+ &data) == Success) {
+
+ dlen = nitems * (format/8);
+ if (slen + dlen > PROP_MAX) {
+ /* too big */
+ toobig = 1;
+ XFree(data);
+ if (err) { /* cut down on messages */
+ break;
+ } else {
+ err = 5;
+ }
+ rfbLog("warning: truncating large PRIMARY"
+ " selection > %d bytes.\n", PROP_MAX);
+ break;
+ }
+ memcpy(selection_str+slen, data, dlen);
+ slen += dlen;
+ selection_str[slen] = '\0';
+ XFree(data);
+ }
+ } while (bytes_after > 0);
+
+ if (! toobig) {
+ err = 0;
+ } else if (err) {
+ err--;
+ }
+
+ if (! sent_one) {
+ /* try to force a send first time in */
+ oldlen = -1;
+ sent_one = 1;
+ }
+
+ /* look for changes in the new value */
+ newlen = strlen(selection_str);
+ strncpy(after, selection_str, CHKSZ);
+
+ if (oldlen == newlen && strncmp(before, after, CHKSZ) == 0) {
+ /* evidently no change */
+ return;
+ }
+ if (newlen == 0) {
+ /* do not bother sending a null string out */
+ return;
+ }
+
+ if (! all_clients_initialized()) {
+ rfbLog("selection_send: no send: uninitialized clients\n");
+ return; /* some clients initializing, cannot send */
+ }
+
+ /* now send it to any connected VNC clients (rfbServerCutText) */
+ rfbSendServerCutText(screen, selection_str, newlen);
+}
+
+
+/*
+ * Routines for monitoring the VNC_CONNECT property for changes.
+ * The vncconnect(1) will set it on our X display.
+ */
+
+Atom vnc_connect_prop = None;
+
+void read_vnc_connect_prop() {
+ Atom type;
+ int format, slen, dlen;
+ unsigned long nitems = 0, bytes_after = 0;
+ unsigned char* data = NULL;
+
+ vnc_connect_str[0] = '\0';
+ slen = 0;
+
+ if (! vnc_connect || vnc_connect_prop == None) {
+ /* not active or problem with VNC_CONNECT atom */
+ return;
+ }
+
+ /* read the property value into vnc_connect_str: */
+ do {
+ if (XGetWindowProperty(dpy, DefaultRootWindow(dpy),
+ vnc_connect_prop, nitems/4, VNC_CONNECT_MAX/16, False,
+ AnyPropertyType, &type, &format, &nitems, &bytes_after,
+ &data) == Success) {
+
+ dlen = nitems * (format/8);
+ if (slen + dlen > VNC_CONNECT_MAX) {
+ /* too big */
+ rfbLog("warning: truncating large VNC_CONNECT"
+ " string > %d bytes.\n", VNC_CONNECT_MAX);
+ XFree(data);
+ break;
+ }
+ memcpy(vnc_connect_str+slen, data, dlen);
+ slen += dlen;
+ vnc_connect_str[slen] = '\0';
+ XFree(data);
+ }
+ } while (bytes_after > 0);
+
+ vnc_connect_str[VNC_CONNECT_MAX] = '\0';
+ rfbLog("read property VNC_CONNECT: %s\n", vnc_connect_str);
+}
+
+/*
+ * This routine is periodically called to check for selection related
+ * and other X11 events and respond to them as needed.
+ */
+void watch_xevents() {
+ XEvent xev;
+ static int first = 1, sent_sel = 0;
+ int have_clients = screen->rfbClientHead ? 1 : 0;
+ time_t last_request = 0, now = time(0);
+
+ X_LOCK;
+ if (first && (watch_selection || vnc_connect)) {
+ /*
+ * register desired event(s) for notification.
+ * PropertyChangeMask is for CUT_BUFFER0 changes.
+ * TODO: does this cause a flood of other stuff?
+ */
+ XSelectInput(dpy, rootwin, PropertyChangeMask);
+ }
+ if (first && watch_selection) {
+ /* create fake window for our selection ownership, etc */
+ selwin = XCreateSimpleWindow(dpy, rootwin, 0, 0, 1, 1, 0, 0, 0);
+ }
+ if (first && vnc_connect) {
+ vnc_connect_str[0] = '\0';
+ vnc_connect_prop = XInternAtom(dpy, "VNC_CONNECT", False);
+ }
+ first = 0;
+
+ /*
+ * 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_sel
+ && now > last_client + sel_waittime) {
+ if (XGetSelectionOwner(dpy, XA_PRIMARY) == None) {
+ cutbuffer_send();
+ }
+ sent_sel = 1;
+ }
+
+ /* 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_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();
+ }
+ }
+ }
+
+ if (! have_clients || ! watch_selection) {
+ /*
+ * no need to monitor selections if no current clients
+ * or -nosel.
+ */
+ X_UNLOCK;
+ return;
+ }
+
+ /* 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_sel) {
+ selection_send(&xev);
+ }
+ }
+ }
+ if (now > last_request + 1) {
+ /*
+ * Every second or two, request PRIMARY, unless we
+ * already own it or there is no owner.
+ * TODO: even at this low rate we should look into
+ * and performance problems in odds cases, etc.
+ */
+ last_request = now;
+ if (! own_selection &&
+ XGetSelectionOwner(dpy, XA_PRIMARY) != None) {
+ XConvertSelection(dpy, XA_PRIMARY, XA_STRING,
+ XA_STRING, selwin, CurrentTime);
+ }
+ }
+ }
+
+ if (! own_selection) {
+ /*
+ * no need to do the PRIMARY maintenance tasks below if
+ * no we do not own it (right?).
+ */
+ X_UNLOCK;
+ return;
+ }
+
+ /* 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_string) {
+ free(xcut_string);
+ xcut_string = NULL;
+ }
+ }
+ }
+ X_UNLOCK;
+}
+
+/*
+ * hook called when a VNC client sends us some "XCut" text (rfbClientCutText).
+ */
+void xcut_receive(char *text, int len, rfbClientPtr cl) {
+
+ if (cl->viewOnly) {
+ return;
+ }
+ if (text == NULL || len == 0) {
+ return;
+ }
+
+ X_LOCK;
+
+ /* associate this text with PRIMARY (and SECONDARY...) */
+ if (! own_selection) {
+ own_selection = 1;
+ /* we need to grab the PRIMARY selection */
+ XSetSelectionOwner(dpy, XA_PRIMARY, selwin, CurrentTime);
+ XFlush(dpy);
+ }
+
+ /* duplicate the text string for our own use. */
+ if (xcut_string != NULL) {
+ free(xcut_string);
+ }
+ xcut_string = (unsigned char *)
+ malloc((size_t) (len+1) * sizeof(unsigned char));
+ strncpy(xcut_string, text, len);
+ xcut_string[len] = '\0'; /* make sure null terminated */
+
+ /* copy this text to CUT_BUFFER0 as well: */
+ XChangeProperty(dpy, rootwin, XA_CUT_BUFFER0, XA_STRING, 8,
+ PropModeReplace, text, len);
+ XFlush(dpy);
+
+ X_UNLOCK;
+
+ set_cutbuffer = 1;
+}
+
+void mark_hint(hint_t);
+
+/*
+ * Here begins a bit of a mess to experiment with multiple cursors ...
+ */
+typedef struct cursor_info {
+ char *data; /* data and mask pointers */
+ char *mask;
+ int wx, wy; /* size of cursor */
+ int sx, sy; /* shift to its centering point */
+ int reverse; /* swap black and white */
+} cursor_info_t;
+
+/* main cursor */
+static char* cur_data =
+" "
+" x "
+" xx "
+" xxx "
+" xxxx "
+" xxxxx "
+" xxxxxx "
+" xxxxxxx "
+" xxxxxxxx "
+" xxxxx "
+" xx xx "
+" x xx "
+" xx "
+" xx "
+" xx "
+" "
+" "
+" ";
+
+static char* cur_mask =
+"xx "
+"xxx "
+"xxxx "
+"xxxxx "
+"xxxxxx "
+"xxxxxxx "
+"xxxxxxxx "
+"xxxxxxxxx "
+"xxxxxxxxxx "
+"xxxxxxxxxx "
+"xxxxxxx "
+"xxx xxxx "
+"xx xxxx "
+" xxxx "
+" xxxx "
+" xx "
+" "
+" ";
+#define CUR_SIZE 18
+#define CUR_DATA cur_data
+#define CUR_MASK cur_mask
+cursor_info_t cur0 = {NULL, NULL, CUR_SIZE, CUR_SIZE, 0, 0, 0};
+
+/*
+ * It turns out we can at least detect mouse is on the root window so
+ * show it (under -mouseX or -X) with this familiar cursor...
+ */
+static char* root_data =
+" "
+" "
+" xxx xxx "
+" xxxx xxxx "
+" xxxxx xxxxx "
+" xxxxx xxxxx "
+" xxxxxxxxxx "
+" xxxxxxxx "
+" xxxxxx "
+" xxxxxx "
+" xxxxxxxx "
+" xxxxxxxxxx "
+" xxxxx xxxxx "
+" xxxxx xxxxx "
+" xxxx xxxx "
+" xxx xxx "
+" "
+" ";
+
+static char* root_mask =
+" "
+" xxxx xxxx "
+" xxxxx xxxxx "
+" xxxxxx xxxxxx "
+" xxxxxxx xxxxxxx "
+" xxxxxxxxxxxxxx "
+" xxxxxxxxxxxx "
+" xxxxxxxxxx "
+" xxxxxxxx "
+" xxxxxxxx "
+" xxxxxxxxxx "
+" xxxxxxxxxxxx "
+" xxxxxxxxxxxxxx "
+" xxxxxxx xxxxxxx "
+" xxxxxx xxxxxx "
+" xxxxx xxxxx "
+" xxxx xxxx "
+" ";
+cursor_info_t cur1 = {NULL, NULL, 18, 18, 8, 8, 1};
+
+cursor_info_t *cursors[2];
+void setup_cursors(void) {
+ /* TODO clean this up if we ever do more cursors... */
+
+ cur0.data = cur_data;
+ cur0.mask = cur_mask;
+
+ cur1.data = root_data;
+ cur1.mask = root_mask;
+
+ cursors[0] = &cur0;
+ cursors[1] = &cur1;
+}
+
+/*
+ * data and functions for -mouse real pointer position updates
+ */
+char cur_save[(4 * CUR_SIZE * CUR_SIZE)];
+int cur_save_x, cur_save_y, cur_save_w, cur_save_h;
+int cur_save_cx, cur_save_cy, cur_save_which, cur_saved = 0;
+
+/*
+ * save current cursor info and the patch of non-cursor data it covers
+ */
+void save_mouse_patch(int x, int y, int w, int h, int cx, int cy, int which) {
+ int pixelsize = bpp >> 3;
+ char *rfb_fb = screen->frameBuffer;
+ int ly, i = 0;
+
+ for (ly = y; ly < y + h; ly++) {
+ memcpy(cur_save+i, rfb_fb + ly * bytes_per_line
+ + x * pixelsize, w * pixelsize);
+
+ i += w * pixelsize;
+ }
+ cur_save_x = x; /* patch geometry */
+ cur_save_y = y;
+ cur_save_w = w;
+ cur_save_h = h;
+
+ cur_save_which = which; /* which cursor and its position */
+ cur_save_cx = cx;
+ cur_save_cy = cy;
+
+ cur_saved = 1;
+}
+
+/*
+ * put the non-cursor patch back in the rfb fb
+ */
+void restore_mouse_patch() {
+ int pixelsize = bpp >> 3;
+ char *rfb_fb = screen->frameBuffer;
+ int ly, i = 0;
+
+ if (! cur_saved) {
+ return; /* not yet saved */
+ }
+
+ for (ly = cur_save_y; ly < cur_save_y + cur_save_h; ly++) {
+ memcpy(rfb_fb + ly * bytes_per_line + cur_save_x * pixelsize,
+ cur_save+i, cur_save_w * pixelsize);
+ i += cur_save_w * pixelsize;
+ }
+}
+
+/*
+ * Descends window tree at pointer until the window cursor matches the current
+ * cursor. So far only used to detect if mouse is on root background or not.
+ * (returns 0 in that case, 1 otherwise).
+ *
+ * It seems impossible to do, but if the actual cursor could ever be
+ * determined we might want to hash that info on window ID or something...
+ */
+int tree_descend_cursor(void) {
+ Window r, c;
+ int rx, ry, wx, wy;
+ unsigned int mask;
+ int descend = 0, tries = 0, maxtries = 1;
+
+ X_LOCK;
+ c = window;
+ while (c) {
+ if (++tries > maxtries) {
+ descend = maxtries;
+ break;
+ }
+ if ( XTestCompareCurrentCursorWithWindow(dpy, c) ) {
+ break;
+ }
+ XQueryPointer(dpy, c, &r, &c, &rx, &ry, &wx, &wy, &mask);
+ descend++;
+ }
+ X_UNLOCK;
+ return descend;
+}
+
+void blackout_nearby_tiles(x, y, dt) {
+ int sx, sy, n, b;
+ int tx = x/tile_x;
+ int ty = y/tile_y;
+
+ if (! blackouts) {
+ return;
+ }
+ if (dt < 1) {
+ dt = 1;
+ }
+ /* loop over a range of tiles, blacking out as needed */
+
+ for (sx = tx - dt; sx <= tx + dt; sx++) {
+ if (sx < 0 || sx >= tile_x) {
+ continue;
+ }
+ for (sy = ty - dt; sy <= ty + dt; sy++) {
+ if (sy < 0 || sy >= tile_y) {
+ continue;
+ }
+ n = sx + sy * ntiles_x;
+ if (tile_blackout[n].cover == 0) {
+ continue;
+ }
+ for (b=0; b <= tile_blackout[n].count; b++) {
+ int x1, y1, x2, y2;
+ x1 = tile_blackout[n].bo[b].x1;
+ y1 = tile_blackout[n].bo[b].y1;
+ x2 = tile_blackout[n].bo[b].x2;
+ y2 = tile_blackout[n].bo[b].y2;
+ zero_fb(x1, y1, x2, y2);
+ }
+ }
+ }
+}
+
+/*
+ * Send rfbCursorPosUpdates back to clients that understand them. This
+ * seems to be TightVNC specific.
+ */
+void cursor_pos_updates(int x, int y) {
+ rfbClientIteratorPtr iter;
+ rfbClientPtr cl;
+ int cnt = 0;
+
+ if (! cursor_pos) {
+ return;
+ }
+ /* x and y are current positions of X11 pointer on the X11 display */
+ if (x == screen->cursorX && y == screen->cursorY) {
+ return;
+ }
+
+ if (screen->cursorIsDrawn) {
+ rfbUndrawCursor(screen);
+ }
+ LOCK(screen->cursorMutex);
+ if (! screen->cursorIsDrawn) {
+ screen->cursorX = x;
+ screen->cursorY = y;
+ }
+ UNLOCK(screen->cursorMutex);
+
+ iter = rfbGetClientIterator(screen);
+ while( (cl = rfbClientIteratorNext(iter)) ) {
+ if (! cl->enableCursorPosUpdates) {
+ continue;
+ }
+ if (cl == last_pointer_client) {
+ /*
+ * special case if this client was the last one to
+ * send a pointer position.
+ */
+ if (x == cursor_x && y == cursor_y) {
+ cl->cursorWasMoved = FALSE;
+ } else {
+ /* an X11 app evidently warped the pointer */
+ if (debug_pointer) {
+ rfbLog("cursor_pos_updates: warp "
+ "detected dx=%3d dy=%3d\n",
+ cursor_x - x, cursor_y - y);
+ }
+ cl->cursorWasMoved = TRUE;
+ cnt++;
+ }
+ } else {
+ cl->cursorWasMoved = TRUE;
+ cnt++;
+ }
+ }
+ rfbReleaseClientIterator(iter);
+
+ if (debug_pointer && cnt) {
+ rfbLog("cursor_pos_updates: sent position x=%3d y=%3d to %d"
+ " clients\n", x, y, cnt);
+ }
+}
+
+/*
+ * draw one of the mouse cursors into the rfb fb
+ */
+void draw_mouse(int x, int y, int which, int update) {
+ int px, py, i, offset;
+ int pixelsize = bpp >> 3;
+ char *rfb_fb = screen->frameBuffer;
+ char cdata, cmask;
+ char *data, *mask;
+ int white = 255, black = 0, shade;
+ int x0, x1, x2, y0, y1, y2;
+ int cur_x, cur_y, cur_sx, cur_sy, reverse;
+ static int first = 1;
+
+ if (! show_mouse) {
+ return;
+ }
+ if (first) {
+ first = 0;
+ setup_cursors();
+ }
+
+ data = cursors[which]->data; /* pattern data */
+ mask = cursors[which]->mask;
+ cur_x = cursors[which]->wx; /* widths */
+ cur_y = cursors[which]->wy;
+ cur_sx = cursors[which]->sx; /* shifts */
+ cur_sy = cursors[which]->sy;
+ reverse = cursors[which]->reverse; /* reverse video */
+
+ if (indexed_colour) {
+ black = BlackPixel(dpy, scr) % 256;
+ white = WhitePixel(dpy, scr) % 256;
+ }
+ if (reverse) {
+ int tmp = black;
+ black = white;
+ white = tmp;
+ }
+
+ /*
+ * notation:
+ * x0, y0: position after cursor shift (no edge corrections)
+ * x1, y1: corrected for lower boundary < 0
+ * x2, y2: position + cursor width and corrected for upper boundary
+ */
+
+ x0 = x1 = x - cur_sx; /* apply shift */
+ if (x1 < 0) x1 = 0;
+
+ y0 = y1 = y - cur_sy;
+ if (y1 < 0) y1 = 0;
+
+ x2 = x0 + cur_x; /* apply width for upper endpoints */
+ if (x2 >= dpy_x) x2 = dpy_x - 1;
+
+ y2 = y0 + cur_y;
+ if (y2 >= dpy_y) y2 = dpy_y - 1;
+
+ /* save the patch and info about which cursor will overwrite it */
+ save_mouse_patch(x1, y1, x2 - x1, y2 - y1, x, y, which);
+
+ for (py = 0; py < cur_y; py++) {
+ if (y0 + py < 0 || y0 + py >= dpy_y) {
+ continue; /* off screen */
+ }
+ for (px = 0; px < cur_x; px++) {
+ if (x0 + px < 0 || x0 + px >= dpy_x){
+ continue; /* off screen */
+ }
+ cdata = data[px + py * cur_x];
+ cmask = mask[px + py * cur_x];
+
+ if (cmask != 'x') {
+ continue; /* transparent */
+ }
+
+ shade = white;
+ if (cdata != cmask) {
+ shade = black;
+ }
+
+ offset = (y0 + py)*bytes_per_line + (x0 + px)*pixelsize;
+
+ /* fill in each color byte in the fb */
+ for (i=0; i < pixelsize; i++) {
+ rfb_fb[offset+i] = (char) shade;
+ }
+ }
+ }
+
+ if (blackouts) {
+ /*
+ * loop over a range of tiles, blacking out as needed
+ * note we currently disable mouse drawing under blackouts.
+ */
+ static int mx = -1, my = -1;
+ int skip = 0;
+ if (mx < 0) {
+ mx = x;
+ my = y;
+ } else if (mx == x && my == y) {
+ skip = 1;
+ }
+ mx = x;
+ my = y;
+
+ if (! skip) {
+ blackout_nearby_tiles(x, y, 2);
+ }
+ }
+
+ if (update) {
+ /* x and y of the real (X server) mouse */
+ static int mouse_x = -1;
+ static int mouse_y = -1;
+
+ if (x != mouse_x || y != mouse_y) {
+ hint_t hint;
+
+ hint.x = x1;
+ hint.y = y2;
+ hint.w = x2 - x1;
+ hint.h = y2 - y1;
+
+ mark_hint(hint);
+
+ if (mouse_x < 0) {
+ mouse_x = 0;
+ }
+ if (mouse_y < 0) {
+ mouse_y = 0;
+ }
+
+ /* XXX this ignores change of shift... */
+ x1 = mouse_x - cur_sx;
+ if (x1 < 0) x1 = 0;
+
+ y1 = mouse_y - cur_sy;
+ if (y1 < 0) y1 = 0;
+
+ x2 = mouse_x - cur_sx + cur_x;
+ if (x2 >= dpy_x) x2 = dpy_x - 1;
+
+ y2 = mouse_y - cur_sy + cur_y;
+ if (y2 >= dpy_y) y2 = dpy_y - 1;
+
+ hint.x = x1;
+ hint.y = y2;
+ hint.w = x2 - x1;
+ hint.h = y2 - y1;
+
+ mark_hint(hint);
+
+ mouse_x = x;
+ mouse_y = y;
+ }
+ }
+}
+
+void redraw_mouse(void) {
+ if (cur_saved) {
+ /* redraw saved mouse from info (save_mouse_patch) */
+ draw_mouse(cur_save_cx, cur_save_cy, cur_save_which, 0);
+ }
+}
+
+void update_mouse(void) {
+ Window root_w, child_w;
+ rfbBool ret;
+ int root_x, root_y, win_x, win_y, which = 0;
+ unsigned int mask;
+
+ X_LOCK;
+ ret = XQueryPointer(dpy, rootwin, &root_w, &child_w, &root_x, &root_y,
+ &win_x, &win_y, &mask);
+ X_UNLOCK;
+
+ if (! ret) {
+ return;
+ }
+
+ if (show_root_cursor) {
+ int descend;
+ if ( (descend = tree_descend_cursor()) ) {
+ which = 0;
+ } else {
+ which = 1;
+ }
+ }
+
+ cursor_pos_updates(root_x - off_x, root_y - off_y);
+ draw_mouse(root_x - off_x, root_y - off_y, which, 1);
+}
+
+/*
+ * For the subwin case follows the window if it is moved.
+ */
+void set_offset(void) {
+ Window w;
+ if (! subwin) {
+ return;
+ }
+ X_LOCK;
+ XTranslateCoordinates(dpy, window, rootwin, 0, 0, &off_x, &off_y, &w);
+ X_UNLOCK;
+}
+
+/*
+ * Some handling of 8bpp PseudoColor colormaps. Called for initializing
+ * the clients and dynamically if -flashcmap is specified.
+ */
+#define NCOLOR 256
+void set_colormap(void) {
+ static int first = 1;
+ static XColor color[NCOLOR], prev[NCOLOR];
+ Colormap cmap;
+ Visual *vis;
+ int i, ncells, diffs = 0;
+
+ if (first) {
+ screen->colourMap.count = NCOLOR;
+ screen->rfbServerFormat.trueColour = FALSE;
+ screen->colourMap.is16 = TRUE;
+ screen->colourMap.data.shorts = (unsigned short*)
+ malloc(3*sizeof(short) * NCOLOR);
+ }
+
+ for (i=0; i < NCOLOR; i++) {
+ prev[i].red = color[i].red;
+ prev[i].green = color[i].green;
+ prev[i].blue = color[i].blue;
+ }
+
+ X_LOCK;
+
+ cmap = DefaultColormap(dpy, scr);
+ ncells = CellsOfScreen(ScreenOfDisplay(dpy, scr));
+ vis = visual;
+
+ if (subwin) {
+ XWindowAttributes attr;
+
+ if (XGetWindowAttributes(dpy, window, &attr)) {
+ cmap = attr.colormap;
+ vis = attr.visual;
+ ncells = vis->map_entries;
+ }
+ }
+
+ if (first && ncells != NCOLOR) {
+ if (! quiet) {
+ fprintf(stderr, "set_colormap: number of cells is %d "
+ "instead of %d.\n", ncells, NCOLOR);
+ }
+ screen->colourMap.count = ncells;
+ }
+
+ if (flash_cmap && ! first) {
+ XWindowAttributes attr;
+ Window r, c;
+ int rx, ry, wx, wy, tries = 0;
+ unsigned int m;
+
+ c = window;
+ while (c && tries++ < 16) {
+ /* XXX XQueryTree somehow? */
+ XQueryPointer(dpy, c, &r, &c, &rx, &ry, &wx, &wy, &m);
+ if (c && XGetWindowAttributes(dpy, c, &attr)) {
+ if (attr.colormap && attr.map_installed) {
+ cmap = attr.colormap;
+ vis = attr.visual;
+ ncells = vis->map_entries;
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ if (ncells > NCOLOR && ! quiet) {
+ fprintf(stderr, "set_colormap: big problem: ncells=%d > %d\n",
+ ncells, NCOLOR);
+ }
+
+ if (vis->class == TrueColor || vis->class == DirectColor) {
+ /*
+ * Kludge to make 8bpp TrueColor & DirectColor be like
+ * the StaticColor map. The ncells = 8 is "8 per subfield"
+ * mentioned in xdpyinfo. Looks OK... likely fortuitously.
+ */
+ if (ncells == 8) {
+ ncells = NCOLOR;
+ }
+ }
+
+ for (i=0; i < ncells; i++) {
+ color[i].pixel = i;
+ color[i].pad = 0;
+ }
+
+ XQueryColors(dpy, cmap, color, ncells);
+
+ X_UNLOCK;
+
+ for(i=0; i < ncells; i++) {
+ screen->colourMap.data.shorts[i*3+0] = color[i].red;
+ screen->colourMap.data.shorts[i*3+1] = color[i].green;
+ screen->colourMap.data.shorts[i*3+2] = color[i].blue;
+
+ if (prev[i].red != color[i].red ||
+ prev[i].green != color[i].green ||
+ prev[i].blue != color[i].blue ) {
+ diffs++;
+ }
+ }
+
+ if (diffs && ! first) {
+ if (! all_clients_initialized()) {
+ rfbLog("set_colormap: warning: sending cmap "
+ "with uninitialized clients.\n");
+ }
+ rfbSetClientColourMaps(screen, 0, ncells);
+ }
+
+ first = 0;
+}
+
+/*
+ * Experimental mode to force the visual of the window instead of querying
+ * it. Currently just used for testing or overriding some rare cases.
+ * Input string can be a decimal or 0x hex or something like TrueColor
+ * or TrueColor:24 to force a depth as well.
+ */
+void set_visual(char *vstring) {
+ int vis, defdepth = DefaultDepth(dpy, scr);
+ XVisualInfo vinfo;
+ char *p;
+
+ if (! quiet) {
+ fprintf(stderr, "set_visual: %s\n", vstring);
+ }
+
+ if ((p = strchr(vstring, ':')) != NULL) {
+ visual_depth = atoi(p+1);
+ *p = '\0';
+ } else {
+ visual_depth = defdepth;
+ }
+ if (strcmp(vstring, "StaticGray") == 0) {
+ vis = StaticGray;
+ } else if (strcmp(vstring, "GrayScale") == 0) {
+ vis = GrayScale;
+ } else if (strcmp(vstring, "StaticColor") == 0) {
+ vis = StaticColor;
+ } else if (strcmp(vstring, "PseudoColor") == 0) {
+ vis = PseudoColor;
+ } else if (strcmp(vstring, "TrueColor") == 0) {
+ vis = TrueColor;
+ } else if (strcmp(vstring, "DirectColor") == 0) {
+ vis = DirectColor;
+ } else {
+ int v_in;
+ if (sscanf(vstring, "0x%x", &v_in) != 1) {
+ if (sscanf(vstring, "%d", &v_in) == 1) {
+ visual_id = (VisualID) v_in;
+ return;
+ }
+ fprintf(stderr, "bad -visual arg: %s\n", vstring);
+ exit(1);
+ }
+ visual_id = (VisualID) v_in;
+ return;
+ }
+ if (XMatchVisualInfo(dpy, scr, visual_depth, vis, &vinfo)) {
+ ;
+ } else if (XMatchVisualInfo(dpy, scr, defdepth, vis, &vinfo)) {
+ ;
+ } else {
+ fprintf(stderr, "could not find visual: %s\n", vstring);
+ exit(1);
+ }
+ visual_id = vinfo.visualid;
+}
+
+/*
+ * Presumably under -nofb the clients will never request the framebuffer.
+ * But we have gotten such a request... so let's just give them
+ * the current view on the display. n.b. x2vnc and perhaps win2vnc
+ * requests a 1x1 pixel for some workaround so sadly this evidently
+ * nearly always happens.
+ */
+void nofb_hook(rfbClientPtr cl) {
+ static int loaded_fb = 0;
+ XImage *fb;
+ if (loaded_fb) {
+ return;
+ }
+ rfbLog("framebuffer requested in -nofb mode by client %s\n", cl->host);
+ fb = XGetImage(dpy, window, 0, 0, dpy_x, dpy_y, AllPlanes, ZPixmap);
+ screen->frameBuffer = fb->data;
+ loaded_fb = 1;
+ screen->displayHook = NULL;
+}
+
+int got_rfbport = 0;
+int got_alwaysshared = 0;
+int got_nevershared = 0;
+/*
+ * initialize the rfb framebuffer/screen
+ */
+void initialize_screen(int *argc, char **argv, XImage *fb) {
+ int have_masks = 0;
+
+ screen = rfbGetScreen(argc, argv, fb->width, fb->height,
+ fb->bits_per_pixel, 8, fb->bits_per_pixel/8);
+
+ if (! quiet) {
+ fprintf(stderr, "\n");
+ }
+
+ if (! screen) {
+ int i;
+ rfbLog("\n");
+ rfbLog("failed to create rfb screen.\n");
+ for (i=0; i< *argc; i++) {
+ rfbLog("\t[%d] %s\n", i, argv[i]);
+ }
+ clean_up_exit(1);
+ }
+
+/*
+ * This ifdef is a transient for source compatibility for people who download
+ * the x11vnc.c file by itself and plop it down into their libvncserver tree.
+ * Remove at some point. BTW, this assumes no usage of earlier "0.7pre".
+ */
+#ifdef LIBVNCSERVER_VERSION
+if (strcmp(LIBVNCSERVER_VERSION, "0.5") && strcmp(LIBVNCSERVER_VERSION, "0.6")) {
+ if (*argc != 1) {
+ int i;
+ rfbLog("*** unrecognized option(s) ***\n");
+ for (i=1; i< *argc; i++) {
+ rfbLog("\t[%d] %s\n", i, argv[i]);
+ }
+ rfbLog("for a list of options run: x11vnc -help\n");
+ clean_up_exit(1);
+ }
+}
+#endif
+
+ screen->paddedWidthInBytes = fb->bytes_per_line;
+ screen->rfbServerFormat.bitsPerPixel = fb->bits_per_pixel;
+ screen->rfbServerFormat.depth = fb->depth;
+ screen->rfbServerFormat.trueColour = (uint8_t) TRUE;
+ have_masks = ((fb->red_mask|fb->green_mask|fb->blue_mask) != 0);
+ if (force_indexed_color) {
+ have_masks = 0;
+ }
+
+ if ( ! have_masks && screen->rfbServerFormat.bitsPerPixel == 8
+ && CellsOfScreen(ScreenOfDisplay(dpy,scr)) ) {
+ /* indexed colour */
+ if (!quiet) rfbLog("Using X display with 8bpp indexed color\n");
+ indexed_colour = 1;
+ set_colormap();
+ } else {
+ /* general case ... */
+ if (! quiet) {
+ rfbLog("Using X display with %dbpp depth=%d true "
+ "color\n", fb->bits_per_pixel, fb->depth);
+ }
+
+ /* convert masks to bit shifts and max # colors */
+ screen->rfbServerFormat.redShift = 0;
+ if ( fb->red_mask ) {
+ while ( ! (fb->red_mask
+ & (1 << screen->rfbServerFormat.redShift) ) ) {
+ screen->rfbServerFormat.redShift++;
+ }
+ }
+ screen->rfbServerFormat.greenShift = 0;
+ if ( fb->green_mask ) {
+ while ( ! (fb->green_mask
+ & (1 << screen->rfbServerFormat.greenShift) ) ) {
+ screen->rfbServerFormat.greenShift++;
+ }
+ }
+ screen->rfbServerFormat.blueShift = 0;
+ if ( fb->blue_mask ) {
+ while ( ! (fb->blue_mask
+ & (1 << screen->rfbServerFormat.blueShift) ) ) {
+ screen->rfbServerFormat.blueShift++;
+ }
+ }
+ screen->rfbServerFormat.redMax
+ = fb->red_mask >> screen->rfbServerFormat.redShift;
+ screen->rfbServerFormat.greenMax
+ = fb->green_mask >> screen->rfbServerFormat.greenShift;
+ screen->rfbServerFormat.blueMax
+ = fb->blue_mask >> screen->rfbServerFormat.blueShift;
+ }
+
+ /* nofb is for pointer/keyboard only handling. */
+ if (nofb) {
+ screen->frameBuffer = NULL;
+ screen->displayHook = nofb_hook;
+ } else {
+ screen->frameBuffer = fb->data;
+ }
+
+ /* called from inetd, we need to treat stdio as our socket */
+ if (inetd) {
+ int fd = dup(0);
+ if (fd < 3) {
+ rfbErr("dup(0) = %d failed.\n", fd);
+ perror("dup");
+ clean_up_exit(1);
+ }
+ fclose(stdin);
+ fclose(stdout);
+ /* we keep stderr for logging */
+ screen->inetdSock = fd;
+ screen->rfbPort = 0;
+
+ } else if (! got_rfbport) {
+ screen->autoPort = TRUE;
+ }
+
+ if (! got_nevershared && ! got_alwaysshared) {
+ if (shared) {
+ screen->rfbAlwaysShared = TRUE;
+ } else {
+ screen->rfbDontDisconnect = TRUE;
+ screen->rfbNeverShared = TRUE;
+ }
+ }
+ /* XXX the following is based on libvncserver defaults. */
+ if (screen->rfbDeferUpdateTime == 5) {
+ /* XXX will be fixed someday */
+ screen->rfbDeferUpdateTime = defer_update;
+ }
+
+ /* event callbacks: */
+ screen->newClientHook = new_client;
+ screen->kbdAddEvent = keyboard;
+ screen->ptrAddEvent = pointer;
+ if (watch_selection) {
+ screen->setXCutText = xcut_receive;
+ }
+
+ if (local_cursor) {
+ cursor = rfbMakeXCursor(CUR_SIZE, CUR_SIZE, CUR_DATA, CUR_MASK);
+ screen->cursor = cursor;
+ } else {
+ screen->cursor = NULL;
+ }
+
+ rfbInitServer(screen);
+
+ bytes_per_line = screen->paddedWidthInBytes;
+ bpp = screen->rfbServerFormat.bitsPerPixel;
+ depth = screen->rfbServerFormat.depth;
+}
+
+/*
+ * Take a comma separated list of geometries: WxH+X+Y and register them as
+ * rectangles to black out from the screen.
+ */
+void initialize_blackout (char *list) {
+ char *p, *blist = strdup(list);
+ int x, y, X, Y, h, w;
+
+ p = strtok(blist, ",");
+ while (p) {
+ /* handle +/-x and +/-y */
+ if (sscanf(p, "%dx%d+%d+%d", &w, &h, &x, &y) == 4) {
+ ;
+ } else if (sscanf(p, "%dx%d-%d+%d", &w, &h, &x, &y) == 4) {
+ x = dpy_x - x - w;
+ } else if (sscanf(p, "%dx%d+%d-%d", &w, &h, &x, &y) == 4) {
+ y = dpy_y - y - h;
+ } else if (sscanf(p, "%dx%d-%d-%d", &w, &h, &x, &y) == 4) {
+ x = dpy_x - x - w;
+ y = dpy_y - y - h;
+ } else {
+ if (*p != '\0') {
+ rfbLog("skipping invalid geometry: %s\n", p);
+ }
+ p = strtok(NULL, ",");
+ continue;
+ }
+ X = x + w;
+ Y = y + h;
+ if (x < 0 || x > dpy_x || y < 0 || y > dpy_y ||
+ X < 0 || X > dpy_x || Y < 0 || Y > dpy_y) {
+ rfbLog("skipping invalid blackout geometry: %s x="
+ "%d-%d,y=%d-%d,w=%d,h=%d\n", p, x, X, y, Y, w, h);
+ } else {
+ rfbLog("blackout rect: %s: x=%d-%d y=%d-%d\n", p,
+ x, X, y, Y);
+
+ /*
+ * note that the black out is x1 <= x but x < x2
+ * for the region. i.e. the x2, y2 are outside
+ * by 1 pixel.
+ */
+ black[blackouts].x1 = x;
+ black[blackouts].y1 = y;
+ black[blackouts].x2 = X;
+ black[blackouts].y2 = Y;
+ blackouts++;
+ if (blackouts >= 100) {
+ rfbLog("too many blackouts: %d\n", blackouts);
+ break;
+ }
+ }
+ p = strtok(NULL, ",");
+ }
+ free(blist);
+}
+
+/*
+ * Now that all blackout rectangles have been constructed, see what overlap
+ * they have with the tiles in the system. If a tile is touched by a
+ * blackout, record information.
+ */
+void blackout_tiles() {
+ int tx, ty;
+ if (! blackouts) {
+ return;
+ }
+
+ /*
+ * to simplify things drop down to single copy mode, no vcr, etc...
+ */
+ single_copytile = 1;
+ if (show_mouse) {
+ rfbLog("disabling remote mouse drawing due to blackouts\n");
+ show_mouse = 0;
+ }
+
+ /* loop over all tiles. */
+ for (ty=0; ty < ntiles_y; ty++) {
+ for (tx=0; tx < ntiles_x; tx++) {
+ sraRegionPtr tile_reg, black_reg;
+ sraRect rect;
+ sraRectangleIterator *iter;
+ int n, b, x1, y1, x2, y2, cnt;
+
+ /* tile number and coordinates: */
+ n = tx + ty * ntiles_x;
+ x1 = tx * tile_x;
+ y1 = ty * tile_y;
+ x2 = x1 + tile_x;
+ y2 = y1 + tile_y;
+ if (x2 > dpy_x) {
+ x2 = dpy_x;
+ }
+ if (y2 > dpy_y) {
+ y2 = dpy_y;
+ }
+
+ /* make regions for the tile and the blackouts: */
+ black_reg = (sraRegionPtr) sraRgnCreate();
+ tile_reg = (sraRegionPtr) sraRgnCreateRect(x1, y1,
+ x2, y2);
+
+ tile_blackout[n].cover = 0;
+ tile_blackout[n].count = 0;
+
+ /* union of blackouts */
+ for (b=0; b < blackouts; b++) {
+ sraRegionPtr tmp_reg = (sraRegionPtr)
+ sraRgnCreateRect(black[b].x1, black[b].y1,
+ black[b].x2, black[b].y2);
+
+ sraRgnOr(black_reg, tmp_reg);
+ sraRgnDestroy(tmp_reg);
+ }
+
+ if (! sraRgnAnd(black_reg, tile_reg)) {
+ /*
+ * no intersection for this tile, so we
+ * are done.
+ */
+ sraRgnDestroy(black_reg);
+ sraRgnDestroy(tile_reg);
+ continue;
+ }
+
+ /*
+ * loop over rectangles that make up the blackout
+ * region:
+ */
+ cnt = 0;
+ iter = sraRgnGetIterator(black_reg);
+ while (sraRgnIteratorNext(iter, &rect)) {
+
+ /* make sure x1 < x2 and y1 < y2 */
+ if (rect.x1 > rect.x2) {
+ int tmp = rect.x2;
+ rect.x2 = rect.x1;
+ rect.x1 = tmp;
+ }
+ if (rect.y1 > rect.y2) {
+ int tmp = rect.y2;
+ rect.y2 = rect.y1;
+ rect.y1 = tmp;
+ }
+
+ /* store coordinates */
+ tile_blackout[n].bo[cnt].x1 = rect.x1;
+ tile_blackout[n].bo[cnt].y1 = rect.y1;
+ tile_blackout[n].bo[cnt].x2 = rect.x2;
+ tile_blackout[n].bo[cnt].y2 = rect.y2;
+
+ /* note if the tile is completely obscured */
+ if (rect.x1 == x1 && rect.y1 == y1 &&
+ rect.x2 == x2 && rect.y2 == y2) {
+ tile_blackout[n].cover = 2;
+ } else {
+ tile_blackout[n].cover = 1;
+ }
+
+ if (++cnt >= 10) {
+ rfbLog("too many blackout rectangles "
+ "for tile %d=%d,%d.\n", n, tx, ty);
+ break;
+ }
+ }
+
+ sraRgnReleaseIterator(iter);
+ sraRgnDestroy(black_reg);
+ sraRgnDestroy(tile_reg);
+
+ tile_blackout[n].count = cnt;
+ }
+ }
+}
+
+void initialize_xinerama () {
+#ifndef LIBVNCSERVER_HAVE_LIBXINERAMA
+ rfbLog("Xinerama: Library libXinerama is not available to determine\n");
+ rfbLog("Xinerama: the head geometries, consider using -blackout\n");
+ rfbLog("Xinerama: if the screen is non-rectangular.\n");
+#else
+ XineramaScreenInfo *sc, *xineramas;
+ sraRegionPtr black_region, tmp_region;
+ sraRectangleIterator *iter;
+ sraRect rect;
+ char *bstr, *tstr;
+ int ev, er, i, n, rcnt;
+
+ if (! XineramaQueryExtension(dpy, &ev, &er)) {
+ rfbLog("Xinerama: disabling: display does not support it.\n");
+ xinerama = 0;
+ return;
+ }
+ if (! XineramaIsActive(dpy)) {
+ /* n.b. change to XineramaActive(dpy, window) someday */
+ rfbLog("Xinerama: disabling: not active on display.\n");
+ xinerama = 0;
+ return;
+ }
+
+ /* n.b. change to XineramaGetData() someday */
+ xineramas = XineramaQueryScreens(dpy, &n);
+ rfbLog("Xinerama: number of sub-screens: %d\n", n);
+
+ if (n == 1) {
+ rfbLog("Xinerama: no blackouts needed (only one"
+ " sub-screen)\n");
+ XFree(xineramas);
+ return; /* must be OK w/o change */
+ }
+
+ black_region = sraRgnCreateRect(0, 0, dpy_x, dpy_y);
+
+ sc = xineramas;
+ for (i=0; i<n; i++) {
+ int x, y, w, h;
+
+ x = sc->x_org;
+ y = sc->y_org;
+ w = sc->width;
+ h = sc->height;
+
+ tmp_region = sraRgnCreateRect(x, y, x + w, y + h);
+
+ sraRgnSubtract(black_region, tmp_region);
+ sraRgnDestroy(tmp_region);
+ sc++;
+ }
+ XFree(xineramas);
+
+ if (sraRgnEmpty(black_region)) {
+ rfbLog("Xinerama: no blackouts needed (screen fills"
+ " rectangle)\n");
+ sraRgnDestroy(black_region);
+ return;
+ }
+
+ /* max len is 10000x10000+10000+10000 (23 chars) per geometry */
+ rcnt = (int) sraRgnCountRects(black_region);
+ bstr = (char *) malloc(30 * rcnt * sizeof(char));
+ tstr = (char *) malloc(30 * sizeof(char));
+ bstr[0] = '\0';
+
+ iter = sraRgnGetIterator(black_region);
+ while (sraRgnIteratorNext(iter, &rect)) {
+ int x, y, w, h;
+
+ /* make sure x1 < x2 and y1 < y2 */
+ if (rect.x1 > rect.x2) {
+ int tmp = rect.x2;
+ rect.x2 = rect.x1;
+ rect.x1 = tmp;
+ }
+ if (rect.y1 > rect.y2) {
+ int tmp = rect.y2;
+ rect.y2 = rect.y1;
+ rect.y1 = tmp;
+ }
+ x = rect.x1;
+ y = rect.y1;
+ w = rect.x2 - x;
+ h = rect.y2 - y;
+ sprintf(tstr, "%dx%d+%d+%d,", w, h, x, y);
+ strcat(bstr, tstr);
+ }
+ initialize_blackout(bstr);
+
+ free(bstr);
+ free(tstr);
+#endif
+}
+
+/*
+ * Fill the framebuffer with zero for the prescribed rectangle
+ */
+void zero_fb(x1, y1, x2, y2) {
+ int pixelsize = bpp >> 3;
+ int line, fill = 0;
+ char *dst;
+
+ if (x1 < 0 || x2 <= x1 || x2 > dpy_x) {
+ return;
+ }
+ if (y1 < 0 || y2 <= y1 || y2 > dpy_y) {
+ return;
+ }
+
+ dst = screen->frameBuffer + y1 * bytes_per_line + x1 * pixelsize;
+ line = y1;
+ while (line++ < y2) {
+ memset(dst, fill, (size_t) (x2 - x1) * pixelsize);
+ dst += bytes_per_line;
+ }
+}
+
+/*
+ * Fill the framebuffer with zeros for each blackout region
+ */
+void blackout_regions() {
+ int i;
+ for (i=0; i < blackouts; i++) {
+ zero_fb(black[i].x1, black[i].y1, black[i].x2, black[i].y2);
+ }
+}
+
+/*
+ * setup tile numbers and allocate the tile and hint arrays:
+ */
+void initialize_tiles() {
+
+ ntiles_x = (dpy_x - 1)/tile_x + 1;
+ ntiles_y = (dpy_y - 1)/tile_y + 1;
+ ntiles = ntiles_x * ntiles_y;
+
+ tile_has_diff = (unsigned char *)
+ malloc((size_t) (ntiles * sizeof(unsigned char)));
+ tile_tried = (unsigned char *)
+ malloc((size_t) (ntiles * sizeof(unsigned char)));
+ tile_blackout = (tile_blackout_t *)
+ malloc((size_t) (ntiles * sizeof(tile_blackout_t)));
+ tile_region = (region_t *) malloc((size_t) (ntiles * sizeof(region_t)));
+
+ tile_row = (XImage **)
+ malloc((size_t) ((ntiles_x + 1) * sizeof(XImage *)));
+ tile_row_shm = (XShmSegmentInfo *)
+ malloc((size_t) ((ntiles_x + 1) * sizeof(XShmSegmentInfo)));
+
+ /* there will never be more hints than tiles: */
+ hint_list = (hint_t *) malloc((size_t) (ntiles * sizeof(hint_t)));
+}
+
+/*
+ * silly function to factor dpy_y until fullscreen shm is not bigger than max.
+ * should always work unless dpy_y is a large prime or something... under
+ * failure fs_factor remains 0 and no fullscreen updates will be tried.
+ */
+void set_fs_factor(int max) {
+ int f, fac = 1, n = dpy_y;
+
+ if ( (bpp/8) * dpy_x * dpy_y <= max ) {
+ fs_factor = 1;
+ return;
+ }
+ for (f=2; f <= 101; f++) {
+ while (n % f == 0) {
+ n = n / f;
+ fac = fac * f;
+ if ( (bpp/8) * dpy_x * (dpy_y/fac) <= max ) {
+ fs_factor = fac;
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * set up an XShm image
+ */
+int shm_create(XShmSegmentInfo *shm, XImage **ximg_ptr, int w, int h,
+ char *name) {
+
+ XImage *xim;
+
+ shm->shmid = -1;
+ shm->shmaddr = (char *) -1;
+ *ximg_ptr = NULL;
+
+ if (nofb) {
+ return 1;
+ }
+
+ X_LOCK;
+
+ if (! using_shm) {
+ /* we only need the XImage created */
+ xim = XCreateImage(dpy, visual, depth, ZPixmap, 0, NULL, w, h,
+ BitmapPad(dpy), 0);
+
+ X_UNLOCK;
+
+ if (xim == NULL) {
+ rfbErr("XCreateImage(%s) failed.\n", name);
+ return 0;
+ }
+ xim->data = (char *) malloc(xim->bytes_per_line * xim->height);
+ if (xim->data == NULL) {
+ rfbErr("XCreateImage(%s) data malloc failed.\n", name);
+ return 0;
+ }
+ if (flip_byte_order) {
+ static int reported = 0;
+ char *bo;
+ if (xim->byte_order == LSBFirst) {
+ bo = "MSBFirst";
+ xim->byte_order = MSBFirst;
+ xim->bitmap_bit_order = MSBFirst;
+ } else {
+ bo = "LSBFirst";
+ xim->byte_order = LSBFirst;
+ xim->bitmap_bit_order = LSBFirst;
+ }
+ if (! reported && ! quiet) {
+ rfbLog("changing XImage byte order"
+ " to %s\n", bo);
+ reported = 1;
+ }
+ }
+
+ *ximg_ptr = xim;
+ return 1;
+ }
+
+ xim = XShmCreateImage(dpy, visual, depth, ZPixmap, NULL, shm, w, h);
+
+ if (xim == NULL) {
+ rfbErr("XShmCreateImage(%s) failed.\n", name);
+ X_UNLOCK;
+ return 0;
+ }
+
+ *ximg_ptr = xim;
+
+ shm->shmid = shmget(IPC_PRIVATE,
+ xim->bytes_per_line * xim->height, IPC_CREAT | 0777);
+
+ if (shm->shmid == -1) {
+ rfbErr("shmget(%s) failed.\n", name);
+ perror("shmget");
+
+ XDestroyImage(xim);
+ *ximg_ptr = NULL;
+
+ X_UNLOCK;
+ return 0;
+ }
+
+ shm->shmaddr = xim->data = (char *) shmat(shm->shmid, 0, 0);
+
+ if (shm->shmaddr == (char *)-1) {
+ rfbErr("shmat(%s) failed.\n", name);
+ perror("shmat");
+
+ XDestroyImage(xim);
+ *ximg_ptr = NULL;
+
+ shmctl(shm->shmid, IPC_RMID, 0);
+ shm->shmid = -1;
+
+ X_UNLOCK;
+ return 0;
+ }
+
+ shm->readOnly = False;
+
+ if (! XShmAttach(dpy, shm)) {
+ rfbErr("XShmAttach(%s) failed.\n", name);
+ XDestroyImage(xim);
+ *ximg_ptr = NULL;
+
+ shmdt(shm->shmaddr);
+ shm->shmaddr = (char *) -1;
+
+ shmctl(shm->shmid, IPC_RMID, 0);
+ shm->shmid = -1;
+
+ X_UNLOCK;
+ return 0;
+ }
+
+ X_UNLOCK;
+ return 1;
+}
+
+void shm_delete(XShmSegmentInfo *shm) {
+ if (! using_shm) {
+ return;
+ }
+ if (shm->shmaddr != (char *) -1) {
+ shmdt(shm->shmaddr);
+ }
+ if (shm->shmid != -1) {
+ shmctl(shm->shmid, IPC_RMID, 0);
+ }
+}
+
+void shm_clean(XShmSegmentInfo *shm, XImage *xim) {
+ if (! using_shm || nofb) {
+ return;
+ }
+ X_LOCK;
+ if (shm->shmid != -1) {
+ XShmDetach(dpy, shm);
+ }
+ if (xim != NULL) {
+ XDestroyImage(xim);
+ }
+ X_UNLOCK;
+
+ shm_delete(shm);
+}
+
+void initialize_shm() {
+ int i;
+
+ /* set all shm areas to "none" before trying to create any */
+ scanline_shm.shmid = -1;
+ scanline_shm.shmaddr = (char *) -1;
+ scanline = NULL;
+ fullscreen_shm.shmid = -1;
+ fullscreen_shm.shmaddr = (char *) -1;
+ fullscreen = NULL;
+ for (i=1; i<=ntiles_x; i++) {
+ tile_row_shm[i].shmid = -1;
+ tile_row_shm[i].shmaddr = (char *) -1;
+ tile_row[i] = NULL;
+ }
+
+ /* the scanline (e.g. 1280x1) shared memory area image: */
+
+ if (! shm_create(&scanline_shm, &scanline, dpy_x, 1, "scanline")) {
+ clean_up_exit(1);
+ }
+
+ /*
+ * the fullscreen (e.g. 1280x1024/fs_factor) shared memory area image:
+ * (we cut down the size of the shm area to try avoid and shm segment
+ * limits, e.g. the default 1MB on Solaris)
+ */
+ set_fs_factor(1024 * 1024);
+ if (fs_frac >= 1.0) {
+ fs_frac = 1.1;
+ fs_factor = 0;
+ }
+ if (! fs_factor) {
+ rfbLog("warning: fullscreen updates are disabled.\n");
+ } else {
+ if (! shm_create(&fullscreen_shm, &fullscreen, dpy_x,
+ dpy_y/fs_factor, "fullscreen")) {
+ clean_up_exit(1);
+ }
+ }
+
+ /*
+ * for copy_tiles we need a lot of shared memory areas, one for
+ * each possible run length of changed tiles. 32 for 1024x768
+ * and 40 for 1280x1024, etc.
+ */
+
+ for (i=1; i<=ntiles_x; i++) {
+ if (! shm_create(&tile_row_shm[i], &tile_row[i], tile_x * i,
+ tile_y, "tile_row")) {
+ if (i == 1) {
+ clean_up_exit(1);
+ }
+ rfbLog("shm: Error creating shared memory tile-row for"
+ " len=%d,\n", i);
+ rfbLog("shm: reverting to -onetile mode. If this"
+ " problem persists\n");
+ rfbLog("shm: try using the -onetile or -noshm options"
+ " to limit\n");
+ rfbLog("shm: shared memory usage, or run ipcrm(1)"
+ " to manually\n");
+ rfbLog("shm: delete unattached shm segments.\n");
+ /* n.b.: "i" not "1", a kludge for cleanup */
+ single_copytile = i;
+ }
+ if (single_copytile && i >= 1) {
+ /* only need 1x1 tiles */
+ break;
+ }
+ }
+}
+
+
+/*
+ * A hint is a rectangular region built from 1 or more adjacent tiles
+ * glued together. Ultimately, this information in a single hint is sent
+ * to libvncserver rather than sending each tile separately.
+ */
+void create_tile_hint(int x, int y, int th, hint_t *hint) {
+ int w = dpy_x - x;
+ int h = dpy_y - y;
+
+ if (w > tile_x) {
+ w = tile_x;
+ }
+ if (h > th) {
+ h = th;
+ }
+
+ hint->x = x;
+ hint->y = y;
+ hint->w = w;
+ hint->h = h;
+}
+
+void extend_tile_hint(int x, int y, int th, hint_t *hint) {
+ int w = dpy_x - x;
+ int h = dpy_y - y;
+
+ if (w > tile_x) {
+ w = tile_x;
+ }
+ if (h > th) {
+ h = th;
+ }
+
+ if (hint->x > x) { /* extend to the left */
+ hint->w += hint->x - x;
+ hint->x = x;
+ }
+ if (hint->y > y) { /* extend upward */
+ hint->h += hint->y - y;
+ hint->y = y;
+ }
+
+ if (hint->x + hint->w < x + w) { /* extend to the right */
+ hint->w = x + w - hint->x;
+ }
+ if (hint->y + hint->h < y + h) { /* extend downward */
+ hint->h = y + h - hint->y;
+ }
+}
+
+void save_hint(hint_t hint, int loc) {
+ /* simply copy it to the global array for later use. */
+ hint_list[loc].x = hint.x;
+ hint_list[loc].y = hint.y;
+ hint_list[loc].w = hint.w;
+ hint_list[loc].h = hint.h;
+}
+
+/*
+ * Glue together horizontal "runs" of adjacent changed tiles into one big
+ * rectangle change "hint" to be passed to the vnc machinery.
+ */
+void hint_updates() {
+ hint_t hint;
+ int x, y, i, n, ty, th;
+ int hint_count = 0, in_run = 0;
+
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x; x++) {
+ n = x + y * ntiles_x;
+
+ if (tile_has_diff[n]) {
+ ty = tile_region[n].first_line;
+ th = tile_region[n].last_line - ty + 1;
+ if (! in_run) {
+ create_tile_hint( x * tile_x,
+ y * tile_y + ty, th, &hint);
+ in_run = 1;
+ } else {
+ extend_tile_hint( x * tile_x,
+ y * tile_y + ty, th, &hint);
+ }
+ } else {
+ if (in_run) {
+ /* end of a row run of altered tiles: */
+ save_hint(hint, hint_count++);
+ in_run = 0;
+ }
+ }
+ }
+ if (in_run) { /* save the last row run */
+ save_hint(hint, hint_count++);
+ in_run = 0;
+ }
+ }
+
+
+ for (i=0; i < hint_count; i++) {
+ /* pass update info to vnc: */
+ mark_hint(hint_list[i]);
+ }
+}
+
+/*
+ * Notifies libvncserver of a changed hint rectangle.
+ */
+void mark_hint(hint_t hint) {
+ int x = hint.x;
+ int y = hint.y;
+ int w = hint.w;
+ int h = hint.h;
+
+ rfbMarkRectAsModified(screen, x, y, x + w, y + h);
+}
+
+/*
+ * Notifies libvncserver of a changed tile rectangle.
+ */
+void mark_tile(int x, int y, int height) {
+ int w = dpy_x - x;
+ int h = dpy_y - y;
+
+ if (w > tile_x) {
+ w = tile_x;
+ }
+
+ /* height is the height of the changed portion of the tile */
+ if (h > height) {
+ h = height;
+ }
+
+ rfbMarkRectAsModified(screen, x, y, x + w, y + h);
+}
+
+/*
+ * Simply send each modified tile separately to the vnc machinery:
+ * (i.e. no hints)
+ */
+void tile_updates() {
+ int x, y, n, ty, th;
+
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x; x++) {
+ n = x + y * ntiles_x;
+
+ if (tile_has_diff[n]) {
+ ty = tile_region[n].first_line;
+ th = tile_region[n].last_line - ty + 1;
+
+ mark_tile(x * tile_x, y * tile_y + ty, th);
+ }
+ }
+ }
+}
+
+
+/*
+ * copy_tiles() gives a slight improvement over copy_tile() since
+ * adjacent runs of tiles are done all at once there is some savings
+ * due to contiguous memory access. Not a great speedup, but in
+ * some cases it can be up to 2X. Even more on a SunRay where no
+ * graphics hardware is involved in the read. Generally, graphics
+ * devices are optimized for write, not read, so we are limited by
+ * the read bandwidth, sometimes only 5 MB/sec on otherwise fast
+ * hardware.
+ */
+
+int *first_line = NULL, *last_line;
+unsigned short *left_diff, *right_diff;
+
+void copy_tiles(int tx, int ty, int nt) {
+ int x, y, line;
+ int size_x, size_y, width1, width2;
+ int off, len, n, dw, dx, t;
+ int w1, w2, dx1, dx2; /* tmps for normal and short tiles */
+ int pixelsize = bpp >> 3;
+ int first_min, last_max;
+
+ int restored_patch = 0; /* for show_mouse */
+
+ char *src, *dst, *s_src, *s_dst, *m_src, *m_dst;
+ char *h_src, *h_dst;
+ if (! first_line) {
+ /* allocate arrays first time in. */
+ int n = ntiles_x + 1;
+ first_line = (int *) malloc((size_t) (n * sizeof(int)));
+ last_line = (int *) malloc((size_t) (n * sizeof(int)));
+ left_diff = (unsigned short *)
+ malloc((size_t) (n * sizeof(unsigned short)));
+ right_diff = (unsigned short *)
+ malloc((size_t) (n * sizeof(unsigned short)));
+ }
+
+ x = tx * tile_x;
+ y = ty * tile_y;
+
+ size_x = dpy_x - x;
+ if ( size_x > tile_x * nt ) {
+ size_x = tile_x * nt;
+ width1 = tile_x;
+ width2 = tile_x;
+ } else {
+ /* short tile */
+ width1 = tile_x; /* internal tile */
+ width2 = size_x - (nt - 1) * tile_x; /* right hand tile */
+ }
+
+ size_y = dpy_y - y;
+ if ( size_y > tile_y ) {
+ size_y = tile_y;
+ }
+
+ n = tx + ty * ntiles_x; /* number of the first tile */
+
+ if (blackouts && tile_blackout[n].cover == 2) {
+ /*
+ * If there are blackouts and this tile is completely covered
+ * no need to poll screen or do anything else..
+ * n.b. we are int single copy_tile mode: nt=1
+ */
+ tile_has_diff[n] = 0;
+ return;
+ }
+
+ X_LOCK;
+ /* read in the whole tile run at once: */
+ if ( using_shm && size_x == tile_x * nt && size_y == tile_y ) {
+ /* general case: */
+ XShmGetImage(dpy, window, tile_row[nt], x, y, AllPlanes);
+ } else {
+ /*
+ * No shm or near bottom/rhs edge case:
+ * (but only if tile size does not divide screen size)
+ */
+ XGetSubImage(dpy, window, x, y, size_x, size_y, AllPlanes,
+ ZPixmap, tile_row[nt], 0, 0);
+ }
+ X_UNLOCK;
+
+ if (blackouts && tile_blackout[n].cover == 1) {
+ /*
+ * If there are blackouts and this tile is partially covered
+ * we should re-black-out the portion.
+ * n.b. we are int single copy_tile mode: nt=1
+ */
+ int x1, x2, y1, y2, b;
+ int w, s, fill = 0;
+
+ for (b=0; b < tile_blackout[n].count; b++) {
+ char *b_dst = tile_row[nt]->data;
+
+ x1 = tile_blackout[n].bo[b].x1 - x;
+ y1 = tile_blackout[n].bo[b].y1 - y;
+ x2 = tile_blackout[n].bo[b].x2 - x;
+ y2 = tile_blackout[n].bo[b].y2 - y;
+
+ w = (x2 - x1) * pixelsize;
+ s = x1 * pixelsize;
+
+ for (line = 0; line < size_y; line++) {
+ if (y1 <= line && line < y2) {
+ memset(b_dst + s, fill, (size_t) w);
+ }
+ b_dst += tile_row[nt]->bytes_per_line;
+ }
+ }
+ }
+
+ /*
+ * Some awkwardness wrt the little remote mouse patch we display.
+ * When threaded we want to have as small a window of time
+ * as possible when the mouse image is not in the fb, otherwise
+ * a libvncserver thread may send the uncorrected patch to the
+ * clients.
+ */
+ if (show_mouse && use_threads && cur_saved) {
+ /* check for overlap */
+ if (cur_save_x + cur_save_w > x && x + size_x > cur_save_x &&
+ cur_save_y + cur_save_h > y && y + size_y > cur_save_y) {
+
+ /* restore the real data to the rfb fb */
+ restore_mouse_patch();
+ restored_patch = 1;
+ }
+ }
+
+ src = tile_row[nt]->data;
+ dst = screen->frameBuffer + y * bytes_per_line + x * pixelsize;
+
+ s_src = src;
+ s_dst = dst;
+
+ for (t=1; t <= nt; t++) {
+ first_line[t] = -1;
+ }
+ /* find the first line with difference: */
+ w1 = width1 * pixelsize;
+ w2 = width2 * pixelsize;
+
+
+ /* foreach line: */
+ for (line = 0; line < size_y; line++) {
+ /* foreach horizontal tile: */
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] != -1) {
+ continue;
+ }
+
+ off = (t-1) * w1;
+ if (t == nt) {
+ len = w2; /* possible short tile */
+ } else {
+ len = w1;
+ }
+
+ if (memcmp(s_dst + off, s_src + off, len)) {
+ first_line[t] = line;
+ }
+ }
+ s_src += tile_row[nt]->bytes_per_line;
+ s_dst += bytes_per_line;
+ }
+
+ /* see if there were any differences for any tile: */
+ first_min = -1;
+ for (t=1; t <= nt; t++) {
+ tile_tried[n+(t-1)] = 1;
+ if (first_line[t] != -1) {
+ if (first_min == -1 || first_line[t] < first_min) {
+ first_min = first_line[t];
+ }
+ }
+ }
+ if (first_min == -1) {
+ /* no tile has a difference, note this and get out: */
+ for (t=1; t <= nt; t++) {
+ tile_has_diff[n+(t-1)] = 0;
+ }
+ if (restored_patch) {
+ redraw_mouse();
+ }
+ return;
+ } else {
+ /*
+ * at least one tile has a difference. make sure info
+ * is recorded (e.g. sometimes we guess tiles and they
+ * came in with tile_has_diff 0)
+ */
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] == -1) {
+ tile_has_diff[n+(t-1)] = 0;
+ } else {
+ tile_has_diff[n+(t-1)] = 1;
+ }
+ }
+ }
+
+ m_src = src + (tile_row[nt]->bytes_per_line * size_y);
+ m_dst = dst + (bytes_per_line * size_y);
+
+ for (t=1; t <= nt; t++) {
+ last_line[t] = first_line[t];
+ }
+
+ /* find the last line with difference: */
+ w1 = width1 * pixelsize;
+ w2 = width2 * pixelsize;
+
+ /* foreach line: */
+ for (line = size_y - 1; line > first_min; line--) {
+
+ m_src -= tile_row[nt]->bytes_per_line;
+ m_dst -= bytes_per_line;
+
+ /* foreach tile: */
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] == -1
+ || last_line[t] != first_line[t]) {
+ /* tile has no changes or already done */
+ continue;
+ }
+
+ off = (t-1) * w1;
+ if (t == nt) {
+ len = w2; /* possible short tile */
+ } else {
+ len = w1;
+ }
+ if (memcmp(m_dst + off, m_src + off, len)) {
+ last_line[t] = line;
+ }
+ }
+ }
+
+ /*
+ * determine the farthest down last changed line
+ * will be used below to limit our memcpy() to the framebuffer.
+ */
+ last_max = -1;
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] == -1) {
+ continue;
+ }
+ if (last_max == -1 || last_line[t] > last_max) {
+ last_max = last_line[t];
+ }
+ }
+
+ /* look for differences on left and right hand edges: */
+ for (t=1; t <= nt; t++) {
+ left_diff[t] = 0;
+ right_diff[t] = 0;
+ }
+
+ h_src = src;
+ h_dst = dst;
+
+ w1 = width1 * pixelsize;
+ w2 = width2 * pixelsize;
+
+ dx1 = (width1 - tile_fuzz) * pixelsize;
+ dx2 = (width2 - tile_fuzz) * pixelsize;
+ dw = tile_fuzz * pixelsize;
+
+ /* foreach line: */
+ for (line = 0; line < size_y; line++) {
+ /* foreach tile: */
+ for (t=1; t <= nt; t++) {
+ if (first_line[t] == -1) {
+ /* tile has no changes at all */
+ continue;
+ }
+
+ off = (t-1) * w1;
+ if (t == nt) {
+ dx = dx2; /* possible short tile */
+ if (dx <= 0) {
+ break;
+ }
+ } else {
+ dx = dx1;
+ }
+
+ if (! left_diff[t] && memcmp(h_dst + off,
+ h_src + off, dw)) {
+ left_diff[t] = 1;
+ }
+ if (! right_diff[t] && memcmp(h_dst + off + dx,
+ h_src + off + dx, dw) ) {
+ right_diff[t] = 1;
+ }
+ }
+ h_src += tile_row[nt]->bytes_per_line;
+ h_dst += bytes_per_line;
+ }
+
+ /* now finally copy the difference to the rfb framebuffer: */
+ s_src = src + tile_row[nt]->bytes_per_line * first_min;
+ s_dst = dst + bytes_per_line * first_min;
+
+ for (line = first_min; line <= last_max; line++) {
+ /* for I/O speed we do not do this tile by tile */
+ memcpy(s_dst, s_src, size_x * pixelsize);
+ s_src += tile_row[nt]->bytes_per_line;
+ s_dst += bytes_per_line;
+ }
+
+ if (restored_patch) {
+ redraw_mouse();
+ }
+
+ /* record all the info in the region array for this tile: */
+ for (t=1; t <= nt; t++) {
+ int s = t - 1;
+
+ if (first_line[t] == -1) {
+ /* tile unchanged */
+ continue;
+ }
+ tile_region[n+s].first_line = first_line[t];
+ tile_region[n+s].last_line = last_line[t];
+
+ tile_region[n+s].top_diff = 0;
+ tile_region[n+s].bot_diff = 0;
+ if ( first_line[t] < tile_fuzz ) {
+ tile_region[n+s].top_diff = 1;
+ }
+ if ( last_line[t] > (size_y - 1) - tile_fuzz ) {
+ tile_region[n+s].bot_diff = 1;
+ }
+
+ tile_region[n+s].left_diff = left_diff[t];
+ tile_region[n+s].right_diff = right_diff[t];
+ }
+
+}
+
+/*
+ * The copy_tile() call in the loop below copies the changed tile into
+ * the rfb framebuffer. Note that copy_tile() sets the tile_region
+ * struct to have info about the y-range of the changed region and also
+ * whether the tile edges contain diffs (within distance tile_fuzz).
+ *
+ * We use this tile_region info to try to guess if the downward and right
+ * tiles will have diffs. These tiles will be checked later in the loop
+ * (since y+1 > y and x+1 > x).
+ *
+ * See copy_tiles_backward_pass() for analogous checking upward and
+ * left tiles.
+ */
+int copy_all_tiles() {
+ int x, y, n, m;
+ int diffs = 0;
+
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x; x++) {
+ n = x + y * ntiles_x;
+
+ if (tile_has_diff[n]) {
+ copy_tiles(x, y, 1);
+ }
+ if (! tile_has_diff[n]) {
+ /*
+ * n.b. copy_tiles() may have detected
+ * no change and reset tile_has_diff to 0.
+ */
+ continue;
+ }
+ diffs++;
+
+ /* neighboring tile downward: */
+ if ( (y+1) < ntiles_y && tile_region[n].bot_diff) {
+ m = x + (y+1) * ntiles_x;
+ if (! tile_has_diff[m]) {
+ tile_has_diff[m] = 2;
+ }
+ }
+ /* neighboring tile to right: */
+ if ( (x+1) < ntiles_x && tile_region[n].right_diff) {
+ m = (x+1) + y * ntiles_x;
+ if (! tile_has_diff[m]) {
+ tile_has_diff[m] = 2;
+ }
+ }
+ }
+ }
+ return diffs;
+}
+
+/*
+ * Routine analogous to copy_all_tiles() above, but for horizontal runs
+ * of adjacent changed tiles.
+ */
+int copy_all_tile_runs() {
+ int x, y, n, m, i;
+ int diffs = 0;
+ int in_run = 0, run = 0;
+ int ntave = 0, ntcnt = 0;
+
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x + 1; x++) {
+ n = x + y * ntiles_x;
+
+ if (x != ntiles_x && tile_has_diff[n]) {
+ in_run = 1;
+ run++;
+ } else {
+ if (! in_run) {
+ in_run = 0;
+ run = 0;
+ continue;
+ }
+ copy_tiles(x - run, y, run);
+
+ ntcnt++;
+ ntave += run;
+ diffs += run;
+
+ /* neighboring tile downward: */
+ for (i=1; i <= run; i++) {
+ if ((y+1) < ntiles_y
+ && tile_region[n-i].bot_diff) {
+ m = (x-i) + (y+1) * ntiles_x;
+ if (! tile_has_diff[m]) {
+ tile_has_diff[m] = 2;
+ }
+ }
+ }
+
+ /* neighboring tile to right: */
+ if (((x-1)+1) < ntiles_x
+ && tile_region[n-1].right_diff) {
+ m = ((x-1)+1) + y * ntiles_x;
+ if (! tile_has_diff[m]) {
+ tile_has_diff[m] = 2;
+ }
+
+ /* note that this starts a new run */
+ in_run = 1;
+ run = 1;
+ } else {
+ in_run = 0;
+ run = 0;
+ }
+ }
+ }
+ }
+ return diffs;
+}
+
+/*
+ * Here starts a bunch of heuristics to guess/detect changed tiles.
+ * They are:
+ * copy_tiles_backward_pass, fill_tile_gaps/gap_try, grow_islands/island_try
+ */
+
+/*
+ * Try to predict whether the upward and/or leftward tile has been modified.
+ * copy_all_tiles() has already done downward and rightward tiles.
+ */
+int copy_tiles_backward_pass() {
+ int x, y, n, m;
+ int diffs = 0;
+
+ for (y = ntiles_y - 1; y >= 0; y--) {
+ for (x = ntiles_x - 1; x >= 0; x--) {
+ n = x + y * ntiles_x; /* number of this tile */
+
+ if (! tile_has_diff[n]) {
+ continue;
+ }
+
+ m = x + (y-1) * ntiles_x; /* neighboring tile upward */
+
+ if (y >= 1 && ! tile_has_diff[m] && tile_region[n].top_diff) {
+ if (! tile_tried[m]) {
+ tile_has_diff[m] = 2;
+ copy_tiles(x, y-1, 1);
+ }
+ }
+
+ m = (x-1) + y * ntiles_x; /* neighboring tile to left */
+
+ if (x >= 1 && ! tile_has_diff[m] && tile_region[n].left_diff) {
+ if (! tile_tried[m]) {
+ tile_has_diff[m] = 2;
+ copy_tiles(x-1, y, 1);
+ }
+ }
+ }
+ }
+ for (n=0; n < ntiles; n++) {
+ if (tile_has_diff[n]) {
+ diffs++;
+ }
+ }
+ return diffs;
+}
+
+void gap_try(int x, int y, int *run, int *saw, int along_x) {
+ int n, m, i, xt, yt;
+
+ n = x + y * ntiles_x;
+
+ if (! tile_has_diff[n]) {
+ if (*saw) {
+ (*run)++; /* extend the gap run. */
+ }
+ return;
+ }
+ if (! *saw || *run == 0 || *run > gaps_fill) {
+ *run = 0; /* unacceptable run. */
+ *saw = 1;
+ return;
+ }
+
+ for (i=1; i <= *run; i++) { /* iterate thru the run. */
+ if (along_x) {
+ xt = x - i;
+ yt = y;
+ } else {
+ xt = x;
+ yt = y - i;
+ }
+
+ m = xt + yt * ntiles_x;
+ if (tile_tried[m]) { /* do not repeat tiles */
+ continue;
+ }
+
+ copy_tiles(xt, yt, 1);
+ }
+ *run = 0;
+ *saw = 1;
+}
+
+/*
+ * Look for small gaps of unchanged tiles that may actually contain changes.
+ * E.g. when paging up and down in a web broswer or terminal there can
+ * be a distracting delayed filling in of such gaps. gaps_fill is the
+ * tweak parameter that sets the width of the gaps that are checked.
+ *
+ * BTW, grow_islands() is actually pretty successful at doing this too...
+ */
+int fill_tile_gaps() {
+ int x, y, run, saw;
+ int n, diffs = 0;
+
+ /* horizontal: */
+ for (y=0; y < ntiles_y; y++) {
+ run = 0;
+ saw = 0;
+ for (x=0; x < ntiles_x; x++) {
+ gap_try(x, y, &run, &saw, 1);
+ }
+ }
+
+ /* vertical: */
+ for (x=0; x < ntiles_x; x++) {
+ run = 0;
+ saw = 0;
+ for (y=0; y < ntiles_y; y++) {
+ gap_try(x, y, &run, &saw, 0);
+ }
+ }
+
+ for (n=0; n < ntiles; n++) {
+ if (tile_has_diff[n]) {
+ diffs++;
+ }
+ }
+ return diffs;
+}
+
+void island_try(int x, int y, int u, int v, int *run) {
+ int n, m;
+
+ n = x + y * ntiles_x;
+ m = u + v * ntiles_x;
+
+ if (tile_has_diff[n]) {
+ (*run)++;
+ } else {
+ *run = 0;
+ }
+
+ if (tile_has_diff[n] && ! tile_has_diff[m]) {
+ /* found a discontinuity */
+
+ if (tile_tried[m]) {
+ return;
+ } else if (*run < grow_fill) {
+ return;
+ }
+
+ copy_tiles(u, v, 1);
+ }
+}
+
+/*
+ * Scan looking for discontinuities in tile_has_diff[]. Try to extend
+ * the boundary of the discontinuity (i.e. make the island larger).
+ * Vertical scans are skipped since they do not seem to yield much...
+ */
+int grow_islands() {
+ int x, y, n, run;
+ int diffs = 0;
+
+ /*
+ * n.b. the way we scan here should keep an extension going,
+ * and so also fill in gaps effectively...
+ */
+
+ /* left to right: */
+ for (y=0; y < ntiles_y; y++) {
+ run = 0;
+ for (x=0; x <= ntiles_x - 2; x++) {
+ island_try(x, y, x+1, y, &run);
+ }
+ }
+ /* right to left: */
+ for (y=0; y < ntiles_y; y++) {
+ run = 0;
+ for (x = ntiles_x - 1; x >= 1; x--) {
+ island_try(x, y, x-1, y, &run);
+ }
+ }
+ for (n=0; n < ntiles; n++) {
+ if (tile_has_diff[n]) {
+ diffs++;
+ }
+ }
+ return diffs;
+}
+
+/*
+ * copy the whole X screen to the rfb framebuffer. For a large enough
+ * number of changed tiles, this is faster than tiles scheme at retrieving
+ * the info from the X server. Bandwidth to client and compression time
+ * are other issues... use -fs 1.0 to disable.
+ */
+void copy_screen() {
+ int pixelsize = bpp >> 3;
+ char *rfb_fb;
+ int i, y, block_size;
+
+ block_size = (dpy_x * (dpy_y/fs_factor) * pixelsize);
+
+ rfb_fb = screen->frameBuffer;
+ y = 0;
+
+ X_LOCK;
+
+ /* screen may be too big for 1 shm area, so broken into fs_factor */
+ for (i=0; i < fs_factor; i++) {
+ if (using_shm) {
+ XShmGetImage(dpy, window, fullscreen, 0, y, AllPlanes);
+ } else {
+ XGetSubImage(dpy, window, 0, y, fullscreen->width,
+ fullscreen->height, AllPlanes, ZPixmap, fullscreen,
+ 0, 0);
+ }
+ memcpy(rfb_fb, fullscreen->data, (size_t) block_size);
+
+ y += dpy_y / fs_factor;
+ rfb_fb += block_size;
+ }
+
+ X_UNLOCK;
+
+ if (blackouts) {
+ blackout_regions();
+ }
+
+ rfbMarkRectAsModified(screen, 0, 0, dpy_x, dpy_y);
+}
+
+/* profiling routines */
+
+double dtime(double *t_old) {
+ /*
+ * usage: call with 0.0 to initialize, subsequent calls give
+ * the time differences.
+ */
+ double t_now, dt;
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+ t_now = now.tv_sec + ( (double) now.tv_usec/1000000. );
+ if (*t_old == 0) {
+ *t_old = t_now;
+ return t_now;
+ }
+ dt = t_now - *t_old;
+ *t_old = t_now;
+ return(dt);
+}
+
+
+/*
+ * Utilities for managing the "naps" to cut down on amount of polling.
+ */
+void nap_set(int tile_cnt) {
+
+ if (count == 0) {
+ /* roll up check for all NSCAN scans */
+ nap_ok = 0;
+ if (naptile && nap_diff_count < 2 * NSCAN * naptile) {
+ /* "2" is a fudge to permit a bit of bg drawing */
+ nap_ok = 1;
+ }
+ nap_diff_count = 0;
+ }
+
+ if (show_mouse) {
+ /* kludge for the up to 4 tiles the mouse patch could occupy */
+ if ( tile_cnt > 4) {
+ last_event = time(0);
+ }
+ } else if (tile_cnt != 0) {
+ last_event = time(0);
+ }
+}
+
+void nap_sleep(int ms, int split) {
+ int i, input = got_user_input;
+
+ /* split up a long nap to improve the wakeup time */
+ for (i=0; i<split; i++) {
+ usleep(ms * 1000 / split);
+ if (! use_threads && i != split - 1) {
+ rfbProcessEvents(screen, -1);
+ }
+ if (input != got_user_input) {
+ break;
+ }
+ }
+}
+
+void nap_check(int tile_cnt) {
+ time_t now;
+
+ nap_diff_count += tile_cnt;
+
+ if (! take_naps) {
+ return;
+ }
+
+ now = time(0);
+
+ if (screen_blank > 0) {
+ int dt = (int) (now - last_event);
+ int ms = 1500;
+
+ /* if no activity, pause here for a second or so. */
+ if (dt > screen_blank) {
+ nap_sleep(ms, 8);
+ return;
+ }
+ }
+ if (naptile && nap_ok && tile_cnt < naptile) {
+ int ms = napfac * waitms;
+ ms = ms > napmax ? napmax : ms;
+ if (now - last_input <= 2) {
+ nap_ok = 0;
+ } else {
+ nap_sleep(ms, 1);
+ }
+ }
+}
+
+/*
+ * This is called to avoid a ~20 second timeout in libvncserver.
+ * May no longer be needed.
+ */
+void ping_clients(int tile_cnt) {
+ static time_t last_send = 0;
+ time_t now = time(0);
+
+ if (rfbMaxClientWait < 20000) {
+ rfbMaxClientWait = 20000;
+ rfbLog("reset rfbMaxClientWait to %d ms.\n",
+ rfbMaxClientWait);
+ }
+ if (tile_cnt) {
+ last_send = now;
+ } else if (now - last_send > 1) {
+ /* Send small heartbeat to client */
+ rfbMarkRectAsModified(screen, 0, 0, 1, 1);
+ last_send = now;
+ }
+}
+
+/*
+ * scan_display() wants to know if this tile can be skipped due to
+ * blackout regions: (no data compare is done, just a quick geometric test)
+ */
+int blackout_line_skip(int n, int x, int y, int rescan, int *tile_count) {
+
+ if (tile_blackout[n].cover == 2) {
+ tile_has_diff[n] = 0;
+ return 1; /* skip it */
+
+ } else if (tile_blackout[n].cover == 1) {
+ int w, x1, y1, x2, y2, b, hit = 0;
+ if (x + NSCAN > dpy_x) {
+ w = dpy_x - x;
+ } else {
+ w = NSCAN;
+ }
+
+ for (b=0; b < tile_blackout[n].count; b++) {
+
+ /* n.b. these coords are in full display space: */
+ x1 = tile_blackout[n].bo[b].x1;
+ x2 = tile_blackout[n].bo[b].x2;
+ y1 = tile_blackout[n].bo[b].y1;
+ y2 = tile_blackout[n].bo[b].y2;
+
+ if (x2 - x1 < w) {
+ /* need to cover full width */
+ continue;
+ }
+ if (y1 <= y && y < y2) {
+ hit = 1;
+ break;
+ }
+ }
+ if (hit) {
+ if (! rescan) {
+ tile_has_diff[n] = 0;
+ } else {
+ *tile_count += tile_has_diff[n];
+ }
+ return 1; /* skip */
+ }
+ }
+ return 0; /* do not skip */
+}
+
+/*
+ * scan_display() wants to know if this changed tile can be skipped due
+ * to blackout regions (we do an actual compare to find the changed region).
+ */
+int blackout_line_cmpskip(int n, int x, int y, char *dst, char *src,
+ int w, int pixelsize) {
+
+ int i, x1, y1, x2, y2, b, hit = 0;
+ int beg = -1, end = -1;
+
+ if (tile_blackout[n].cover == 0) {
+ return 0; /* 0 means do not skip it. */
+ } else if (tile_blackout[n].cover == 2) {
+ return 1; /* 1 means skip it. */
+ }
+
+ /* tile has partial coverage: */
+
+ for (i=0; i < w * pixelsize; i++) {
+ if (*(dst+i) != *(src+i)) {
+ beg = i/pixelsize; /* beginning difference */
+ break;
+ }
+ }
+ for (i = w * pixelsize - 1; i >= 0; i--) {
+ if (*(dst+i) != *(src+i)) {
+ end = i/pixelsize; /* ending difference */
+ break;
+ }
+ }
+ if (beg < 0 || end < 0) {
+ /* problem finding range... */
+ return 0;
+ }
+
+ /* loop over blackout rectangles: */
+ for (b=0; b < tile_blackout[n].count; b++) {
+
+ /* y in full display space: */
+ y1 = tile_blackout[n].bo[b].y1;
+ y2 = tile_blackout[n].bo[b].y2;
+
+ /* x relative to tile origin: */
+ x1 = tile_blackout[n].bo[b].x1 - x;
+ x2 = tile_blackout[n].bo[b].x2 - x;
+
+ if (y1 > y || y >= y2) {
+ continue;
+ }
+ if (x1 <= beg && end <= x2) {
+ hit = 1;
+ break;
+ }
+ }
+ if (hit) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*
+ * Loop over 1-pixel tall horizontal scanlines looking for changes.
+ * Record the changes in tile_has_diff[]. Scanlines in the loop are
+ * equally spaced along y by NSCAN pixels, but have a slightly random
+ * starting offset ystart ( < NSCAN ) from scanlines[].
+ */
+int scan_display(int ystart, int rescan) {
+ char *src, *dst;
+ int pixelsize = bpp >> 3;
+ int x, y, w, n;
+ int tile_count = 0;
+ int whole_line = 1, nodiffs = 0;
+
+ y = ystart;
+
+ while (y < dpy_y) {
+
+ /* grab the horizontal scanline from the display: */
+ X_LOCK;
+ if (using_shm) {
+ XShmGetImage(dpy, window, scanline, 0, y, AllPlanes);
+ } else {
+ XGetSubImage(dpy, window, 0, y, scanline->width,
+ scanline->height, AllPlanes, ZPixmap, scanline,
+ 0, 0);
+ }
+ X_UNLOCK;
+
+ /* for better memory i/o try the whole line at once */
+ src = scanline->data;
+ dst = screen->frameBuffer + y * bytes_per_line;
+
+ if (whole_line && ! memcmp(dst, src, bytes_per_line)) {
+ /* no changes anywhere in scan line */
+ nodiffs = 1;
+ if (! rescan) {
+ y += NSCAN;
+ continue;
+ }
+ }
+
+ x = 0;
+ while (x < dpy_x) {
+ n = (x/tile_x) + (y/tile_y) * ntiles_x;
+
+ if (blackouts) {
+ if (blackout_line_skip(n, x, y, rescan,
+ &tile_count)) {
+ x += NSCAN;
+ continue;
+ }
+ }
+
+ if (rescan) {
+ if (nodiffs || tile_has_diff[n]) {
+ tile_count += tile_has_diff[n];
+ x += NSCAN;
+ continue;
+ }
+ }
+
+ /* set ptrs to correspond to the x offset: */
+ src = scanline->data + x * pixelsize;
+ dst = screen->frameBuffer + y * bytes_per_line
+ + x * pixelsize;
+
+ /* compute the width of data to be compared: */
+ if (x + NSCAN > dpy_x) {
+ w = dpy_x - x;
+ } else {
+ w = NSCAN;
+ }
+
+ if (memcmp(dst, src, w * pixelsize)) {
+ /* found a difference, record it: */
+ if (! blackouts) {
+ tile_has_diff[n] = 1;
+ tile_count++;
+ } else {
+ if (blackout_line_cmpskip(n, x, y,
+ dst, src, w, pixelsize)) {
+ tile_has_diff[n] = 0;
+ } else {
+ tile_has_diff[n] = 1;
+ tile_count++;
+ }
+ }
+ }
+ x += NSCAN;
+ }
+ y += NSCAN;
+ }
+ return tile_count;
+}
+
+/*
+ * toplevel for the scanning, rescanning, and applying the heuristics.
+ */
+void scan_for_updates() {
+ int i, tile_count, tile_diffs;
+ double frac1 = 0.1; /* tweak parameter to try a 2nd scan_display() */
+ double frac2 = 0.35; /* or 3rd */
+ for (i=0; i < ntiles; i++) {
+ tile_has_diff[i] = 0;
+ tile_tried[i] = 0;
+ }
+
+ /*
+ * n.b. this program has only been tested so far with
+ * tile_x = tile_y = NSCAN = 32!
+ */
+
+ count++;
+ count %= NSCAN;
+
+ if (count % (NSCAN/4) == 0) {
+ /* some periodic maintenance */
+
+ if (subwin) {
+ set_offset(); /* follow the subwindow */
+ }
+ if (indexed_colour) { /* check for changed colormap */
+ set_colormap();
+ }
+ }
+
+ if (show_mouse && ! use_threads) {
+ /* single-thread is safe to do it here for all scanning */
+ restore_mouse_patch();
+ }
+
+ /* scan with the initial y to the jitter value from scanlines: */
+ scan_in_progress = 1;
+ tile_count = scan_display(scanlines[count], 0);
+
+ nap_set(tile_count);
+
+ if (fs_factor && frac1 >= fs_frac) {
+ /* make frac1 < fs_frac if fullscreen updates are enabled */
+ frac1 = fs_frac/2.0;
+ }
+
+ if (tile_count > frac1 * ntiles) {
+ /*
+ * many tiles have changed, so try a rescan (since it should
+ * be short compared to the many upcoming copy_tiles() calls)
+ */
+
+ /* this check is done to skip the extra scan_display() call */
+ if (! fs_factor || tile_count <= fs_frac * ntiles) {
+ int cp, tile_count_old = tile_count;
+
+ /* choose a different y shift for the 2nd scan: */
+ cp = (NSCAN - count) % NSCAN;
+
+ tile_count = scan_display(scanlines[cp], 1);
+
+ if (tile_count >= (1 + frac2) * tile_count_old) {
+ /* on a roll... do a 3rd scan */
+ cp = (NSCAN - count + 7) % NSCAN;
+ tile_count = scan_display(scanlines[cp], 1);
+ }
+ }
+ scan_in_progress = 0;
+
+ /*
+ * At some number of changed tiles it is better to just
+ * copy the full screen at once. I.e. time = c1 + m * r1
+ * where m is number of tiles, r1 is the copy_tiles()
+ * time, and c1 is the scan_display() time: for some m
+ * it crosses the full screen update time.
+ *
+ * We try to predict that crossover with the fs_frac
+ * fudge factor... seems to be about 1/2 the total number
+ * of tiles. n.b. this ignores network bandwidth,
+ * compression time etc...
+ *
+ * Use -fs 1.0 to disable on slow links.
+ */
+ if (fs_factor && tile_count > fs_frac * ntiles) {
+ fb_copy_in_progress = 1;
+ copy_screen();
+ if (show_mouse || cursor_pos) {
+ if (show_mouse && ! use_threads) {
+ redraw_mouse();
+ }
+ update_mouse();
+ }
+ fb_copy_in_progress = 0;
+ if (use_threads && ! old_pointer) {
+ pointer(-1, 0, 0, NULL);
+ }
+ nap_check(tile_count);
+ return;
+ }
+ }
+ scan_in_progress = 0;
+
+ /* copy all tiles with differences from display to rfb framebuffer: */
+ fb_copy_in_progress = 1;
+
+ if (single_copytile) {
+ /*
+ * Old way, copy I/O one tile at a time.
+ */
+ tile_diffs = copy_all_tiles();
+ } else {
+ /*
+ * New way, does runs of horizontal tiles at once.
+ * Note that below, for simplicity, the extra tile finding
+ * (e.g. copy_tiles_backward_pass) is done the old way.
+ */
+ tile_diffs = copy_all_tile_runs();
+ }
+
+ /*
+ * This backward pass for upward and left tiles complements what
+ * was done in copy_all_tiles() for downward and right tiles.
+ */
+ tile_diffs = copy_tiles_backward_pass();
+
+ /* Given enough tile diffs, try the islands: */
+ if (grow_fill && tile_diffs > 4) {
+ tile_diffs = grow_islands();
+ }
+
+ /* Given enough tile diffs, try the gaps: */
+ if (gaps_fill && tile_diffs > 4) {
+ tile_diffs = fill_tile_gaps();
+ }
+
+ fb_copy_in_progress = 0;
+ if (use_threads && ! old_pointer) {
+ /*
+ * tell the pointer handler it can process any queued
+ * pointer events:
+ */
+ pointer(-1, 0, 0, NULL);
+ }
+
+ if (blackouts) {
+ /* ignore any diffs in completely covered tiles */
+ int x, y, n;
+ for (y=0; y < ntiles_y; y++) {
+ for (x=0; x < ntiles_x; x++) {
+ n = x + y * ntiles_x;
+ if (tile_blackout[n].cover == 2) {
+ tile_has_diff[n] = 0;
+ }
+ }
+ }
+ }
+
+ if (use_hints) {
+ hint_updates(); /* use krfb/x0rfbserver hints algorithm */
+ } else {
+ tile_updates(); /* send each tile change individually */
+ }
+
+ /* Work around threaded rfbProcessClientMessage() calls timeouts */
+ if (use_threads) {
+ ping_clients(tile_diffs);
+ }
+
+ /* Handle the remote mouse pointer */
+ if (show_mouse || cursor_pos) {
+ if (show_mouse && ! use_threads) {
+ redraw_mouse();
+ }
+ update_mouse();
+ }
+
+ nap_check(tile_diffs);
+}
+
+int check_user_input(double, int *);
+
+void watch_loop(void) {
+ int cnt = 0;
+ double dt = 0.0;
+
+ if (use_threads) {
+ rfbRunEventLoop(screen, -1, TRUE);
+ }
+
+ while (1) {
+
+ got_user_input = 0;
+ got_pointer_input = 0;
+ got_keyboard_input = 0;
+
+ if (! use_threads) {
+ rfbProcessEvents(screen, -1);
+ if (check_user_input(dt, &cnt)) {
+ /* true means loop back for more input */
+ continue;
+ }
+ }
+
+ if (shut_down) {
+ clean_up_exit(0);
+ }
+
+ watch_xevents();
+ check_connect_inputs();
+
+ if (! screen->rfbClientHead) { /* waiting for a client */
+ usleep(200 * 1000);
+ continue;
+ }
+
+ if (nofb) { /* no framebuffer polling needed */
+ if (cursor_pos) {
+ update_mouse();
+ }
+ continue;
+ }
+
+ if (watch_bell) {
+ /*
+ * check for any bell events.
+ * n.b. assumes -nofb folks do not want bell...
+ */
+ watch_bell_event();
+ }
+ if (! show_dragging && button_mask) {
+ /* if any button is pressed do not update screen */
+ /* XXX consider: use_threads || got_pointer_input */
+ X_LOCK;
+ XFlush(dpy);
+ X_UNLOCK;
+ } else {
+ /* for timing the scan to try to detect thrashing */
+ double tm = 0.0;
+ dtime(&tm);
+
+ rfbUndrawCursor(screen);
+ scan_for_updates();
+
+ dt = dtime(&tm);
+ }
+
+ /* sleep a bit to lessen load */
+ usleep(waitms * 1000);
+ cnt++;
+ }
+}
+
+/*
+ * 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().
+ */
+
+int check_user_input(double dt, int *cnt) {
+
+ if (old_pointer) {
+ /* every n-th drops thru to scan */
+ if ((got_user_input || ui_skip < 0) && *cnt % ui_skip != 0) {
+ *cnt++;
+ XFlush(dpy);
+ return 1; /* short circuit watch_loop */
+ } else {
+ return 0;
+ }
+ }
+
+ if (got_keyboard_input) {
+ if (*cnt % ui_skip != 0) {
+ *cnt++;
+ return 1; /* short circuit watch_loop */
+ }
+ /* otherwise continue with pointer input */
+ }
+
+ if (got_pointer_input) {
+ int eaten = 0, miss = 0, max_eat = 50;
+ int g, g_in;
+ double spin = 0.0, tm = 0.0;
+ double quick_spin_fac = 0.40;
+ double grind_spin_time = 0.175;
+
+
+ dtime(&tm);
+ g = g_in = got_pointer_input;
+
+ /*
+ * 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) {
+ rfbCheckFds(screen, 1000);
+ XFlush(dpy);
+
+ 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) {
+ g = got_pointer_input;
+ if (eaten++ < max_eat) {
+ continue;
+ }
+ } else {
+ miss++;
+ }
+ if (miss > 1) { /* 1 means out on 2nd miss */
+ break;
+ }
+ }
+
+
+ /*
+ * 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;
+ tm = 0.0;
+ dtime(&tm);
+
+ g = got_pointer_input;
+ miss = 0;
+ for (i=0; i<split; i++) {
+ usleep(ms * 1000);
+ rfbCheckFds(screen, 1000);
+ spin += dtime(&tm);
+ if (got_pointer_input > g) {
+ XFlush(dpy);
+ miss = 0;
+ } else {
+ miss++;
+ }
+ g = got_pointer_input;
+ if (miss > 2) {
+ break;
+ }
+ if (1000 * spin > ms * split) {
+ break;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+void print_help() {
+ char help[] =
+"\n"
+"x11vnc: allow VNC connections to real X11 displays.\n"
+"\n"
+"Typical usage is:\n"
+"\n"
+" Run this command in a shell on the remote machine \"far-host\":\n"
+"\n"
+" x11vnc -display :0\n"
+"\n"
+" Then run this in another window on the machine you are sitting at:\n"
+"\n"
+" vncviewer far-host:0\n"
+"\n"
+"Once x11vnc establishes connections with the X11 server and starts\n"
+"listening as a VNC server it will print out a string: PORT=XXXX where\n"
+"XXXX is typically 5900 (the default VNC port). One would next run something\n"
+"like this on the local machine: \"vncviewer host:N\" where N is XXXX - 5900.\n"
+"\n"
+"By default x11vnc will not allow the screen to be shared and it will\n"
+"exit as soon as a client disconnects. See -shared and -forever below\n"
+"to override these protections.\n"
+"\n"
+"For additional info see: http://www.karlrunge.com/x11vnc/\n"
+" and http://www.karlrunge.com/x11vnc/#faq\n"
+"\n"
+"Options:\n"
+"\n"
+"-display disp X11 server display to connect to, the X server process\n"
+" must be running on same machine and support MIT-SHM.\n"
+" Equivalent to setting the DISPLAY env. variable.\n"
+"-id windowid Show the window corresponding to <windowid> not the\n"
+" entire display. Warning: bugs! new toplevels missed!...\n"
+"-flashcmap In 8bpp indexed color, let the installed colormap flash\n"
+" as the pointer moves from window to window (slow).\n"
+"-notruecolor Force 8bpp indexed color even if it looks like TrueColor.\n"
+"\n"
+"-visual n Experimental option: probably does not do what you\n"
+" think. It simply *forces* the visual used for the\n"
+" framebuffer; this may be a bad thing... It is useful for\n"
+" testing and for some workarounds. n may be a decimal\n"
+" number, or 0x hex. Run xdpyinfo(1) for the values.\n"
+" One may also use \"TrueColor\", etc. see <X11/X.h>\n"
+" for a list. If the string ends in \":m\" for better\n"
+" or for worse the visual depth is forced to be m.\n"
+"\n"
+"-viewonly All clients can only watch (default %s).\n"
+"-shared VNC display is shared (default %s).\n"
+"-forever Keep listening for more connections rather than exiting\n"
+" as soon as the first client(s) disconnect. Same as -many\n"
+"-connect string For use with \"vncviewer -listen\" reverse connections.\n"
+" If string has the form \"host\" or \"host:port\"\n"
+" the connection is made once at startup. Use commas\n"
+" for a list. If string contains \"/\" it is a file to\n"
+" periodically check for new hosts. The first line is\n"
+" read and then file is truncated.\n"
+"-vncconnect Monitor the VNC_CONNECT X property set by vncconnect(1).\n"
+"-auth file Set the X authority file to be \"file\", equivalent to\n"
+" setting the XAUTHORITY env. var to \"file\" before startup.\n"
+"-allow addr1[,addr2..] Only allow client connections from IP addresses matching\n"
+" the comma separated list of numerical addresses.\n"
+" Can be a prefix, e.g. \"192.168.100.\" to match a\n"
+" simple subnet, for more control build libvncserver with\n"
+" libwrap support.\n"
+"-localhost Same as -allow 127.0.0.1\n"
+"-passwdfile filename Specify libvncserver -passwd via the first line of file\n"
+" \"filename\" instead of via command line. Note: this\n"
+" is a simple plaintext passwd, see also -rfbauth below.\n"
+"-accept string Run a command (possibly to prompt the user at the\n"
+" X11 display) to decide whether an incoming client\n"
+" should be allowed to connect or not. \"string\" is\n"
+" an external command run via system(3) (see below for\n"
+" special cases). Be sure to quote \"string\" if it\n"
+" contains spaces, etc. The RFB_CLIENT_IP environment\n"
+" variable will be set to the incoming client IP number\n"
+" and the port in RFB_CLIENT_PORT (or -1 if unavailable).\n"
+" The x11vnc process id will be in RFB_X11VNC_PID and a\n"
+" client id number in RFB_CLIENT_ID. If the external\n"
+" command returns 0 the client is accepted, otherwise\n"
+" the client is rejected. See below for an extension to\n"
+" accept a client view-only.\n"
+"\n"
+" If \"string\" is \"popup\" then a builtin popup window\n"
+" is used. The popup will time out after 120 seconds,\n"
+" use \"popup:N\" to modify the timeout to N seconds\n"
+" (use 0 for no timeout)\n"
+"\n"
+" If \"string\" is \"xmessage\" then an xmessage(1)\n"
+" invocation is used for the command.\n"
+"\n"
+" Both \"popup\" and \"xmessage\" will present an option\n"
+" for accepting the client \"View-Only\" (the client\n"
+" can only watch). This option will not be presented if\n"
+" -viewonly has been specified, in which case the entire\n"
+" display is view only.\n"
+"\n"
+" If the user supplied command is prefixed with something\n"
+" like \"yes:0,no:*,view:3 mycommand ...\" then this\n"
+" associates the numerical command return code with\n"
+" the actions: accept, reject, and accept-view-only,\n"
+" respectively. Use \"*\" instead of a number to indicate\n"
+" the default action (in case the command returns an\n"
+" unexpected value). E.g. \"no:*\" is a good choice.\n"
+"\n"
+" Note that x11vnc blocks while the external command or\n"
+" or popup is running (other clients may see no updates\n"
+" during this period).\n"
+"\n"
+" More -accept tricks: use \"popupmouse\" to only allow\n"
+" mouse clicks in the builtin popup to be recognized.\n"
+" Similarly use \"popupkey\" to only recognize keystroke\n"
+" responses. All 3 of the popup keywords can be followed\n"
+" by +N+M to supply a position for the popup window.\n"
+" The default is to center the popup window.\n"
+"\n"
+"-gone string As -accept string, except to run a user supplied command\n"
+" when a client goes away (disconnects). Unlike -accept,\n"
+" the command return code is not interpreted by x11vnc.\n"
+"\n"
+"-inetd Launched by inetd(1): stdio instead of listening socket.\n"
+" Note: if you are not redirecting stderr to a log file\n"
+" you must also specify the -q option.\n"
+"\n"
+"-noshm Do not use the MIT-SHM extension for the polling.\n"
+" Remote displays can be polled this way: be careful this\n"
+" can use large amounts of network bandwidth. This is\n"
+" also of use if the local machine has a limited number\n"
+" of shm segments and -onetile is not sufficient.\n"
+"-flipbyteorder Sometimes needed if remotely polled host has different\n"
+" endianness. Ignored unless -noshm is set.\n"
+"-blackout string Black out rectangles on the screen. string is a comma\n"
+" separated list of WxH+X+Y type geometries for each rect.\n"
+"-xinerama If your screen is composed of multiple monitors\n"
+" glued together via XINERAMA, and that screen is\n"
+" non-rectangular this option will try to guess the areas\n"
+" to black out (if your system has libXinerama).\n"
+"\n"
+"-o logfile Write stderr messages to file \"logfile\" instead of\n"
+" to the terminal. Same as -logfile.\n"
+"-q Be quiet by printing less informational output to\n"
+" stderr. Same as -quiet.\n"
+"-bg Go into the background after screen setup. Messages to\n"
+" stderr are lost unless -o logfile is used. Something\n"
+" like this could be useful in a script:\n"
+" port=`ssh $host \"x11vnc -display :0 -bg\" | grep PORT`\n"
+" port=`echo \"$port\" | sed -e 's/PORT=//'`\n"
+" port=`expr $port - 5900`\n"
+" vncviewer $host:$port\n"
+"\n"
+"-modtweak Handle AltGr/Shift modifiers for differing languages\n"
+" between client and host (default %s).\n"
+"-nomodtweak Send the keysym directly to the X server.\n"
+"-remap string Read keysym remappings from file \"string\". Format is\n"
+" one pair of keysyms per line (can be name or hex value).\n"
+" \"string\" can also be of form: key1-key2,key3-key4...\n"
+" To map a key to a button click, use the fake keysyms\n"
+" \"Button1\", ..., etc. E.g. -remap Super_R-Button2\n"
+"-nobell Do not watch for XBell events.\n"
+"-nofb Ignore framebuffer: only process keyboard and pointer.\n"
+"-nosel Do not manage exchange of X selection/cutbuffer.\n"
+"-noprimary Do not poll the PRIMARY selection for changes and send\n"
+" back to clients. PRIMARY is still set on received\n"
+" changes, however.\n"
+"\n"
+"-nocursor Do not have the viewer show a local cursor.\n"
+"-mouse Draw a 2nd cursor at the current X pointer position.\n"
+"-mouseX As -mouse, but also draw an X on root background.\n"
+"-X Shorthand for -mouseX -nocursor.\n"
+"-xwarppointer Move the pointer with XWarpPointer instead of XTEST\n"
+" (try as a workaround if pointer behaves poorly, e.g.\n"
+" on touchscreens or other non-standard setups).\n"
+"-cursorpos Send the X cursor position back to all vnc clients that\n"
+" support the TightVNC CursorPosUpdates extension.\n"
+"-buttonmap string String to remap mouse buttons. Format: IJK-LMN, this\n"
+" maps buttons I -> L, etc., e.g. -buttonmap 13-31\n"
+"\n"
+" Button presses can also be mapped to keystrokes: replace\n"
+" a button digit on the right of the dash with :<sym>:\n"
+" or :<sym1>+<sym2>: etc. for multiple keys. For example,\n"
+" if the viewing machine has a mouse-wheel (buttons 4 5)\n"
+" but the x11vnc side does not, these will do scrolls:\n"
+" -buttonmap 12345-123:Prior::Next:\n"
+" -buttonmap 12345-123:Up+Up+Up::Down+Down+Down:\n"
+"\n"
+" If you include a modifier like \"Shift_L\" the\n"
+" modifier's up/down state is toggled, e.g. to send\n"
+" \"The\" use :Shift_L+t+Shift_L+h+e: (the 1st one is\n"
+" shift down and the 2nd one is shift up). (note: the\n"
+" initial state of the modifier is ignored and not reset)\n"
+" To include button events use \"Button1\", ... etc.\n"
+"\n"
+"-nodragging Do not update the display during mouse dragging events\n"
+" (mouse motion with a button held down). Greatly\n"
+" improves response on slow setups, but you lose all\n"
+" visual feedback for drags, text selection, and some\n"
+" menu traversals.\n"
+"-old_pointer Do not use the new pointer input handling mechanisms.\n"
+" See check_input() and pointer() for details.\n"
+"-input_skip n For the old pointer handling when non-threaded: try to\n"
+" read n user input events before scanning display. n < 0\n"
+" means to act as though there is always user input.\n"
+"\n"
+"-debug_pointer Print debugging output for every pointer event.\n"
+"-debug_keyboard Print debugging output for every keyboard event.\n"
+"\n"
+"-defer time Time in ms to wait for updates before sending to client\n"
+" [rfbDeferUpdateTime] (default %d).\n"
+"-wait time Time in ms to pause between screen polls. Used to cut\n"
+" down on load (default %d).\n"
+"-nap Monitor activity and if low take longer naps between\n"
+" polls to really cut down load when idle (default %s).\n"
+"-sigpipe string Broken pipe (SIGPIPE) handling. \"string\" can be\n"
+" \"ignore\" or \"exit\". For \"ignore\" libvncserver\n"
+" will handle the abrupt loss of a client and continue,\n"
+" for \"exit\" x11vnc will cleanup and exit at the 1st\n"
+" broken connection. Default is \"ignore\".\n"
+#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
+"-threads Whether or not to use the threaded libvncserver\n"
+"-nothreads algorithm [rfbRunEventLoop] (default %s).\n"
+#endif
+"\n"
+"-fs f If the fraction of changed tiles in a poll is greater\n"
+" than f, the whole screen is updated (default %.2f).\n"
+"-onetile Do not use the new copy_tiles() framebuffer mechanism,\n"
+" just use 1 shm tile for polling. Same as -old_copytile.\n"
+" Limits shm segments used to 3.\n"
+"-gaps n Heuristic to fill in gaps in rows or cols of n or\n"
+" less tiles. Used to improve text paging (default %d).\n"
+"-grow n Heuristic to grow islands of changed tiles n or wider\n"
+" by checking the tile near the boundary (default %d).\n"
+"-fuzz n Tolerance in pixels to mark a tiles edges as changed\n"
+" (default %d).\n"
+"-hints Use krfb/x0rfbserver hints (glue changed adjacent\n"
+" horizontal tiles into one big rectangle) (default %s).\n"
+"-nohints Do not use hints; send each tile separately.\n"
+"%s\n"
+"\n"
+"These options are passed to libvncserver:\n"
+"\n"
+;
+ fprintf(stderr, help,
+ view_only ? "on":"off",
+ shared ? "on":"off",
+ use_modifier_tweak ? "on":"off",
+ defer_update,
+ waitms,
+ take_naps ? "on":"off",
+#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
+ use_threads ? "on":"off",
+#endif
+ fs_frac,
+ gaps_fill,
+ grow_fill,
+ tile_fuzz,
+ use_hints ? "on":"off",
+ ""
+ );
+
+ rfbUsage();
+ exit(1);
+}
+
+/*
+ * choose a desktop name
+ */
+#define MAXN 256
+
+char *this_host() {
+ char host[MAXN];
+#ifdef LIBVNCSERVER_HAVE_GETHOSTNAME
+ if (gethostname(host, MAXN) == 0) {
+ return strdup(host);
+ }
+#endif
+ return NULL;
+}
+
+char *choose_title(char *display) {
+ static char title[(MAXN+10)];
+ strcpy(title, "x11vnc");
+
+ if (display == NULL) {
+ display = getenv("DISPLAY");
+ }
+ if (display == NULL) {
+ return title;
+ }
+ title[0] = '\0';
+ if (display[0] == ':') {
+ if (this_host() != NULL) {
+ strncpy(title, this_host(), MAXN - strlen(title));
+ }
+ }
+ strncat(title, display, MAXN - strlen(title));
+ if (subwin) {
+ char *name;
+ if (XFetchName(dpy, window, &name)) {
+ strncat(title, " ", MAXN - strlen(title));
+ strncat(title, name, MAXN - strlen(title));
+ }
+ }
+ return title;
+}
+
+/*
+ * check blacklist for OSs with tight shm limits.
+ */
+int limit_shm(void) {
+ struct utsname ut;
+ int limit = 0;
+
+ if (uname(&ut) == -1) {
+ return 0;
+ }
+ if (!strcmp(ut.sysname, "SunOS")) {
+ char *r = ut.release;
+ if (*r == '5' && *(r+1) == '.') {
+ if (strchr("2345678", *(r+2)) != NULL) {
+ limit = 1;
+ }
+ }
+ }
+ if (limit && ! quiet) {
+ fprintf(stderr, "reducing shm usage on %s %s (adding "
+ "-onetile)\n", ut.sysname, ut.release);
+ }
+ return limit;
+}
+
+int main(int argc, char** argv) {
+
+ XImage *fb;
+ int i, op, ev, er, maj, min;
+ char *use_dpy = NULL;
+ char *auth_file = NULL;
+ char *arg, *visual_str = NULL;
+ char *logfile = NULL;
+ char *passwdfile = NULL;
+ int pw_loc = -1;
+ int dt = 0;
+ int bg = 0;
+ int got_rfbwait = 0;
+ int got_deferupdate = 0, got_defer = 0;
+
+ /* used to pass args we do not know about to rfbGetScreen(): */
+ int argc2 = 1; char *argv2[128];
+
+ argv2[0] = strdup(argv[0]);
+
+ for (i=1; i < argc; i++) {
+ /* quick-n-dirty --option handling. */
+ arg = argv[i];
+ if (strstr(arg, "--") == arg) {
+ arg++;
+ }
+
+ if (!strcmp(arg, "-display")) {
+ use_dpy = argv[++i];
+ } else if (!strcmp(arg, "-id")) {
+ if (sscanf(argv[++i], "0x%x", &subwin) != 1) {
+ if (sscanf(argv[i], "%d", &subwin) != 1) {
+ fprintf(stderr, "bad -id arg: %s\n",
+ argv[i]);
+ exit(1);
+ }
+ }
+ } else if (!strcmp(arg, "-visual")) {
+ visual_str = argv[++i];
+ } else if (!strcmp(arg, "-flashcmap")) {
+ flash_cmap = 1;
+ } else if (!strcmp(arg, "-notruecolor")) {
+ force_indexed_color = 1;
+ } else if (!strcmp(arg, "-viewonly")) {
+ view_only = 1;
+ } else if (!strcmp(arg, "-passwdfile")) {
+ passwdfile = argv[++i];
+ } else if (!strcmp(arg, "-shared")) {
+ shared = 1;
+ } else if (!strcmp(arg, "-auth")) {
+ auth_file = argv[++i];
+ } else if (!strcmp(arg, "-allow")) {
+ allow_list = argv[++i];
+ } else if (!strcmp(arg, "-localhost")) {
+ allow_list = "127.0.0.1";
+ } else if (!strcmp(arg, "-accept")) {
+ accept_cmd = argv[++i];
+ } else if (!strcmp(arg, "-gone")) {
+ gone_cmd = argv[++i];
+ } else if (!strcmp(arg, "-many")
+ || !strcmp(arg, "-forever")) {
+ connect_once = 0;
+ } else if (!strcmp(arg, "-connect")) {
+ i++;
+ if (strchr(arg, '/')) {
+ client_connect_file = argv[i];
+ } else {
+ client_connect = strdup(argv[i]);
+ }
+ } else if (!strcmp(arg, "-vncconnect")) {
+ vnc_connect = 1;
+ } else if (!strcmp(arg, "-inetd")) {
+ inetd = 1;
+ } else if (!strcmp(arg, "-noshm")) {
+ using_shm = 0;
+ } else if (!strcmp(arg, "-flipbyteorder")) {
+ flip_byte_order = 1;
+ } else if (!strcmp(arg, "-modtweak")) {
+ use_modifier_tweak = 1;
+ } else if (!strcmp(arg, "-nomodtweak")) {
+ use_modifier_tweak = 0;
+ } else if (!strcmp(arg, "-remap")) {
+ remap_file = argv[++i];
+ } else if (!strcmp(arg, "-blackout")) {
+ blackout_string = argv[++i];
+ } else if (!strcmp(arg, "-xinerama")) {
+ xinerama = 1;
+ } else if (!strcmp(arg, "-nobell")) {
+ watch_bell = 0;
+ } else if (!strcmp(arg, "-nofb")) {
+ nofb = 1;
+ } else if (!strcmp(arg, "-nosel")) {
+ watch_selection = 0;
+ } else if (!strcmp(arg, "-noprimary")) {
+ watch_primary = 0;
+ } else if (!strcmp(arg, "-nocursor")) {
+ local_cursor = 0;
+ } else if (!strcmp(arg, "-mouse")) {
+ show_mouse = 1;
+ } else if (!strcmp(arg, "-mouseX")) {
+ show_mouse = 1;
+ show_root_cursor = 1;
+ } else if (!strcmp(arg, "-X")) {
+ show_mouse = 1;
+ show_root_cursor = 1;
+ local_cursor = 0;
+ } else if (!strcmp(arg, "-xwarppointer")) {
+ use_xwarppointer = 1;
+ } else if (!strcmp(arg, "-cursorpos")) {
+ cursor_pos = 1;
+ } else if (!strcmp(arg, "-buttonmap")) {
+ pointer_remap = argv[++i];
+ } else if (!strcmp(arg, "-nodragging")) {
+ show_dragging = 0;
+ } else if (!strcmp(arg, "-input_skip")) {
+ ui_skip = atoi(argv[++i]);
+ if (! ui_skip) ui_skip = 1;
+ } else if (!strcmp(arg, "-old_pointer")) {
+ old_pointer = 1;
+ } else if (!strcmp(arg, "-onetile")
+ || !strcmp(arg, "-old_copytile")) {
+ single_copytile = 1;
+ } else if (!strcmp(arg, "-debug_pointer")) {
+ debug_pointer++;
+ } else if (!strcmp(arg, "-debug_keyboard")) {
+ debug_keyboard++;
+ } else if (!strcmp(arg, "-defer")) {
+ defer_update = atoi(argv[++i]);
+ got_defer = 1;
+ } else if (!strcmp(arg, "-wait")) {
+ waitms = atoi(argv[++i]);
+ } else if (!strcmp(arg, "-nap")) {
+ take_naps = 1;
+#ifdef LIBVNCSERVER_HAVE_LIBPTHREAD
+ } else if (!strcmp(arg, "-threads")) {
+ use_threads = 1;
+ } else if (!strcmp(arg, "-nothreads")) {
+ use_threads = 0;
+#endif
+ } else if (!strcmp(arg, "-sigpipe")) {
+ if (!strcmp(argv[++i], "ignore")) {
+ sigpipe = 1;
+ } else if (!strcmp(argv[i], "exit")) {
+ sigpipe = 2;
+ } else if (!strcmp(argv[i], "skip")) {
+ sigpipe = 0;
+ } else {
+ fprintf(stderr, "bad -sigpipe arg: %s, must "
+ "be \"ignore\" or \"exit\"\n", argv[i]);
+ exit(1);
+ }
+ } else if (!strcmp(arg, "-fs")) {
+ fs_frac = atof(argv[++i]);
+ } else if (!strcmp(arg, "-gaps")) {
+ gaps_fill = atoi(argv[++i]);
+ } else if (!strcmp(arg, "-grow")) {
+ grow_fill = atoi(argv[++i]);
+ } else if (!strcmp(arg, "-fuzz")) {
+ tile_fuzz = atoi(argv[++i]);
+ } else if (!strcmp(arg, "-hints")) {
+ use_hints = 1;
+ } else if (!strcmp(arg, "-nohints")) {
+ use_hints = 0;
+ } else if (!strcmp(arg, "-h") || !strcmp(arg, "-help")
+ || !strcmp(arg, "-?")) {
+ print_help();
+ } else if (!strcmp(arg, "-o") || !strcmp(arg, "-logfile")) {
+ logfile = argv[++i];
+ } else if (!strcmp(arg, "-q") || !strcmp(arg, "-quiet")) {
+ quiet = 1;
+#ifdef LIBVNCSERVER_HAVE_SETSID
+ } else if (!strcmp(arg, "-bg") || !strcmp(arg, "-background")) {
+ bg = 1;
+#endif
+ } else {
+ if (!strcmp(arg, "-desktop")) {
+ dt = 1;
+ }
+ if (!strcmp(arg, "-passwd")) {
+ pw_loc = i;
+ }
+ if (!strcmp(arg, "-rfbwait")) {
+ got_rfbwait = 1;
+ }
+ if (!strcmp(arg, "-deferupdate")) {
+ got_deferupdate = 1;
+ }
+ if (!strcmp(arg, "-rfbport")) {
+ got_rfbport = 1;
+ }
+ if (!strcmp(arg, "-alwaysshared ")) {
+ got_alwaysshared = 1;
+ }
+ if (!strcmp(arg, "-nevershared")) {
+ got_nevershared = 1;
+ }
+ /* otherwise copy it for libvncserver use below. */
+ if (argc2 < 100) {
+ argv2[argc2++] = strdup(arg);
+ }
+ }
+ }
+ if (logfile) {
+ int n;
+ if ((n = open(logfile, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
+ fprintf(stderr, "error opening logfile: %s\n", logfile);
+ perror("open");
+ exit(1);
+ }
+ if (dup2(n, 2) < 0) {
+ fprintf(stderr, "dup2 failed\n");
+ perror("dup2");
+ exit(1);
+ }
+ if (n > 2) {
+ close(n);
+ }
+ }
+ if (! quiet && ! inetd) {
+ int i;
+ for (i=1; i < argc2; i++) {
+ fprintf(stderr, "passing arg to libvncserver: %s\n",
+ argv2[i]);
+ if (!strcmp(argv2[i], "-passwd")) {
+ i++;
+ }
+ }
+ }
+
+
+ /*
+ * If -passwd was used, clear it out of argv. This does not
+ * work on all UNIX, have to use execvp() in general...
+ */
+ if (pw_loc > 0) {
+ char *p = argv[pw_loc];
+ while (*p != '\0') {
+ *p++ = '\0';
+ }
+ if (pw_loc+1 < argc) {
+ p = argv[pw_loc+1];
+ while (*p != '\0') {
+ *p++ = '\0';
+ }
+ }
+ } else if (passwdfile) {
+ char line[512];
+ FILE *in;
+ in = fopen(passwdfile, "r");
+ if (in == NULL) {
+ fprintf(stderr, "cannot open passwdfile: %s\n",
+ passwdfile);
+ perror("fopen");
+ exit(1);
+ }
+ if (fgets(line, 512, in) != NULL) {
+ line[strlen(line)-1] = '\0';
+ argv2[argc2++] = "-passwd";
+ argv2[argc2++] = strdup(line);
+ } else {
+ fprintf(stderr, "cannot read passwdfile: %s\n",
+ passwdfile);
+ perror("fgets");
+ exit(1);
+ }
+ }
+
+ /* fixup settings that do not make sense */
+
+ if (use_threads && nofb && cursor_pos) {
+ if (! quiet) {
+ fprintf(stderr, "disabling -threads under -nofb "
+ "-cursorpos\n");
+ }
+ use_threads = 0;
+ }
+ if (tile_fuzz < 1) {
+ tile_fuzz = 1;
+ }
+ if (waitms < 0) {
+ waitms = 0;
+ }
+ if (inetd) {
+ shared = 0;
+ connect_once = 1;
+ bg = 0;
+ }
+
+ /* increase rfbwait if threaded */
+ if (use_threads && ! got_rfbwait) {
+ argv2[argc2++] = "-rfbwait";
+ argv2[argc2++] = "604800000"; /* one week... */
+ }
+
+ /* check for OS with small shm limits */
+ if (using_shm && ! single_copytile) {
+ if (limit_shm()) {
+ single_copytile = 1;
+ }
+ }
+
+ if (nofb && ! got_deferupdate && ! got_defer) {
+ /* reduce defer time under -nofb */
+ defer_update = defer_update_nofb;
+ }
+ if (! got_deferupdate) {
+ char tmp[40];
+ /* XXX not working yet in libvncserver */
+ sprintf(tmp, "%d", defer_update);
+ argv2[argc2++] = "-deferupdate";
+ argv2[argc2++] = strdup(tmp);
+ }
+ if (debug_pointer || debug_keyboard) {
+ if (bg || quiet) {
+ fprintf(stderr, "disabling -bg/-q under -debug_pointer"
+ "/-debug_keyboard\n");
+ bg = 0;
+ quiet = 0;
+ }
+ }
+
+ if (! quiet) {
+ fprintf(stderr, "\n");
+ fprintf(stderr, "display: %s\n", use_dpy ? use_dpy
+ : "null");
+ fprintf(stderr, "subwin: 0x%x\n", subwin);
+ fprintf(stderr, "visual: %s\n", visual_str ? visual_str
+ : "null");
+ fprintf(stderr, "flashcmap: %d\n", flash_cmap);
+ fprintf(stderr, "force_idx: %d\n", force_indexed_color);
+ fprintf(stderr, "viewonly: %d\n", view_only);
+ fprintf(stderr, "shared: %d\n", shared);
+ fprintf(stderr, "authfile: %s\n", auth_file ? auth_file
+ : "null");
+ fprintf(stderr, "passfile: %s\n", passwdfile ? passwdfile
+ : "null");
+ fprintf(stderr, "logfile: %s\n", logfile ? logfile
+ : "null");
+ fprintf(stderr, "allow: %s\n", allow_list ? allow_list
+ : "null");
+ fprintf(stderr, "accept: %s\n", accept_cmd ? accept_cmd
+ : "null");
+ fprintf(stderr, "gone: %s\n", gone_cmd ? gone_cmd
+ : "null");
+ fprintf(stderr, "conn_once: %d\n", connect_once);
+ fprintf(stderr, "connect: %s\n", client_connect
+ ? client_connect : "null");
+ fprintf(stderr, "connectfile %s\n", client_connect_file
+ ? client_connect_file : "null");
+ fprintf(stderr, "vnc_conn: %d\n", vnc_connect);
+ fprintf(stderr, "inetd: %d\n", inetd);
+ fprintf(stderr, "using_shm: %d\n", using_shm);
+ fprintf(stderr, "flipbytes: %d\n", flip_byte_order);
+ fprintf(stderr, "mod_tweak: %d\n", use_modifier_tweak);
+ fprintf(stderr, "remap: %s\n", remap_file ? remap_file
+ : "null");
+ fprintf(stderr, "blackout: %s\n", blackout_string
+ ? blackout_string : "null");
+ fprintf(stderr, "xinerama: %d\n", xinerama);
+ fprintf(stderr, "watchbell: %d\n", watch_bell);
+ fprintf(stderr, "nofb: %d\n", nofb);
+ fprintf(stderr, "watchsel: %d\n", watch_selection);
+ fprintf(stderr, "watchprim: %d\n", watch_primary);
+ fprintf(stderr, "loc_curs: %d\n", local_cursor);
+ fprintf(stderr, "mouse: %d\n", show_mouse);
+ fprintf(stderr, "root_curs: %d\n", show_root_cursor);
+ fprintf(stderr, "xwarpptr: %d\n", use_xwarppointer);
+ fprintf(stderr, "cursorpos: %d\n", cursor_pos);
+ fprintf(stderr, "buttonmap: %s\n", pointer_remap
+ ? pointer_remap : "null");
+ fprintf(stderr, "dragging: %d\n", show_dragging);
+ fprintf(stderr, "inputskip: %d\n", ui_skip);
+ fprintf(stderr, "old_ptr: %d\n", old_pointer);
+ fprintf(stderr, "onetile: %d\n", single_copytile);
+ fprintf(stderr, "debug_ptr: %d\n", debug_pointer);
+ fprintf(stderr, "debug_key: %d\n", debug_keyboard);
+ fprintf(stderr, "defer: %d\n", defer_update);
+ fprintf(stderr, "waitms: %d\n", waitms);
+ fprintf(stderr, "take_naps: %d\n", take_naps);
+ fprintf(stderr, "sigpipe: %d\n", sigpipe);
+ fprintf(stderr, "threads: %d\n", use_threads);
+ fprintf(stderr, "fs_frac: %.2f\n", fs_frac);
+ fprintf(stderr, "gaps_fill: %d\n", gaps_fill);
+ fprintf(stderr, "grow_fill: %d\n", grow_fill);
+ fprintf(stderr, "tile_fuzz: %d\n", tile_fuzz);
+ fprintf(stderr, "use_hints: %d\n", use_hints);
+ fprintf(stderr, "bg: %d\n", bg);
+ fprintf(stderr, "%s\n", lastmod);
+ } else {
+ rfbLogEnable(0);
+ }
+
+ /* open the X display: */
+ X_INIT;
+ if (auth_file) {
+ char *tmp;
+ int len = strlen("XAUTHORITY=") + strlen(auth_file) + 1;
+ tmp = (char *) malloc((size_t) len);
+ sprintf(tmp, "XAUTHORITY=%s", auth_file);
+ putenv(tmp);
+ }
+ if (use_dpy) {
+ dpy = XOpenDisplay(use_dpy);
+ } else if ( (use_dpy = getenv("DISPLAY")) ) {
+ dpy = XOpenDisplay(use_dpy);
+ } else {
+ dpy = XOpenDisplay("");
+ }
+
+ if (! dpy) {
+ fprintf(stderr, "XOpenDisplay failed (%s)\n",
+ use_dpy ? use_dpy:"null");
+ exit(1);
+ } else if (use_dpy) {
+ if (! quiet) fprintf(stderr, "Using X display %s\n", use_dpy);
+ } else {
+ if (! quiet) fprintf(stderr, "Using default X display.\n");
+ }
+
+ /* check for XTEST */
+ if (! XTestQueryExtension(dpy, &ev, &er, &maj, &min)) {
+ fprintf(stderr, "Display does not support XTest extension.\n");
+ exit(1);
+ }
+
+ /* check for MIT-SHM */
+ if (! nofb && ! XShmQueryExtension(dpy)) {
+ if (! using_shm) {
+ if (! quiet) {
+ fprintf(stderr, "warning: display does not "
+ "support XShm.\n");
+ }
+ } else {
+ fprintf(stderr, "Display does not support XShm "
+ "extension (must be local).\n");
+ exit(1);
+ }
+ }
+
+ if (visual_str != NULL) {
+ set_visual(visual_str);
+ }
+#ifdef LIBVNCSERVER_HAVE_XKEYBOARD
+ /* check for XKEYBOARD */
+ if (watch_bell) {
+ if (! XkbQueryExtension(dpy, &op, &ev, &er, &maj, &min)) {
+ if (! quiet) {
+ fprintf(stderr, "warning: disabling bell.\n");
+ }
+ watch_bell = 0;
+ } else {
+ initialize_watch_bell();
+ }
+ }
+#endif
+
+ /*
+ * Window managers will often grab the display during resize, etc.
+ * To avoid deadlock (our user resize input is not processed)
+ * we tell the server to process our requests during all grabs:
+ */
+ XTestGrabControl(dpy, True);
+
+ scr = DefaultScreen(dpy);
+ rootwin = RootWindow(dpy, scr);
+
+ /* set up parameters for subwin or non-subwin cases: */
+ if (! subwin) {
+ window = rootwin;
+ dpy_x = DisplayWidth(dpy, scr);
+ dpy_y = DisplayHeight(dpy, scr);
+ off_x = 0;
+ off_y = 0;
+ visual = DefaultVisual(dpy, scr);
+ } else {
+ /* experiment to share just one window */
+ XWindowAttributes attr;
+
+ window = (Window) subwin;
+ if ( ! XGetWindowAttributes(dpy, window, &attr) ) {
+ fprintf(stderr, "bad window: 0x%lx\n", window);
+ exit(1);
+ }
+ dpy_x = attr.width;
+ dpy_y = attr.height;
+ visual = attr.visual;
+
+ /* show_mouse has some segv crashes as well */
+ if (show_root_cursor) {
+ show_root_cursor = 0;
+ if (! quiet) {
+ fprintf(stderr, "disabling root cursor drawing"
+ " for subwindow\n");
+ }
+ }
+
+ set_offset();
+ }
+
+ /* initialize depth to reasonable value */
+ depth = DefaultDepth(dpy, scr);
+
+ /*
+ * User asked for non-default visual, this is not working well but it
+ * does some useful things... What should it do in general?
+ */
+ if (visual_id) {
+ XVisualInfo vinfo_tmpl, *vinfo;
+ int n;
+
+ vinfo_tmpl.visualid = visual_id;
+ vinfo = XGetVisualInfo(dpy, VisualIDMask, &vinfo_tmpl, &n);
+ if (vinfo == NULL || n == 0) {
+ fprintf(stderr, "could not match visual_id: 0x%x\n",
+ (int) visual_id);
+ exit(1);
+ }
+ visual = vinfo->visual;
+ depth = vinfo->depth;
+ if (visual_depth) {
+ depth = visual_depth; /* force it */
+ }
+ if (! quiet) {
+ fprintf(stderr, "vis id: 0x%x\n",
+ (int) vinfo->visualid);
+ fprintf(stderr, "vis scr: %d\n", vinfo->screen);
+ fprintf(stderr, "vis depth %d\n", vinfo->depth);
+ fprintf(stderr, "vis class %d\n", vinfo->class);
+ fprintf(stderr, "vis rmask 0x%lx\n", vinfo->red_mask);
+ fprintf(stderr, "vis gmask 0x%lx\n", vinfo->green_mask);
+ fprintf(stderr, "vis bmask 0x%lx\n", vinfo->blue_mask);
+ fprintf(stderr, "vis cmap_sz %d\n", vinfo->colormap_size);
+ fprintf(stderr, "vis b/rgb %d\n", vinfo->bits_per_rgb);
+ }
+
+ XFree(vinfo);
+ }
+
+
+ if (nofb || visual_id) {
+ fb = XCreateImage(dpy, visual, depth, ZPixmap, 0, NULL,
+ dpy_x, dpy_y, BitmapPad(dpy), 0);
+ /*
+ * For -nofb we do not allocate the framebuffer, so we
+ * can save a few MB of memory.
+ */
+ if (! nofb) {
+ fb->data = (char *) malloc(fb->bytes_per_line *
+ fb->height);
+ }
+ } else {
+ fb = XGetImage(dpy, window, 0, 0, dpy_x, dpy_y, AllPlanes,
+ ZPixmap);
+ if (! quiet) {
+ fprintf(stderr, "Read initial data from X display into"
+ " framebuffer.\n");
+ }
+ }
+ if (fb->bits_per_pixel == 24 && ! quiet) {
+ fprintf(stderr, "warning: 24 bpp may have poor"
+ " performance.\n");
+ }
+
+ if (! dt) {
+ static char str[] = "-desktop";
+ argv2[argc2++] = str;
+ argv2[argc2++] = choose_title(use_dpy);
+ }
+
+ /*
+ * n.b. we do not have to X_LOCK any X11 calls until watch_loop()
+ * is called since we are single-threaded until then.
+ */
+
+ initialize_screen(&argc2, argv2, fb);
+
+ initialize_tiles();
+
+ /* rectangular blackout regions */
+ if (blackout_string != NULL) {
+ initialize_blackout(blackout_string);
+ }
+ if (xinerama) {
+ initialize_xinerama();
+ }
+ if (blackouts) {
+ blackout_tiles();
+ }
+
+ initialize_shm(); /* also creates XImages when using_shm = 0 */
+
+ set_signals();
+
+ if (blackouts) { /* blackout fb as needed. */
+ copy_screen();
+ }
+
+ if (use_modifier_tweak) {
+ initialize_modtweak();
+ }
+ if (remap_file != NULL) {
+ initialize_remap(remap_file);
+ }
+ initialize_pointer_map();
+
+ if (! inetd) {
+ if (! screen->rfbPort || screen->rfbListenSock < 0) {
+ rfbLog("Error: could not obtain listening port.\n");
+ clean_up_exit(1);
+ }
+ }
+ if (! quiet) {
+ rfbLog("screen setup finished.\n");
+ }
+ if (screen->rfbPort) {
+ char *host = this_host();
+ int port = screen->rfbPort;
+ if (host != NULL) {
+ /* note that vncviewer special cases 5900-5999 */
+ if (inetd) {
+ ; /* should not occur */
+ } else if (quiet) {
+ if (port >= 5900) {
+ fprintf(stderr, "The VNC desktop is "
+ "%s:%d\n", host, port - 5900);
+ } else {
+ fprintf(stderr, "The VNC desktop is "
+ "%s:%d\n", host, port);
+ }
+ } else if (port >= 5900) {
+ rfbLog("The VNC desktop is %s:%d\n", host,
+ port - 5900);
+ if (port >= 6000) {
+ rfbLog("possible aliases: %s:%d, "
+ "%s::%d\n", host, port, host, port);
+ }
+ } else {
+ rfbLog("The VNC desktop is %s:%d\n", host,
+ port);
+ rfbLog("possible alias: %s::%d\n",
+ host, port);
+ }
+ }
+ fflush(stderr);
+ fprintf(stdout, "PORT=%d\n", screen->rfbPort);
+ fflush(stdout);
+ }
+
+#if defined(LIBVNCSERVER_HAVE_FORK) && defined(LIBVNCSERVER_HAVE_SETSID)
+ if (bg) {
+ /* fork into the background now */
+ int p, n;
+ if ((p = fork()) > 0) {
+ exit(0);
+ } else if (p == -1) {
+ fprintf(stderr, "could not fork\n");
+ perror("fork");
+ clean_up_exit(1);
+ }
+ if (setsid() == -1) {
+ fprintf(stderr, "setsid failed\n");
+ perror("setsid");
+ clean_up_exit(1);
+ }
+ /* adjust our stdio */
+ n = open("/dev/null", O_RDONLY);
+ dup2(n, 0);
+ dup2(n, 1);
+ if (! logfile) {
+ dup2(n, 2);
+ }
+ if (n > 2) {
+ close(n);
+ }
+ }
+#endif
+
+ watch_loop();
+
+ return(0);
+}