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;