diff options
Diffstat (limited to 'x11vnc/x11vnc.c')
-rw-r--r-- | x11vnc/x11vnc.c | 6742 |
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 <[email protected]> + * 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 *)¬ify_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); +} |