You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libtdevnc/x11vnc/x11vnc.c

7325 lines
182 KiB

/*
* x11vnc.c: a VNC server for X displays.
*
* Copyright (c) 2002-2004 Karl J. Runge <runge@karlrunge.com>
* All rights reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*
*
* This program is based heavily on the following programs:
*
* the originial x11vnc.c in libvncserver (Johannes E. Schindelin)
* krfb, the KDE desktopsharing project (Tim Jansen)
* x0rfbserver, the original native X vnc server (Jens Wagner)
*
* The primary goal of this program is to create a portable and simple
* command-line server utility that allows a VNC viewer to connect to an
* actual X display (as the above do). The only non-standard dependency
* of this program is the static library libvncserver.a (although in
* some environments libjpeg.so may not be readily available and needs
* to be installed, it may be found at ftp://ftp.uu.net/graphics/jpeg/).
* To increase portability it is written in plain C.
*
* The next goal is to improve performance and interactive response.
* The algorithm of x0rfbserver was used as a base. 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 incorrectly display windows using 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. This may only be when threaded,
* which is no longer the default.
*
* There seems to be a serious bug with simultaneous clients when
* threaded, currently the only workaround in this case is -nothreads.
*
*/
/*
* These ' -- filename -- ' comments represent a partial cleanup:
* they are an odd way to indicate how this huge file would be split up
* someday into multiple files. Not finished, externs and other things
* would need to be done, but it indicates the breakup, including static
* keyword for local items.
*
* The primary reason we do not break up this file is for user
* convenience: those wanting to use the latest version download a single
* file, x11vnc.c, and off they go...
*/
/* -- x11vnc.h -- */
#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
#ifdef LIBVNCSERVER_HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef LIBVNCSERVER_HAVE_NETINET_IN_H
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#endif
/*
* Temporary kludge: to run with -xinerama define the following
* macro (uncomment) 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, but it won't be in users' build trees for a while,
* so one can do it manually here.
#define LIBVNCSERVER_HAVE_LIBXINERAMA
*/
#ifdef LIBVNCSERVER_HAVE_LIBXINERAMA
#include <X11/extensions/Xinerama.h>
#endif
/* date +'"lastmod: %Y-%m-%d";' */
char lastmod[] = "lastmod: 2004-06-17";
/* X display info */
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 indexed_colour = 0;
int num_buttons = -1;
/* image structures */
XImage *scanline;
XImage *fullscreen;
XImage **tile_row; /* for all possible row runs */
/* corresponding shm structures */
XShmSegmentInfo scanline_shm;
XShmSegmentInfo fullscreen_shm;
XShmSegmentInfo *tile_row_shm; /* for all possible row runs */
/* rfb info */
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[16]; /* hardwired max rectangles. */
int cover;
int count;
} tile_blackout_t;
blackout_t blackr[100]; /* hardwired max blackouts */
int blackouts = 0;
tile_blackout_t *tile_blackout;
/* saved cursor */
int cur_save_x, cur_save_y, cur_save_w, cur_save_h, cur_saved = 0;
/* times of recent events */
time_t last_event, last_input, last_client = 0;
/* last client to move pointer */
rfbClientPtr last_pointer_client = NULL;
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 fb_copy_in_progress = 0;
int shut_down = 0;
/* string for the VNC_CONNECT property */
#define VNC_CONNECT_MAX 512
char vnc_connect_str[VNC_CONNECT_MAX+1];
Atom vnc_connect_prop = None;
/* function prototypes (see filename comment above) */
int all_clients_initialized(void);
void blackout_tiles(void);
void check_connect_inputs(void);
void clean_up_exit(int);
void clear_modifiers(int init);
void clear_keys(void);
void copy_screen(void);
double dtime(double *);
void initialize_blackout(char *);
void initialize_modtweak(void);
void initialize_pointer_map(char *);
void initialize_remap(char *);
void initialize_screen(int *argc, char **argv, XImage *fb);
void initialize_shm(void);
void initialize_signals(void);
void initialize_tiles(void);
void initialize_watch_bell(void);
void initialize_xinerama(void);
void keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client);
void myXTestFakeKeyEvent(Display*, KeyCode, Bool, time_t);
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;
void mark_hint(hint_t);
enum rfbNewClientAction new_client(rfbClientPtr client);
void nofb_hook(rfbClientPtr client);
void pointer(int mask, int x, int y, rfbClientPtr client);
void read_vnc_connect_prop(void);
void redraw_mouse(void);
void restore_mouse_patch(void);
void rfbPE(rfbScreenInfoPtr, long);
void scan_for_updates(void);
void set_colormap(void);
void set_offset(void);
void set_visual(char *vstring);
void shm_clean(XShmSegmentInfo *, XImage *);
void shm_delete(XShmSegmentInfo *);
void update_mouse(void);
void watch_bell_event(void);
void watch_xevents(void);
void xcut_receive(char *text, int len, rfbClientPtr client);
void zero_fb(int, int, int, int);
/* -- options.h -- */
/*
* variables for the 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. */
char *viewonly_passwd = NULL; /* view only passwd. */
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 */
int clear_mods = 0; /* -clear_mods (1) and -clear_keys (2) */
int nofb = 0; /* do not send any fb updates */
int subwin = 0; /* -id */
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 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 */
int watch_selection = 1; /* normal selection/cutbuffer maintenance */
int watch_primary = 1; /* more dicey, poll for changes in PRIMARY */
int sigpipe = 1; /* 0=skip, 1=ignore, 2=exit */
/* for -visual override */
VisualID visual_id = (VisualID) 0;
int visual_depth = 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. */
int debug_pointer = 0;
int debug_keyboard = 0;
int quiet = 0;
int got_rfbport = 0;
int got_alwaysshared = 0;
int got_nevershared = 0;
/* threaded vs. non-threaded (default) */
#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
/* -- util.h -- */
/* 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
/*
* following is based on IsModifierKey in Xutil.h
*/
#define ismodkey(keysym) \
((((KeySym)(keysym) >= XK_Shift_L) && ((KeySym)(keysym) <= XK_Hyper_R) && \
((KeySym)(keysym) != XK_Caps_Lock) && ((KeySym)(keysym) != XK_Shift_Lock)))
/*
* 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)
/* -- cleanup.c -- */
/*
* Exiting and error handling routines
*/
static int exit_flag = 0;
int exit_sig = 0;
/*
* Normal exiting
*/
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;
}
}
if (clear_mods == 1) {
clear_modifiers(0);
} else if (clear_mods == 2) {
clear_keys();
}
X_LOCK;
XTestDiscard(dpy);
XCloseDisplay(dpy);
X_UNLOCK;
fflush(stderr);
exit(ret);
}
/*
* General problem handler
*/
static void interrupted (int sig) {
int i;
exit_sig = sig;
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);
}
if (sig == SIGINT) {
shut_down = 1;
return;
}
/*
* 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 (clear_mods == 1) {
clear_modifiers(0);
} else if (clear_mods == 2) {
clear_keys();
}
if (sig) {
exit(2);
}
}
/* X11 error handlers */
static XErrorHandler Xerror_def;
static XIOErrorHandler XIOerr_def;
static int Xerror(Display *d, XErrorEvent *error) {
X_UNLOCK;
interrupted(0);
return (*Xerror_def)(d, error);
}
static int XIOerr(Display *d) {
X_UNLOCK;
interrupted(0);
return (*XIOerr_def)(d);
}
/* signal handlers */
void initialize_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("initialize_signals: will exit on SIGPIPE\n");
signal(SIGPIPE, interrupted);
}
X_LOCK;
Xerror_def = XSetErrorHandler(Xerror);
XIOerr_def = XSetIOErrorHandler(XIOerr);
X_UNLOCK;
}
/* -- connections.c -- */
/*
* routines for handling incoming, outgoing, etc connections
*/
static int accepted_client = 0;
static int client_count = 0;
/*
* check that all clients are in RFB_NORMAL state
*/
int all_clients_initialized(void) {
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;
}
/*
* utility to run a user supplied command setting some RFB_ env vars.
* used by, e.g., accept_client() and client_gone()
*/
static 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_server_ip[100];
static char env_rfb_server_port[100];
static char env_rfb_x11vnc_pid[100];
static char env_rfb_client_count[100];
char *addr = client->host;
int rc;
char *saddr_ip_str = NULL;
int saddr_len, saddr_port;
struct sockaddr_in saddr;
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=%p", (void *) 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 */
saddr_len = sizeof(saddr);
memset(&saddr, 0, sizeof(saddr));
saddr_port = -1;
if (!getpeername(client->sock, (struct sockaddr *)&saddr, &saddr_len)) {
saddr_port = ntohs(saddr.sin_port);
}
sprintf(env_rfb_client_port, "RFB_CLIENT_PORT=%d", saddr_port);
putenv(env_rfb_client_port);
/*
* now do RFB_SERVER_IP and RFB_SERVER_PORT (i.e. us!)
* This will establish a 5-tuple (including tcp) the external
* program can potentially use to work out the virtual circuit
* for this connection.
*/
saddr_len = sizeof(saddr);
memset(&saddr, 0, sizeof(saddr));
saddr_port = -1;
saddr_ip_str = "unknown";
if (!getsockname(client->sock, (struct sockaddr *)&saddr, &saddr_len)) {
saddr_port = ntohs(saddr.sin_port);
#ifdef LIBVNCSERVER_HAVE_NETINET_IN_H
saddr_ip_str = inet_ntoa(saddr.sin_addr);
#endif
}
sprintf(env_rfb_server_ip, "RFB_SERVER_IP=%s", saddr_ip_str);
putenv(env_rfb_server_ip);
sprintf(env_rfb_server_port, "RFB_SERVER_PORT=%d", saddr_port);
putenv(env_rfb_server_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);
/*
* work out the number of clients (have to use client_count
* since there is deadlock in rfbGetClientIterator)
*/
sprintf(env_rfb_client_count, "RFB_CLIENT_COUNT=%d", client_count);
putenv(env_rfb_client_count);
rfbLog("running command:\n");
rfbLog(" %s\n", cmd);
rc = system(cmd);
if (rc >= 256) {
rc = rc/256;
}
rfbLog("command returned: %d\n", rc);
return rc;
}
/*
* callback for when a client disconnects
*/
static 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.
*/
static 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.
*/
static 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;
}
/*
* process a "yes:0,no:*,view:3" type action list comparing to command
* return code rc. * means the default action with no other match.
*/
static 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.
*
*/
static 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.
*/
static 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"
*/
static 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;
}
}
/*
* Break up comma separated list of hosts and call do_reverse_connect()
*/
static 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;
}
}
/*
* Routines for monitoring the VNC_CONNECT property for changes.
* The vncconnect(1) will set it on our X display.
*/
void read_vnc_connect_prop(void) {
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);
}
/*
* check if client_connect has been set, if so make the reverse connections.
*/
static void send_client_connect(void) {
if (client_connect != NULL) {
reverse_connect(client_connect);
free(client_connect);
client_connect = NULL;
}
}
/*
* monitor the various input methods
*/
void check_connect_inputs(void) {
/* 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);
}
/* -- keyboard.c -- */
/*
* For tweaking modifiers wrt the Alt-Graph key, etc.
*/
#define LEFTSHIFT 1
#define RIGHTSHIFT 2
#define ALTGR 4
static char mod_state = 0;
static char modifiers[0x100];
static KeyCode keycodes[0x100];
static KeyCode left_shift_code, right_shift_code, altgr_code;
void initialize_modtweak(void) {
KeySym keysym, *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 */
keysym = keymap[(i - minkey) * syms_per_keycode + 1];
if (keysym != NoSymbol) {
continue;
}
/* 1st one */
keysym = keymap[(i - minkey) * syms_per_keycode + 0];
if (keysym == NoSymbol) {
continue;
}
XConvertCase(keysym, &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++) {
keysym = keymap[ (i - minkey) * syms_per_keycode + j ];
if ( keysym >= ' ' && keysym < 0x100
&& i == XKeysymToKeycode(dpy, keysym) ) {
keycodes[keysym] = i;
modifiers[keysym] = 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);
}
/*
* Routine to retreive current state keyboard. 1 means down, 0 up.
*/
static void get_keystate(int *keystate) {
int i, k;
char keys[32];
/* n.b. caller decides to X_LOCK or not. */
XQueryKeymap(dpy, keys);
for (i=0; i<32; i++) {
char c = keys[i];
for (k=0; k < 8; k++) {
if (c & 0x1) {
keystate[8*i + k] = 1;
} else {
keystate[8*i + k] = 0;
}
c = c >> 1;
}
}
}
/*
* Try to KeyRelease any non-Lock modifiers that are down.
*/
void clear_modifiers(int init) {
static KeyCode keycodes[256];
static KeySym keysyms[256];
static char *keystrs[256];
static int kcount = 0, first = 1;
int keystate[256];
int i, j, minkey, maxkey, syms_per_keycode;
KeySym *keymap;
KeySym keysym;
KeyCode keycode;
/* n.b. caller decides to X_LOCK or not. */
if (first) {
/*
* we store results in static arrays, to aid interrupted
* case, but modifiers could have changed during session...
*/
XDisplayKeycodes(dpy, &minkey, &maxkey);
keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1),
&syms_per_keycode);
for (i = minkey; i <= maxkey; i++) {
for (j = 0; j < syms_per_keycode; j++) {
keysym = keymap[ (i - minkey) * syms_per_keycode + j ];
if (keysym == NoSymbol || ! ismodkey(keysym)) {
continue;
}
keycode = XKeysymToKeycode(dpy, keysym);
if (keycode == NoSymbol) {
continue;
}
keycodes[kcount] = keycode;
keysyms[kcount] = keysym;
keystrs[kcount] = strdup(XKeysymToString(keysym));
kcount++;
}
}
XFree((void *) keymap);
first = 0;
}
if (init) {
return;
}
get_keystate(keystate);
for (i=0; i < kcount; i++) {
keysym = keysyms[i];
keycode = keycodes[i];
if (! keystate[(int) keycode]) {
continue;
}
if (clear_mods) {
rfbLog("clear_modifiers: up: %-10s (0x%x) "
"keycode=0x%x\n", keystrs[i], keysym, keycode);
}
myXTestFakeKeyEvent(dpy, keycode, False, CurrentTime);
}
XFlush(dpy);
}
/*
* Attempt to set all keys to Up position. Can mess up typing at the
* physical keyboard so use with caution.
*/
void clear_keys(void) {
int k, keystate[256];
/* n.b. caller decides to X_LOCK or not. */
get_keystate(keystate);
for (k=0; k<256; k++) {
if (keystate[k]) {
KeyCode keycode = (KeyCode) k;
rfbLog("clear_keys: keycode=%d\n", keycode);
myXTestFakeKeyEvent(dpy, keycode, False, CurrentTime);
}
}
XFlush(dpy);
}
/*
* 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;
static keyremap_t *keyremaps = NULL;
/*
* process the -remap string (file or mapping string)
*/
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);
}
/*
* debugging wrapper for XTestFakeKeyEvent()
*/
void myXTestFakeKeyEvent(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);
}
/*
* does the actual tweak:
*/
static 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) {
myXTestFakeKeyEvent(dpy, left_shift_code, !dn, CurrentTime);
}
if (mod_state & RIGHTSHIFT) {
myXTestFakeKeyEvent(dpy, right_shift_code, !dn, CurrentTime);
}
}
if ( ! is_shift && mod == 1 ) {
myXTestFakeKeyEvent(dpy, left_shift_code, dn, CurrentTime);
}
if ( altgr_code && (mod_state & ALTGR) && mod != 2 ) {
myXTestFakeKeyEvent(dpy, altgr_code, !dn, CurrentTime);
}
if ( altgr_code && ! (mod_state & ALTGR) && mod == 2 ) {
myXTestFakeKeyEvent(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);
}
}
/*
* tweak the modifier under -modtweak
*/
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;
myXTestFakeKeyEvent(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.
*/
static rfbClientPtr last_keyboard_client = NULL;
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 ) {
myXTestFakeKeyEvent(dpy, k, (Bool) down, CurrentTime);
XFlush(dpy);
last_event = last_input = time(0);
got_user_input++;
got_keyboard_input++;
}
X_UNLOCK;
}
/* -- pointer.c -- */
/*
* 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
static prtremap_t pointer_map[MAX_BUTTONS+1][MAX_BUTTON_EVENTS];
/*
* For parsing the -buttonmap sections, e.g. "4" or ":Up+Up+Up:"
*/
static void buttonparse(int from, char **s) {
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)++;
}
}
/*
* process the -buttonmap string
*/
void initialize_pointer_map(char *pointer_remap) {
unsigned char map[MAX_BUTTONS];
int i, k;
/*
* This routine counts the number of pointer buttons on the X
* server (to avoid problems, even crashes, if a client has more
* buttons). And also initializes any pointer button remapping
* from -buttonmap option.
*/
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;
}
/*
* Send a pointer event to the X server.
*/
static 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) {
myXTestFakeKeyEvent(dpy, key, True,
CurrentTime);
}
if (up) {
myXTestFakeKeyEvent(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;
}
/*
* Actual callback from libvncserver when it gets a pointer event.
*/
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);
}
/* -- bell.c -- */
/*
* Bell event handling. Requires XKEYBOARD extension.
*/
#ifdef LIBVNCSERVER_HAVE_XKEYBOARD
static int xkb_base_event_type;
/*
* check for XKEYBOARD, set up xkb_base_event_type
*/
void initialize_watch_bell(void) {
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(void) {
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(void) {}
#endif
/* -- selection.c -- */
/*
* Selection/Cutbuffer/Clipboard handlers.
*/
static int own_selection = 0; /* whether we currently own PRIMARY or not */
static int set_cutbuffer = 0; /* to avoid bouncing the CutText right back */
static int sel_waittime = 15; /* some seconds to skip before first send */
static 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.
*/
static 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)
static 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.
*/
static void selection_request(XEvent *ev) {
XSelectionEvent notify_event;
XSelectionRequestEvent *req_event;
unsigned int length;
unsigned char *data;
#ifndef XA_LENGTH
unsigned long XA_LENGTH = XInternAtom(dpy, "LENGTH", True);
#endif
req_event = &(ev->xselectionrequest);
notify_event.type = SelectionNotify;
notify_event.display = req_event->display;
notify_event.requestor = req_event->requestor;
notify_event.selection = req_event->selection;
notify_event.target = req_event->target;
notify_event.time = req_event->time;
if (req_event->property == None) {
notify_event.property = req_event->target;
} else {
notify_event.property = req_event->property;
}
if (xcut_string) {
length = strlen(xcut_string);
} else {
length = 0;
}
if (ev->xselectionrequest.target == XA_LENGTH) {
/* length request */
XChangeProperty(ev->xselectionrequest.display,
ev->xselectionrequest.requestor,
ev->xselectionrequest.property,
ev->xselectionrequest.target, 32, PropModeReplace,
(unsigned char *) &length, sizeof(unsigned int));
} else {
/* data request */
data = (unsigned char *)xcut_string;
XChangeProperty(ev->xselectionrequest.display,
ev->xselectionrequest.requestor,
ev->xselectionrequest.property,
ev->xselectionrequest.target, 8, PropModeReplace,
data, length);
}
XSendEvent(req_event->display, req_event->requestor, False, 0,
(XEvent *)&notify_event);
XFlush(dpy);
}
/*
* 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.
*/
static void cutbuffer_send(void) {
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
static 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);
}
/*
* This routine is periodically called to check for selection related
* and other X11 events and respond to them as needed.
*/
void watch_xevents(void) {
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, (unsigned char *) text, len);
XFlush(dpy);
X_UNLOCK;
set_cutbuffer = 1;
}
/* -- cursor.c -- */
/*
* Here begins a bit of a mess to experiment with multiple cursors
* drawn on the remote background ...
*/
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
static 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 "
" ";
static cursor_info_t cur1 = {NULL, NULL, 18, 18, 8, 8, 1};
static cursor_info_t *cursors[2];
static 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
*/
static char cur_save[(4 * CUR_SIZE * CUR_SIZE)];
static int cur_save_cx, cur_save_cy, cur_save_which;
/*
* save current cursor info and the patch of non-cursor data it covers
*/
static 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(void) {
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...
*/
static 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;
}
/*
* This is for mouse patch drawing under -xinerama or -blackout
*/
static 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.
*/
static 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
*/
static 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;
}
}
}
/*
* wrapper to redraw the mouse patch
*/
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);
}
}
/*
* routine called periodically to update the mouse aspects (drawn &
* cursorpos updates)
*/
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);
}
/* -- screen.c -- */
/*
* X11 and rfb display/screen related routines
*/
/*
* 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;
}
/*
* 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;
if (viewonly_passwd) {
/* append the view only passwd after the normal passwd */
char **passwds_new = malloc(3*sizeof(char**));
char **passwds_old = (char **) screen->rfbAuthPasswdData;
passwds_new[0] = passwds_old[0];
passwds_new[1] = viewonly_passwd;
passwds_new[2] = NULL;
screen->rfbAuthPasswdData = (void*) passwds_new;
}
}
/* -- xinerama.c -- */
/*
* routines related to xinerama and blacking out rectangles
*/
/*
* 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.
*/
blackr[blackouts].x1 = x;
blackr[blackouts].y1 = y;
blackr[blackouts].x2 = X;
blackr[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(void) {
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(blackr[b].x1, blackr[b].y1,
blackr[b].x2, blackr[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 (void) {
#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;
}
}
/* -- scan.c -- */
/*
* routines for scanning and reading the X11 display for changes, and
* for doing all the tile work (shm, etc).
*/
/* array to hold the hints: */
static hint_t *hint_list;
/* nap state */
static int nap_ok = 0, nap_diff_count = 0;
static int scan_count = 0; /* indicates which scan pattern we are on */
static int scan_in_progress = 0;
/* scan pattern jitter from x0rfbserver */
#define NSCAN 32
static 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
};
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. */
static region_t *tile_region;
/*
* setup tile numbers and allocate the tile and hint arrays:
*/
void initialize_tiles(void) {
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.
*/
static int fs_factor = 0;
static 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
*/
static 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(void) {
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.
*/
static 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;
}
static 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;
}
}
static 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.
*/
static void hint_updates(void) {
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.
*/
static 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)
*/
static void tile_updates(void) {
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.
*/
static int *first_line = NULL, *last_line;
static unsigned short *left_diff, *right_diff;
static 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.
*/
static int copy_all_tiles(void) {
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.
*/
static int copy_all_tile_runs(void) {
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.
*/
static int copy_tiles_backward_pass(void) {
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;
}
static 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...
*/
static int fill_tile_gaps(void) {
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;
}
static 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...
*/
static int grow_islands(void) {
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;
}
/*
* Fill the framebuffer with zeros for each blackout region
*/
static void blackout_regions(void) {
int i;
for (i=0; i < blackouts; i++) {
zero_fb(blackr[i].x1, blackr[i].y1, blackr[i].x2, blackr[i].y2);
}
}
/*
* 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(void) {
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);
}
/*
* Utilities for managing the "naps" to cut down on amount of polling.
*/
static void nap_set(int tile_cnt) {
if (scan_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);
}
}
/*
* split up a long nap to improve the wakeup time
*/
static void nap_sleep(int ms, int split) {
int i, input = got_user_input;
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;
}
}
}
/*
* see if we should take a nap of some sort between polls
*/
static 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.
*/
static 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)
*/
static 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).
*/
static 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;
}
}
/*
* 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;
}
/*
* 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[].
*/
static 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(void) {
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!
*/
scan_count++;
scan_count %= NSCAN;
if (scan_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[scan_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 - scan_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 - scan_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);
}
/* -- x11vnc.c -- */
/*
* main routine for the x11vnc program
*/
static int defer_update_nofb = 6; /* defer a shorter time under -nofb */
/*
* 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().
*/
static 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;
}
/*
* simple function for measuring sub-second time differences, using
* a double to hold the value.
*/
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);
}
/*
* utility wrapper to call rfbProcessEvents
*/
void rfbPE(rfbScreenInfoPtr scr, long us) {
if (! use_threads) {
rfbProcessEvents(scr, us);
}
}
/*
* main x11vnc loop: polls, checks for events, iterate libvncserver, etc.
*/
static 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++;
}
}
/*
* text printed out under -help option
*/
static void print_help(void) {
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"
"\n"
"Rudimentary config file support: if the file $HOME/.x11vncrc exists then each\n"
"line in it is treated as a single command line option. Disable with -norc.\n"
"For each option name, the leading character \"-\" is not required. E.g. a\n"
"line that is either \"nap\" or \"-nap\" may be used. Likewise \"wait 100\"\n"
"or \"-wait 100\" are acceptable lines. The \"#\" character comments out to\n"
"the end of the line in the usual way. Leading and trailing whitespace is\n"
"trimmed off.\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"
"-viewpasswd string Supply a 2nd password for view-only logins. The -passwd\n"
" (full-access) password must also be supplied.\n"
"-passwdfile filename Specify libvncserver -passwd via the first line of\n"
" the file \"filename\" instead of via command line.\n"
" If a second non blank line exists in the file it is\n"
" taken as a view-only password (i.e. -viewpasswd) Note:\n"
" this is a simple plaintext passwd, see also -rfbauth\n"
" and -storepasswd below.\n"
"-storepasswd pass file Store password \"pass\" as the VNC password in the\n"
" file \"file\". Once the password is stored the\n"
" program exits. Use the password via \"-rfbauth file\"\n"
"-accept string Run a command (possibly to prompt the user at the X11\n"
" display) to decide whether an incoming client should be\n"
" allowed to connect or not. \"string\" is an external\n"
" command run via system(3) (see below for special cases).\n"
" Be sure to quote \"string\" if it contains spaces,\n"
" etc. If the external command returns 0 the client is\n"
" accepted, otherwise the client is rejected. See below\n"
" for an extension to accept a client view-only.\n"
"\n"
" Environment: The RFB_CLIENT_IP environment variable will\n"
" be set to the incoming client IP number and the port\n"
" in RFB_CLIENT_PORT (or -1 if unavailable). Similarly,\n"
" RFB_SERVER_IP and RFB_SERVER_PORT (the x11vnc side\n"
" of the connection), are set to allow identification\n"
" of the tcp virtual circuit. The x11vnc process\n"
" id will be in RFB_X11VNC_PID, a client id number in\n"
" RFB_CLIENT_ID, and the number of other connected clients\n"
" in RFB_CLIENT_COUNT.\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"
"-rc filename Use \"filename\" instead of $HOME/.x11vncrc for rc file.\n"
"-norc Do not process the $HOME/.x11vncrc file for options.\n"
"\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"
"-clear_mods At startup and exit clear the modifier keys by sending\n"
" KeyRelease for each one. The Lock modifiers are skipped.\n"
" Used to clear the state if the display was accidentally\n"
" left with any pressed down. That should be rare.\n"
"-clear_keys As -clear_mods, except try to release any pressed key.\n"
" Intended for debugging. This option and -clear_mods\n"
" can interfere with typing at the physical keyboard.\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"
"\n"
"-nofb Ignore framebuffer: only process keyboard and pointer.\n"
"-nobell Do not watch for XBell events.\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);
}
/*
* utility to get the current host name
*/
#define MAXN 256
static char *this_host(void) {
char host[MAXN];
#ifdef LIBVNCSERVER_HAVE_GETHOSTNAME
if (gethostname(host, MAXN) == 0) {
return strdup(host);
}
#endif
return NULL;
}
/*
* choose a desktop name
*/
static 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.
*/
static 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;
}
/*
* quick-n-dirty ~/.x11vncrc: each line (except # comments) is a cmdline option.
*/
static int argc2 = 0;
static char **argv2;
static void check_rcfile(int argc, char **argv) {
int i, norc = 0, argmax = 512;
char *infile = NULL;
char rcfile[512];
FILE *rc;
for (i=1; i < argc; i++) {
if (!strcmp(argv[i], "-norc")) {
norc = 1;
}
if (!strcmp(argv[i], "-rc")) {
if (i+1 >= argc) {
fprintf(stderr, "-rc option requires a "
"filename\n");
exit(1);
} else {
infile = argv[i+1];
}
}
}
if (norc) {
;
} else if (infile != NULL) {
rc = fopen(infile, "r");
if (rc == NULL) {
fprintf(stderr, "could not open rcfile: %s\n", infile);
perror("fopen");
exit(1);
}
} else if (getenv("HOME") == NULL) {
norc = 1;
} else {
strncpy(rcfile, getenv("HOME"), 500);
strcat(rcfile, "/.x11vncrc");
rc = fopen(rcfile, "r");
if (rc == NULL) {
norc = 1;
}
}
argv2 = (char **) malloc(argmax * sizeof(char *));
argv2[argc2++] = strdup(argv[0]);
if (! norc) {
char line[1024], parm[1024], tmp[1025];
while (fgets(line, 1024, rc) != NULL) {
char *q, *p = line;
q = p;
while (*q) {
if (*q == '\n') {
while (isspace(*q)) {
*q = '\0';
if (q == p) {
break;
}
q--;
}
break;
}
q++;
}
if ( (q = strchr(p, '#')) != NULL) {
*q = '\0';
}
while (*p) {
if (! isspace(*p)) {
break;
}
p++;
}
if (*p == '\0') {
continue;
}
if ( sscanf(p, "%s", parm) != 1) {
fprintf(stderr, "invalid rcfile line: %s\n", p);
exit(1);
}
if (parm[0] == '-') {
strncpy(tmp, parm, 1024);
} else {
tmp[0] = '-';
strncpy(tmp+1, parm, 1024);
}
argv2[argc2++] = strdup(tmp);
if (argc2 >= argmax) {
fprintf(stderr, "too many rcfile options\n");
exit(1);
}
p += strlen(parm);
while (*p) {
if (! isspace(*p)) {
break;
}
p++;
}
if (*p == '\0') {
continue;
}
argv2[argc2++] = strdup(p);
if (argc2 >= argmax) {
fprintf(stderr, "too many rcfile options\n");
exit(1);
}
}
fclose(rc);
}
for (i=1; i < argc; i++) {
argv2[argc2++] = strdup(argv[i]);
if (argc2 >= argmax) {
fprintf(stderr, "too many rcfile options\n");
exit(1);
}
}
}
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;
char *blackout_string = NULL;
char *remap_file = NULL;
char *pointer_remap = NULL;
int pw_loc = -1;
int vpw_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 argc_vnc = 1; char *argv_vnc[128];
argv_vnc[0] = strdup(argv[0]);
check_rcfile(argc, argv);
/* kludge for the new array argv2 set in check_rcfile() */
# define argc argc2
# define argv argv2
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, "-viewpasswd")) {
vpw_loc = i;
viewonly_passwd = strdup(argv[++i]);
} else if (!strcmp(arg, "-passwdfile")) {
passwdfile = argv[++i];
} else if (!strcmp(arg, "-storepasswd")) {
if (i+2 >= argc || vncEncryptAndStorePasswd(argv[i+1],
argv[i+2]) != 0) {
fprintf(stderr, "-storepasswd failed\n");
exit(1);
} else {
fprintf(stderr, "stored passwd in file %s\n",
argv[i+2]);
exit(0);
}
} 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, "-clear_mods")) {
clear_mods = 1;
} else if (!strcmp(arg, "-clear_keys")) {
clear_mods = 2;
} 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, "-norc")) {
;
} else if (!strcmp(arg, "-rc")) {
i++;
} 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 (argc_vnc < 100) {
argv_vnc[argc_vnc++] = 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 < argc_vnc; i++) {
fprintf(stderr, "passing arg to libvncserver: %s\n",
argv_vnc[i]);
if (!strcmp(argv_vnc[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) {
/* read passwd from file */
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) {
int len = strlen(line);
if (len > 0 && line[len-1] == '\n') {
line[len-1] = '\0';
}
argv_vnc[argc_vnc++] = "-passwd";
argv_vnc[argc_vnc++] = strdup(line);
pw_loc = 100; /* just for pw_loc check below */
if (fgets(line, 512, in) != NULL) {
/* try to read viewonly passwd from file */
int ok = 0;
len = strlen(line);
if (len > 0 && line[len-1] == '\n') {
line[len-1] = '\0';
}
if (strlen(line) > 0) {
char *p = line;
/* check for non-blank line */
while (*p != '\0') {
if (! isspace(*p)) {
ok = 1;
}
p++;
}
}
if (ok) {
viewonly_passwd = strdup(line);
} else {
fprintf(stderr, "*** not setting"
" viewonly password to the 2nd"
" line of %s. (blank or other"
" problem)\n", passwdfile);
}
}
} else {
fprintf(stderr, "cannot read a line from "
"passwdfile: %s\n", passwdfile);
exit(1);
}
fclose(in);
}
if (vpw_loc > 0) {
char *p = argv[vpw_loc];
while (*p != '\0') {
*p++ = '\0';
}
if (vpw_loc+1 < argc) {
p = argv[vpw_loc+1];
while (*p != '\0') {
*p++ = '\0';
}
}
}
if (viewonly_passwd && pw_loc < 0) {
fprintf(stderr, "-passwd must be supplied when using "
"-viewpasswd\n");
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) {
argv_vnc[argc_vnc++] = "-rfbwait";
argv_vnc[argc_vnc++] = "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);
argv_vnc[argc_vnc++] = "-deferupdate";
argv_vnc[argc_vnc++] = 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, "clearmods: %d\n", clear_mods);
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";
argv_vnc[argc_vnc++] = str;
argv_vnc[argc_vnc++] = 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(&argc_vnc, argv_vnc, 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 */
initialize_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(pointer_remap);
clear_modifiers(1);
if (clear_mods == 1) {
clear_modifiers(0);
}
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);
#undef argc
#undef argv
}