From c984f2b126dbadd99bbb4882fba15e32331cabcd Mon Sep 17 00:00:00 2001 From: dscho Date: Thu, 3 Apr 2003 10:49:34 +0000 Subject: [PATCH] new version from Karl! --- contrib/x11vnc.c | 1452 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 1187 insertions(+), 265 deletions(-) diff --git a/contrib/x11vnc.c b/contrib/x11vnc.c index 3967b9a..2395702 100644 --- a/contrib/x11vnc.c +++ b/contrib/x11vnc.c @@ -1,7 +1,8 @@ /* * x11vnc.c: a VNC server for X displays. * - * Copyright (c) 2002 Karl J. Runge All rights reserved. + * Copyright (c) 2002-2003 Karl J. Runge + * 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 @@ -39,15 +40,57 @@ * 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. Parameters in the Makefile should be adjusted to the build - * system and then "make x11vnc" should create it. For earlier releases - * (say libvncserver-0.4) this file may be inserted in place of the - * original x11vnc.c file. + * CVS tree and released in version 0.5. For earlier releases (say + * libvncserver-0.4) this file may be inserted in place of the original + * x11vnc.c file. + * + * 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 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. + * + * It currently cannot capture XBell beeps (impossible?) And, of course, + * general audio at the remote display is lost as well unless one separately + * sets up some audio side-channel. + * + * Windows using visuals other than the default X visual may have their + * colors messed up. When using 8bpp indexed color, the colormap may + * become out of date (as the colormap is added to) or incorrect. + * + * 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. + * + * 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 +#include #include #include #include @@ -76,12 +119,17 @@ typedef KeySym X_KeySym; #include +/* X and rfb framebuffer */ Display *dpy = 0; +Visual *visual; +Window window, rootwin; +int subwin = 0; int scr; -int dpy_x, dpy_y; int bpp; -int window; int button_mask = 0; +int dpy_x, dpy_y; +int off_x, off_y; +int indexed_colour = 0; XImage *tile; XImage *scanline; @@ -102,13 +150,14 @@ int tile_y = 32; int ntiles, ntiles_x, ntiles_y; /* arrays that indicate changed or checked tiles. */ -char *tile_has_diff, *tile_tried; +unsigned char *tile_has_diff, *tile_tried; typedef struct tile_change_region { - short first_line, last_line; /* start and end lines, along y, */ - /* of the changed area inside a tile. */ - short left_diff, right_diff; /* info about differences along edges. */ - short top_diff, bot_diff; + /* 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. */ @@ -123,90 +172,176 @@ typedef struct hint { /* array to hold the hints: */ hint_t *hint_list; -int shared = 0; /* share vnc display. */ -int view_only = 0; /* client can only watch. */ -int connect_once = 1; /* allow only one client connection. */ +/* various command line options */ + +int shared = 0; /* share vnc display. */ +int view_only = 0; /* clients can only watch. */ +int connect_once = 1; /* disconnect after first connection session. */ +int flash_cmap = 0; /* follow installed colormaps */ int use_modifier_tweak = 0; /* use the altgr_keyboard modifier tweak */ +int local_cursor = 1; /* whether the viewer draws a local cursor */ +int show_mouse = 0; /* display a cursor for the real mouse */ +int show_root_cursor = 0; /* show X when on root background */ + /* * waitms is the msec to wait between screen polls. Not too old h/w shows - * poll times of 15-35ms, so maybe this value cuts the rest load by 2 or so. + * 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 use_threads = 1; /* but only if compiled with HAVE_PTHREADS */ + +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 nap_ok = 0, nap_diff_count = 0; +time_t last_event, last_input; /* tile heuristics: */ +double fs_frac = 0.6; /* 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. */ -double fs_frac = 0.6; /* threshold tile fraction to do fullscreen updates. */ +/* scan pattern jitter from x0rfbserver */ #define NSCAN 32 -int scanlines[NSCAN] = { /* scan pattern jitter from x0rfbserver */ +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 got_user_input; -int count = 0; +int cursor_x, cursor_y; /* x and y from the viewer(s) */ +int got_user_input = 0; int shut_down = 0; +#if defined(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. This should not be too bad since keyboard and pointer - * updates are infrequent compared to the scanning. (note: these lines are - * noops unless HAVE_PTHREADS) XXX: what is going on? + * avoid XIO crashes. */ MUTEX(x11Mutex); -#define X_LOCK LOCK(x11Mutex); -#define X_UNLOCK UNLOCK(x11Mutex); - -void mark_hint(hint_t); - -void clean_up_exit (void) { +#define X_LOCK LOCK(x11Mutex) +#define X_UNLOCK UNLOCK(x11Mutex) +#define X_INIT INIT_MUTEX(x11Mutex) - X_LOCK +/* + * Exiting and error handling: + */ +void shm_clean(XShmSegmentInfo *, XImage *); +void shm_delete(XShmSegmentInfo *); - XTestDiscard(dpy); +int exit_flag = 0; +void clean_up_exit (int ret) { + exit_flag = 1; /* remove the shm areas: */ + shm_clean(&tile_shm, tile); + shm_clean(&scanline_shm, scanline); + shm_clean(&fullscreen_shm, fullscreen); - XShmDetach(dpy, &tile_shm); - XDestroyImage(tile); - shmdt(tile_shm.shmaddr); - shmctl(tile_shm.shmid, IPC_RMID, 0); + X_LOCK; + XTestDiscard(dpy); + X_UNLOCK; - XShmDetach(dpy, &scanline_shm); - XDestroyImage(scanline); - shmdt(scanline_shm.shmaddr); - shmctl(scanline_shm.shmid, IPC_RMID, 0); + exit(ret); +} - XShmDetach(dpy, &fullscreen_shm); - XDestroyImage(fullscreen); - shmdt(fullscreen_shm.shmaddr); - shmctl(fullscreen_shm.shmid, IPC_RMID, 0); +void interrupted (int sig) { + if (exit_flag) { + if (use_threads) { + usleep2(250 * 1000); + } + exit(4); + } + exit_flag++; + if (sig == 0) { + printf("caught X11 error:\n"); + } else { + printf("caught signal: %d\n", sig); + } + /* + * to avoid deadlock, etc, just delete the shm areas and + * leave the X stuff hanging. + */ + shm_delete(&tile_shm); + shm_delete(&scanline_shm); + shm_delete(&fullscreen_shm); + if (sig) { + exit(2); + } +} - /* more cleanup? */ +XErrorHandler Xerror_def; +XIOErrorHandler XIOerr_def; +int Xerror(Display *d, XErrorEvent *error) { + interrupted(0); + return (*Xerror_def)(d, error); +} +int XIOerr(Display *d) { + interrupted(0); + return (*XIOerr_def)(d); +} - X_UNLOCK - exit(0); +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); + + X_LOCK; + Xerror_def = XSetErrorHandler(Xerror); + XIOerr_def = XSetIOErrorHandler(XIOerr); + X_UNLOCK; } void client_gone(rfbClientPtr client) { if (connect_once) { - printf("only one connection allowed.\n"); - clean_up_exit(); + printf("viewer exited.\n"); + clean_up_exit(0); } } enum rfbNewClientAction new_client(rfbClientPtr client) { + static client_count = 0; + last_event = last_input = time(0); if (connect_once) { + if (screen->rfbDontDisconnect && screen->rfbNeverShared) { + if (! shared && client_count) { + printf("denying additional client: %s\n", + client->host); + return(RFB_CLIENT_REFUSE); + } + } client->clientGoneHook = client_gone; } if (view_only) { @@ -214,11 +349,13 @@ enum rfbNewClientAction new_client(rfbClientPtr client) { } else { client->clientData = (void *) 0; } + client_count++; return(RFB_CLIENT_ACCEPT); } -/* For tweaking modifiers wrt the Alt-Graph key */ - +/* + * For tweaking modifiers wrt the Alt-Graph key, etc. + */ #define LEFTSHIFT 1 #define RIGHTSHIFT 2 #define ALTGR 4 @@ -277,9 +414,10 @@ void initialize_keycodes() { void DebugXTestFakeKeyEvent(Display* dpy, KeyCode keysym, Bool down, time_t cur_time) { - fprintf(stderr,"XTestFakeKeyEvent(dpy,%s(0x%x),%s,CurrentTime)\n", - XKeysymToString(XKeycodeToKeysym(dpy,keysym,0)),keysym,down?"down":"up"); - XTestFakeKeyEvent(dpy,keysym,down,cur_time); + fprintf(stderr,"XTestFakeKeyEvent(dpy,%s(0x%x),%s,CurrentTime)\n", + XKeysymToString(XKeycodeToKeysym(dpy,keysym,0)),keysym, + down?"down":"up"); + XTestFakeKeyEvent(dpy,keysym,down,cur_time); } /* #define XTestFakeKeyEvent DebugXTestFakeKeyEvent */ @@ -292,7 +430,7 @@ void tweak_mod(signed char mod, Bool down) { return; } - X_LOCK + X_LOCK; if (is_shift && mod != 1) { if (mod_state & LEFTSHIFT) { XTestFakeKeyEvent(dpy, left_shift_code, !dn, CurrentTime); @@ -310,7 +448,7 @@ void tweak_mod(signed char mod, Bool down) { if ( altgr_code && ! (mod_state & ALTGR) && mod == 2 ) { XTestFakeKeyEvent(dpy, altgr_code, dn, CurrentTime); } - X_UNLOCK + X_UNLOCK; } static void modifier_tweak_keyboard(Bool down, KeySym keysym, rfbClientPtr client) { @@ -339,14 +477,14 @@ static void modifier_tweak_keyboard(Bool down, KeySym keysym, rfbClientPtr clien tweak_mod(modifiers[keysym], True); k = keycodes[keysym]; } else { - X_LOCK + X_LOCK; k = XKeysymToKeycode(dpy, (X_KeySym) keysym); - X_UNLOCK + X_UNLOCK; } if ( k != NoSymbol ) { - X_LOCK + X_LOCK; XTestFakeKeyEvent(dpy, k, (X_Bool) down, CurrentTime); - X_UNLOCK + X_UNLOCK; } if ( tweak ) { @@ -354,12 +492,18 @@ static void modifier_tweak_keyboard(Bool down, KeySym keysym, rfbClientPtr clien } } -/* key event handler */ +/* + * key event handler + */ static void keyboard(Bool down, KeySym keysym, rfbClientPtr client) { KeyCode k; - /* fprintf(stderr,"keyboard(%s,%s(0x%x),client)\n", - down?"down":"up",XKeysymToString(keysym),(int)keysym); */ + if (0) { + X_LOCK; + fprintf(stderr,"keyboard(%s,%s(0x%x),client)\n", + down?"down":"up",XKeysymToString(keysym),(int)keysym); + X_UNLOCK; + } if (view_only) { return; @@ -370,7 +514,7 @@ static void keyboard(Bool down, KeySym keysym, rfbClientPtr client) { return; } - X_LOCK + X_LOCK; k = XKeysymToKeycode(dpy, (X_KeySym) keysym); @@ -378,13 +522,16 @@ static void keyboard(Bool down, KeySym keysym, rfbClientPtr client) { XTestFakeKeyEvent(dpy, k, (X_Bool) down, CurrentTime); XFlush(dpy); + last_event = last_input = time(0); got_user_input++; } - X_UNLOCK + X_UNLOCK; } -/* mouse event handler */ +/* + * pointer event handler + */ static void pointer(int mask, int x, int y, rfbClientPtr client) { int i; @@ -392,10 +539,14 @@ static void pointer(int mask, int x, int y, rfbClientPtr client) { return; } - X_LOCK + X_LOCK; + + XTestFakeMotionEvent(dpy, scr, x + off_x, y + off_y, CurrentTime); - XTestFakeMotionEvent(dpy, 0, x, y, CurrentTime); + cursor_x = x; + cursor_y = y; + last_event = last_input = time(0); got_user_input++; for (i=0; i < 5; i++) { @@ -404,35 +555,30 @@ static void pointer(int mask, int x, int y, rfbClientPtr client) { (mask & (1<> 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 windows 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_depth_cursor(void) { + Window r, c; + int rx, ry, wx, wy; + unsigned int mask; + int depth = 0, tries = 0, maxtries = 1; + + X_LOCK; + c = window; + while (c) { + if (++tries > maxtries) { + depth = maxtries; + break; + } + if ( XTestCompareCurrentCursorWithWindow(dpy, c) ) { + break; + } + XQueryPointer(dpy, c, &r, &c, &rx, &ry, &wx, &wy, &mask); + depth++; + } + X_UNLOCK; + return depth; +} + +/* + * 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 (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 (reverse) { + black = white; + white = 0; + } + + /* + * 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 (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; + Bool 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 depth; + if ( (depth = tree_depth_cursor()) ) { + which = 0; + } else { + which = 1; + } + } + + draw_mouse(root_x - off_x, root_y - off_y, which, 1); +} + +void set_offset(void) { + Window w; + if (! subwin) { + return; + } + X_LOCK; + XTranslateCoordinates(dpy, window, rootwin, 0, 0, &off_x, &off_y, &w); + X_UNLOCK; +} + +#define NCOLOR 256 +void set_colormap(void) { + static int first = 1; + static XColor color[NCOLOR], prev[NCOLOR]; + Colormap cmap; + int i, 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++) { + color[i].pixel = 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); + + 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 this is a hack, XQueryTree probably better. */ + 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; + break; + } + } else { + break; + } + } + } + + XQueryColors(dpy, cmap, color, NCOLOR); + + X_UNLOCK; + + for(i=0; i < NCOLOR; 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) { + rfbSetClientColourMaps(screen, 0, NCOLOR); + } + + first = 0; +} + +/* + * initialize the rfb framebuffer/screen + */ void initialize_screen(int *argc, char **argv, XImage *fb) { screen = rfbGetScreen(argc, argv, fb->width, fb->height, @@ -463,35 +1020,18 @@ void initialize_screen(int *argc, char **argv, XImage *fb) { screen->rfbServerFormat.depth = fb->depth; screen->rfbServerFormat.trueColour = (uint8_t) TRUE; - if ( screen->rfbServerFormat.bitsPerPixel == 8 ) { - /* 8 bpp */ - if(CellsOfScreen(ScreenOfDisplay(dpy,scr))) { - /* indexed colour */ - XColor color[256]; - int i; - screen->colourMap.count = 256; - screen->rfbServerFormat.trueColour = FALSE; - screen->colourMap.is16 = TRUE; - for(i=0;i<256;i++) - color[i].pixel=i; - XQueryColors(dpy,DefaultColormap(dpy,scr),color,256); - screen->colourMap.data.shorts = (unsigned short*)malloc(3*sizeof(short)*screen->colourMap.count); - for(i=0;icolourMap.count;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; - } - } else { - /* true colour */ - screen->rfbServerFormat.redShift = 0; - screen->rfbServerFormat.greenShift = 2; - screen->rfbServerFormat.blueShift = 5; - screen->rfbServerFormat.redMax = 3; - screen->rfbServerFormat.greenMax = 7; - screen->rfbServerFormat.blueMax = 3; - } + if ( screen->rfbServerFormat.bitsPerPixel == 8 + && CellsOfScreen(ScreenOfDisplay(dpy,scr)) ) { + /* indexed colour */ + printf("using 8bpp indexed colour\n"); + indexed_colour = 1; + set_colormap(); } else { /* general case ... */ + printf("using %dbpp depth=%d true colour\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 @@ -530,8 +1070,13 @@ void initialize_screen(int *argc, char **argv, XImage *fb) { if (screen->rfbDeferUpdateTime == 5) { screen->rfbDeferUpdateTime = defer_update; } - if (shared && ! screen->rfbNeverShared) { - screen->rfbAlwaysShared = TRUE; + if (! screen->rfbNeverShared && ! screen->rfbAlwaysShared) { + if (shared) { + screen->rfbAlwaysShared = TRUE; + } else { + screen->rfbDontDisconnect = TRUE; + screen->rfbNeverShared = TRUE; + } } /* event callbacks: */ @@ -539,8 +1084,12 @@ void initialize_screen(int *argc, char **argv, XImage *fb) { screen->kbdAddEvent = keyboard; screen->ptrAddEvent = pointer; - cursor = rfbMakeXCursor(cursor_x, cursor_y, cur_data, cur_mask); - screen->cursor = cursor; + if (local_cursor) { + cursor = rfbMakeXCursor(CUR_SIZE, CUR_SIZE, CUR_DATA, CUR_MASK); + screen->cursor = cursor; + } else { + screen->cursor = NULL; + } rfbInitServer(screen); @@ -557,8 +1106,10 @@ void initialize_tiles() { ntiles_y = (dpy_y - 1)/tile_y + 1; ntiles = ntiles_x * ntiles_y; - tile_has_diff = (char *) malloc((size_t) (ntiles * sizeof(char))); - tile_tried = (char *) malloc((size_t) (ntiles * sizeof(char))); + tile_has_diff = (unsigned char *) + malloc((size_t) (ntiles * sizeof(unsigned char))); + tile_tried = (unsigned char *) + malloc((size_t) (ntiles * sizeof(unsigned char))); tile_region = (region_t *) malloc((size_t) (ntiles * sizeof(region_t))); /* there will never be more hints than tiles: */ @@ -589,35 +1140,75 @@ void set_fs_factor(int max) { } } -void initialize_shm() { +/* + * set up an XShm image + */ +void shm_create(XShmSegmentInfo *shm, XImage **ximg_ptr, int w, int h, + char *name) { - /* the tile (e.g. 32x32) shared memory area image: */ + XImage *xim; - tile = XShmCreateImage(dpy, DefaultVisual(dpy, 0), bpp, ZPixmap, - NULL, &tile_shm, tile_x, tile_y); + X_LOCK; - tile_shm.shmid = shmget(IPC_PRIVATE, - tile->bytes_per_line * tile->height, IPC_CREAT | 0777); + xim = XShmCreateImage(dpy, visual, bpp, ZPixmap, NULL, shm, w, h); - tile_shm.shmaddr = tile->data = (char *) shmat(tile_shm.shmid, 0, 0); - tile_shm.readOnly = False; + if (xim == NULL) { + fprintf(stderr, "XShmCreateImage(%s) failed.\n", name); + exit(1); + } - XShmAttach(dpy, &tile_shm); + *ximg_ptr = xim; + shm->shmid = shmget(IPC_PRIVATE, + xim->bytes_per_line * xim->height, IPC_CREAT | 0777); - /* the scanline (e.g. 1280x1) shared memory area image: */ + if (shm->shmid == -1) { + fprintf(stderr, "shmget(%s) failed.\n", name); + perror("shmget"); + exit(1); + } - scanline = XShmCreateImage(dpy, DefaultVisual(dpy, 0), bpp, ZPixmap, - NULL, &scanline_shm, dpy_x, 1); + shm->shmaddr = xim->data = (char *) shmat(shm->shmid, 0, 0); - scanline_shm.shmid = shmget(IPC_PRIVATE, - scanline->bytes_per_line * scanline->height, IPC_CREAT | 0777); + if (shm->shmaddr == (char *)-1) { + fprintf(stderr, "shmat(%s) failed.\n", name); + perror("shmat"); + exit(1); + } - scanline_shm.shmaddr = scanline->data - = (char *) shmat(scanline_shm.shmid, 0, 0); - scanline_shm.readOnly = False; + shm->readOnly = False; + + if (! XShmAttach(dpy, shm)) { + fprintf(stderr, "XShmAttach(%s) failed.\n", name); + exit(1); + } - XShmAttach(dpy, &scanline_shm); + X_UNLOCK; +} + +void shm_delete(XShmSegmentInfo *shm) { + shmdt(shm->shmaddr); + shmctl(shm->shmid, IPC_RMID, 0); +} + +void shm_clean(XShmSegmentInfo *shm, XImage *xim) { + X_LOCK; + XShmDetach(dpy, shm); + XDestroyImage(xim); + X_UNLOCK; + + shm_delete(shm); +} + +void initialize_shm() { + + /* the tile (e.g. 32x32) shared memory area image: */ + + shm_create(&tile_shm, &tile, tile_x, tile_y, "tile"); + + /* the scanline (e.g. 1280x1) shared memory area image: */ + + shm_create(&scanline_shm, &scanline, dpy_x, 1, "scanline"); /* * the fullscreen (e.g. 1280x1024/fs_factor) shared memory area image: @@ -630,19 +1221,12 @@ void initialize_shm() { return; } - fullscreen = XShmCreateImage(dpy, DefaultVisual(dpy, 0), bpp, ZPixmap, - NULL, &fullscreen_shm, dpy_x, dpy_y/fs_factor); - - fullscreen_shm.shmid = shmget(IPC_PRIVATE, - fullscreen->bytes_per_line * fullscreen->height, IPC_CREAT | 0777); - - fullscreen_shm.shmaddr = fullscreen->data - = (char *) shmat(fullscreen_shm.shmid, 0, 0); - fullscreen_shm.readOnly = False; + shm_create(&fullscreen_shm, &fullscreen, dpy_x, dpy_y/fs_factor, + "fullscreen"); - XShmAttach(dpy, &fullscreen_shm); } + /* * A hint is a rectangular region built from 1 or more adjacent tiles * glued together. Ultimately, this information in a single hint is sent @@ -694,9 +1278,7 @@ void extend_tile_hint(int x, int y, int th, hint_t *hint) { } void save_hint(hint_t hint, int loc) { - /* copy it to the global array: */ - - hint_list[loc].x = hint.x; + hint_list[loc].x = hint.x; /* copy to the global array */ hint_list[loc].y = hint.y; hint_list[loc].w = hint.w; hint_list[loc].h = hint.h; @@ -804,17 +1386,20 @@ void tile_updates() { * diff) or a suspected change (from our various heuristics). * * Examine the whole tile for the y-range of difference, copy that - * image difference to the vnc framebuffer, and do bookkeepping wrt + * image difference to the rfb framebuffer, and do bookkeepping wrt * the y-range and edge differences. * * This call is somewhat costly, maybe 1-2 ms. Primarily the XShmGetImage * and then the memcpy/memcmp. */ + void copy_tile(int tx, int ty) { int x, y, line, first_line, last_line; int size_x, size_y, n, dw, dx; int pixelsize = bpp >> 3; - short l_diff = 0, r_diff = 0; + unsigned short l_diff = 0, r_diff = 0; + + int restored_patch = 0; /* for show_mouse */ char *src, *dst, *s_src, *s_dst, *m_src, *m_dst; char *h_src, *h_dst; @@ -823,16 +1408,14 @@ void copy_tile(int tx, int ty) { y = ty * tile_y; size_x = dpy_x - x; - if ( size_x > tile_x ) { - size_x = tile_x; - } + if ( size_x > tile_x ) size_x = tile_x; + size_y = dpy_y - y; - if ( size_y > tile_y ) { - size_y = tile_y; - } + if ( size_y > tile_y ) size_y = tile_y; + n = tx + ty * ntiles_x; /* number of the tile */ - X_LOCK + X_LOCK; if ( size_x == tile_x && size_y == tile_y ) { /* general case: */ XShmGetImage(dpy, window, tile, x, y, AllPlanes); @@ -844,7 +1427,25 @@ void copy_tile(int tx, int ty) { XGetSubImage(dpy, window, x, y, size_x, size_y, AllPlanes, ZPixmap, tile, 0, 0); } - X_UNLOCK + X_UNLOCK; + + /* + * 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->data; dst = screen->frameBuffer + y * bytes_per_line + x * pixelsize; @@ -868,6 +1469,9 @@ void copy_tile(int tx, int ty) { if (first_line == -1) { /* tile has no difference, note it and get out: */ tile_has_diff[n] = 0; + if (restored_patch) { + redraw_mouse(); + } return; } else { /* @@ -911,13 +1515,17 @@ void copy_tile(int tx, int ty) { h_dst += bytes_per_line; } - /* now copy the difference to the vnc framebuffer: */ + /* now copy the difference to the rfb framebuffer: */ for (line = first_line; line <= last_line; line++) { memcpy(s_dst, s_src, size_x * pixelsize); s_src += tile->bytes_per_line; s_dst += bytes_per_line; } + if (restored_patch) { + redraw_mouse(); + } + /* record all the info in the region array for this tile: */ tile_region[n].first_line = first_line; tile_region[n].last_line = last_line; @@ -936,7 +1544,7 @@ void copy_tile(int tx, int ty) { /* * The copy_tile() call in the loop below copies the changed tile into - * the vnc framebuffer. Note that copy_tile() sets the tile_region + * 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). * @@ -947,8 +1555,9 @@ void copy_tile(int tx, int ty) { * See copy_tiles_backward_pass() for analogous checking upward and * left tiles. */ -void copy_all_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++) { @@ -964,38 +1573,40 @@ void copy_all_tiles() { */ 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] = 1; + 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] = 1; + tile_has_diff[m] = 2; } } } } + 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 - * They are of varying utility... and perhaps some should be dropped. */ /* * Try to predict whether the upward and/or leftward tile has been modified. * copy_all_tiles() has already done downward and rightward tiles. */ -void copy_tiles_backward_pass() { +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--) { @@ -1009,6 +1620,7 @@ void copy_tiles_backward_pass() { if (y >= 1 && ! tile_has_diff[m] && tile_region[n].top_diff) { if (! tile_tried[m]) { + tile_has_diff[m] = 2; copy_tile(x, y-1); } } @@ -1017,11 +1629,18 @@ void copy_tiles_backward_pass() { if (x >= 1 && ! tile_has_diff[m] && tile_region[n].left_diff) { if (! tile_tried[m]) { + tile_has_diff[m] = 2; copy_tile(x-1, y); } } } } + 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) { @@ -1067,10 +1686,11 @@ void gap_try(int x, int y, int *run, int *saw, int along_x) { * 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. + * BTW, grow_islands() is actually pretty successful at doing this too. */ -void fill_tile_gaps() { +int fill_tile_gaps() { int x, y, run, saw; + int n, diffs = 0; /* horizontal: */ for (y=0; y < ntiles_y; y++) { @@ -1089,6 +1709,13 @@ void fill_tile_gaps() { 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) { @@ -1104,7 +1731,7 @@ void island_try(int x, int y, int u, int v, int *run) { } if (tile_has_diff[n] && ! tile_has_diff[m]) { - /* found discontinuity */ + /* found a discontinuity */ if (tile_tried[m]) { return; @@ -1121,8 +1748,9 @@ void island_try(int x, int y, int u, int v, int *run) { * the boundary of the discontinuity (i.e. make the island larger). * Vertical scans are skipped since they do not seem to yield much... */ -void grow_islands() { - int x, y, run; +int grow_islands() { + int x, y, n, run; + int diffs = 0; /* * n.b. the way we scan here should keep an extension going, @@ -1143,35 +1771,134 @@ void grow_islands() { 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 vnc framebuffer. For a large enough + * 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 is another issue... - * use -fs 1.0 to disable. + * 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 *vnc_fb; + char *rfb_fb; int i, y, block_size, xi; block_size = (dpy_x * (dpy_y/fs_factor) * pixelsize); - vnc_fb = screen->frameBuffer; + rfb_fb = screen->frameBuffer; y = 0; + X_LOCK; + for (i=0; i < fs_factor; i++) { xi = XShmGetImage(dpy, window, fullscreen, 0, y, AllPlanes); - memcpy(vnc_fb, fullscreen->data, (size_t) block_size); + memcpy(rfb_fb, fullscreen->data, (size_t) block_size); y += dpy_y / fs_factor; - vnc_fb += block_size; + rfb_fb += block_size; } + X_UNLOCK; + rfbMarkRectAsModified(screen, 0, 0, dpy_x, dpy_y); } +/* + * 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 it up to improve the wakeup time */ + for (i=0; i 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); + } + } +} + +void ping_clients(int tile_cnt) { + static time_t last_send = 0; + time_t now = time(0); + + if (rfbMaxClientWait <= 3000) { + rfbMaxClientWait = 3000; + printf("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; + } +} + /* * Loop over 1-pixel tall horizontal scanlines looking for changes. * Record the changes in tile_has_diff[]. Scanlines in the loop are @@ -1179,28 +1906,45 @@ void copy_screen() { * 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 pixelsize = bpp >> 3; - char *src, *dst; + int whole_line = 1, nodiffs; y = ystart; while (y < dpy_y) { /* grab the horizontal scanline from the display: */ - X_LOCK + X_LOCK; XShmGetImage(dpy, window, scanline, 0, y, AllPlanes); - X_UNLOCK + X_UNLOCK; + + /* for better memory i/o try the whole line at once */ + src = scanline->data; + dst = screen->frameBuffer + y * bytes_per_line; + + nodiffs = 0; + 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 (rescan && tile_has_diff[n]) { - 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: */ @@ -1209,13 +1953,13 @@ int scan_display(int ystart, int rescan) { + x * pixelsize; /* compute the width of data to be compared: */ - if ( x + NSCAN > dpy_x ) { + if (x + NSCAN > dpy_x) { w = dpy_x - x; } else { w = NSCAN; } - if (memcmp(dst, src, w * pixelsize) ) { + if (memcmp(dst, src, w * pixelsize)) { /* found a difference, record it: */ tile_has_diff[n] = 1; tile_count++; @@ -1231,7 +1975,7 @@ int scan_display(int ystart, int rescan) { * toplevel for the scanning, rescanning, and applying the heuristics. */ void scan_for_updates() { - int i, tile_count; + int i, tile_count, tile_diffs; double frac1 = 0.1; /* tweak parameter to try a 2nd scan_display() */ for (i=0; i < ntiles; i++) { @@ -1247,9 +1991,25 @@ void scan_for_updates() { count++; count %= NSCAN; + if (count % (NSCAN/4) == 0) { + 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: */ 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; @@ -1274,35 +2034,45 @@ void scan_for_updates() { /* * 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 and c1 is the scan_display() - * time: for some m it crosses the full screen update time. + * where m is number of tiles, r1 is the copy_tile() + * 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... * - * 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, etc. - * use -fs 1.0 to disable on slow links. + * Use -fs 1.0 to disable on slow links. */ if (fs_factor && tile_count > fs_frac * ntiles) { copy_screen(); + if (show_mouse) { + if (! use_threads) { + redraw_mouse(); + } + update_mouse(); + } + nap_check(tile_count); return; } } - /* copy all tiles with differences from display to vnc framebuffer: */ - copy_all_tiles(); + /* copy all tiles with differences from display to rfb framebuffer: */ + tile_diffs = copy_all_tiles(); /* * This backward pass for upward and left tiles complements what * was done in copy_all_tiles() for downward and right tiles. */ - copy_tiles_backward_pass(); + tile_diffs = copy_tiles_backward_pass(); - if (grow_fill) { - grow_islands(); + if (grow_fill && tile_diffs > 4) { + tile_diffs = grow_islands(); } - if (gaps_fill) { - fill_tile_gaps(); + if (gaps_fill && tile_diffs > 4) { + tile_diffs = fill_tile_gaps(); } if (use_hints) { @@ -1310,15 +2080,26 @@ void scan_for_updates() { } 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) { + if (! use_threads) { + redraw_mouse(); + } + update_mouse(); + } + + nap_check(tile_diffs); } void watch_loop(void) { int cnt = 0; -#if !defined(HAVE_PTHREADS) - use_threads = 0; -#endif - if (use_threads) { rfbRunEventLoop(screen, -1, TRUE); } @@ -1337,7 +2118,7 @@ void watch_loop(void) { } if (shut_down) { - clean_up_exit(); + clean_up_exit(0); } if (! screen->rfbClientHead) { /* waiting for a client */ @@ -1346,6 +2127,7 @@ void watch_loop(void) { } rfbUndrawCursor(screen); + scan_for_updates(); usleep(waitms * 1000); @@ -1356,92 +2138,188 @@ void watch_loop(void) { void print_help() { char help[] = +"\n" "x11vnc 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" +"-id windowid show the window corresponding to 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" +"\n" +"-viewonly clients can only watch (default %s).\n" +"-shared VNC display is shared (default %s).\n" +"-many keep listening for more connections rather than exiting\n" +" as soon as the first clients disconnect.\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" +"\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" +"\n" "-defer time time in ms to wait for updates before sending to\n" -" client [rfbDeferUpdateTime] (default %d)\n" +" client [rfbDeferUpdateTime] (default %d).\n" "-wait time time in ms to pause between screen polls. used\n" -" to cut down on load (default %d)\n" +" to cut 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" +#ifdef 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" "-gaps n heuristic to fill in gaps in rows or cols of n or less\n" " 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" -"-fs f if the fraction of changed tiles in a poll is greater\n" -" than f, the whole screen is updated (default %.2f)\n" -"-fuzz n tolerance in pixels to mark a tiles edges as changed.\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" -"\n" -"-modtweak handle AltGr/Shift modifiers for differing languages\n" -" between client and host (default %d).\n" -"-nomodtweak send the keysym directly to the X server.\n" -"-threads use threaded algorithm [rfbRunEventLoop] if compiled\n" -" with threads (default %s).\n" -"-nothreads do not use [rfbRunEventLoop].\n" -"-viewonly clients can only watch (default %s).\n" -"-shared VNC display is shared (default %s)\n" +"%s\n" "\n" "These options are passed to libvncserver:\n" "\n" ; - fprintf(stderr, help, defer_update, waitms, gaps_fill, grow_fill, - fs_frac, tile_fuzz, - use_hints ? "on":"off", use_modifier_tweak ? "on":"off", - use_threads ? "on":"off", view_only ? "on":"off", - shared ? "on":"off"); + fprintf(stderr, help, + view_only ? "on":"off", + shared ? "on":"off", + use_modifier_tweak ? "on":"off", + defer_update, + waitms, + take_naps ? "on":"off", +#ifdef 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 *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] == ':') { + char host[MAXN]; + if (gethostname(host, MAXN) == 0) { + strncpy(title, 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; +} + int main(int argc, char** argv) { XImage *fb; int i, ev, er, maj, min; char *use_dpy = NULL; - + int dt = 0; /* used to pass args we do not know about to rfbGetScreen(): */ int argc2 = 1; char *argv2[100]; + argv2[0] = argv[0]; for (i=1; i < argc; i++) { if (!strcmp(argv[i], "-display")) { use_dpy = argv[++i]; + } else if (!strcmp(argv[i], "-id")) { + /* expt to just show one window. XXX not finished. */ + if (sscanf(argv[++i], "0x%x", &subwin) != 1) { + if (sscanf(argv[i], "%d", &subwin) != 1) { + printf("bad -id arg: %s\n", argv[i]); + exit(1); + } + } + } else if (!strcmp(argv[i], "-flashcmap")) { + flash_cmap = 1; + } else if (!strcmp(argv[i], "-viewonly")) { + view_only = 1; + } else if (!strcmp(argv[i], "-shared")) { + shared = 1; + } else if (!strcmp(argv[i], "-many")) { + connect_once = 0; + } else if (!strcmp(argv[i], "-modtweak")) { + use_modifier_tweak = 1; + } else if (!strcmp(argv[i], "-nomodtweak")) { + use_modifier_tweak = 0; + } else if (!strcmp(argv[i], "-nocursor")) { + local_cursor = 0; + } else if (!strcmp(argv[i], "-mouse")) { + show_mouse = 1; + } else if (!strcmp(argv[i], "-mouseX")) { + show_mouse = 1; + show_root_cursor = 1; + } else if (!strcmp(argv[i], "-X")) { + show_mouse = 1; + show_root_cursor = 1; + local_cursor = 0; } else if (!strcmp(argv[i], "-defer")) { defer_update = atoi(argv[++i]); } else if (!strcmp(argv[i], "-wait")) { waitms = atoi(argv[++i]); + } else if (!strcmp(argv[i], "-nap")) { + take_naps = 1; +#ifdef HAVE_LIBPTHREAD + } else if (!strcmp(argv[i], "-threads")) { + use_threads = 1; + } else if (!strcmp(argv[i], "-nothreads")) { + use_threads = 0; +#endif + } else if (!strcmp(argv[i], "-fs")) { + fs_frac = atof(argv[++i]); } else if (!strcmp(argv[i], "-gaps")) { gaps_fill = atoi(argv[++i]); } else if (!strcmp(argv[i], "-grow")) { grow_fill = atoi(argv[++i]); - } else if (!strcmp(argv[i], "-fs")) { - fs_frac = atof(argv[++i]); } else if (!strcmp(argv[i], "-fuzz")) { tile_fuzz = atoi(argv[++i]); } else if (!strcmp(argv[i], "-hints")) { use_hints = 1; } else if (!strcmp(argv[i], "-nohints")) { use_hints = 0; - } else if (!strcmp(argv[i], "-threads")) { - use_threads = 1; - } else if (!strcmp(argv[i], "-nothreads")) { - use_threads = 0; - } else if (!strcmp(argv[i], "-modtweak")) { - use_modifier_tweak = 1; - } else if (!strcmp(argv[i], "-nomodtweak")) { - use_modifier_tweak = 0; - } else if (!strcmp(argv[i], "-viewonly")) { - view_only = 1; - } else if (!strcmp(argv[i], "-shared")) { - shared = 1; } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help")) { print_help(); } else { + if (!strcmp(argv[i], "-desktop")) { + dt = 1; + } /* otherwise copy it for use below. */ printf("passing arg to libvncserver: %s\n", argv[i]); if (argc2 < 100) { @@ -1449,22 +2327,31 @@ int main(int argc, char** argv) { } } } + if (tile_fuzz < 1) { tile_fuzz = 1; } if (waitms < 0) { waitms = 0; } + printf("viewonly: %d\n", view_only); + printf("shared: %d\n", shared); + printf("conn_once: %d\n", connect_once); + printf("mod_tweak: %d\n", use_modifier_tweak); + printf("loc_curs: %d\n", local_cursor); + printf("mouse: %d\n", show_mouse); + printf("root_curs: %d\n", show_root_cursor); printf("defer: %d\n", defer_update); printf("waitms: %d\n", waitms); - printf("tile_fuzz: %d\n", tile_fuzz); + printf("take_naps: %d\n", take_naps); + printf("threads: %d\n", use_threads); + printf("fs_frac: %.2f\n", fs_frac); printf("gaps_fill: %d\n", gaps_fill); printf("grow_fill: %d\n", grow_fill); - printf("fs_frac: %.2f\n", fs_frac); + printf("tile_fuzz: %d\n", tile_fuzz); printf("use_hints: %d\n", use_hints); - printf("viewonly: %d\n", view_only); - printf("shared: %d\n", shared); + X_INIT; if (use_dpy) { dpy = XOpenDisplay(use_dpy); } else if ( (use_dpy = getenv("DISPLAY")) ) { @@ -1472,6 +2359,7 @@ int main(int argc, char** argv) { } else { dpy = XOpenDisplay(""); } + if (! dpy) { printf("XOpenDisplay failed (%s)\n", use_dpy); exit(1); @@ -1499,10 +2387,36 @@ int main(int argc, char** argv) { XTestGrabControl(dpy, True); scr = DefaultScreen(dpy); - window = RootWindow(dpy, scr); + rootwin = RootWindow(dpy, scr); + + 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) ) { + printf("bad window: 0x%x\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; + printf("disabling root cursor drawing for subwindow\n"); + } - dpy_x = DisplayWidth(dpy, scr); - dpy_y = DisplayHeight(dpy, scr); + set_offset(); + } fb = XGetImage(dpy, window, 0, 0, dpy_x, dpy_y, AllPlanes, ZPixmap); printf("Read initial data from display into framebuffer.\n"); @@ -1511,8 +2425,14 @@ int main(int argc, char** argv) { printf("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 X11 calls until watch_loop() + * n.b. we do not have to X_LOCK any X11 calls until watch_loop() * is called since we are single-threaded until then. */ @@ -1522,6 +2442,8 @@ int main(int argc, char** argv) { initialize_shm(); + set_signals(); + if (use_modifier_tweak) { initialize_keycodes(); }