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.
7325 lines
182 KiB
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 *)¬ify_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;
|
|