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.
1170 lines
26 KiB
1170 lines
26 KiB
/* -- xevents.c -- */
|
|
|
|
#include "x11vnc.h"
|
|
#include "xwrappers.h"
|
|
#include "xkb_bell.h"
|
|
#include "xrandr.h"
|
|
#include "xdamage.h"
|
|
#include "xrecord.h"
|
|
#include "selection.h"
|
|
#include "keyboard.h"
|
|
#include "cursor.h"
|
|
#include "gui.h"
|
|
#include "connections.h"
|
|
#include "unixpw.h"
|
|
#include "cleanup.h"
|
|
|
|
/* XXX CHECK BEFORE RELEASE */
|
|
int grab_buster = 0;
|
|
int sync_tod_delay = 3;
|
|
|
|
void initialize_vnc_connect_prop(void);
|
|
void initialize_x11vnc_remote_prop(void);
|
|
void initialize_clipboard_atom(void);
|
|
void spawn_grab_buster(void);
|
|
void sync_tod_with_servertime(void);
|
|
void check_keycode_state(void);
|
|
void check_autorepeat(void);
|
|
void check_xevents(int reset);
|
|
void xcut_receive(char *text, int len, rfbClientPtr cl);
|
|
|
|
|
|
static void initialize_xevents(int reset);
|
|
static void print_xevent_bases(void);
|
|
static void get_prop(char *str, int len, Atom prop);
|
|
static void bust_grab(int reset);
|
|
static int process_watch(char *str, int parent, int db);
|
|
static void grab_buster_watch(int parent, char *dstr);
|
|
|
|
|
|
void initialize_vnc_connect_prop(void) {
|
|
vnc_connect_str[0] = '\0';
|
|
RAWFB_RET_VOID
|
|
vnc_connect_prop = XInternAtom(dpy, "VNC_CONNECT", False);
|
|
}
|
|
|
|
void initialize_x11vnc_remote_prop(void) {
|
|
x11vnc_remote_str[0] = '\0';
|
|
RAWFB_RET_VOID
|
|
x11vnc_remote_prop = XInternAtom(dpy, "X11VNC_REMOTE", False);
|
|
}
|
|
|
|
void initialize_clipboard_atom(void) {
|
|
RAWFB_RET_VOID
|
|
clipboard_atom = XInternAtom(dpy, "CLIPBOARD", False);
|
|
if (clipboard_atom == None) {
|
|
if (! quiet) rfbLog("could not find atom CLIPBOARD\n");
|
|
if (watch_clipboard) {
|
|
watch_clipboard = 0;
|
|
}
|
|
if (set_clipboard) {
|
|
set_clipboard = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void initialize_xevents(int reset) {
|
|
static int did_xselect_input = 0;
|
|
static int did_xcreate_simple_window = 0;
|
|
static int did_vnc_connect_prop = 0;
|
|
static int did_x11vnc_remote_prop = 0;
|
|
static int did_clipboard_atom = 0;
|
|
static int did_xfixes = 0;
|
|
static int did_xdamage = 0;
|
|
static int did_xrandr = 0;
|
|
|
|
RAWFB_RET_VOID
|
|
|
|
if (reset) {
|
|
did_xselect_input = 0;
|
|
did_xcreate_simple_window = 0;
|
|
did_vnc_connect_prop = 0;
|
|
did_x11vnc_remote_prop = 0;
|
|
did_clipboard_atom = 0;
|
|
did_xfixes = 0;
|
|
did_xdamage = 0;
|
|
did_xrandr = 0;
|
|
}
|
|
|
|
if ((watch_selection || vnc_connect) && !did_xselect_input) {
|
|
/*
|
|
* register desired event(s) for notification.
|
|
* PropertyChangeMask is for CUT_BUFFER0 changes.
|
|
* XXX: does this cause a flood of other stuff?
|
|
*/
|
|
X_LOCK;
|
|
XSelectInput(dpy, rootwin, PropertyChangeMask);
|
|
X_UNLOCK;
|
|
did_xselect_input = 1;
|
|
}
|
|
if (watch_selection && !did_xcreate_simple_window) {
|
|
/* create fake window for our selection ownership, etc */
|
|
|
|
X_LOCK;
|
|
selwin = XCreateSimpleWindow(dpy, rootwin, 0, 0, 1, 1, 0, 0, 0);
|
|
X_UNLOCK;
|
|
did_xcreate_simple_window = 1;
|
|
}
|
|
|
|
if (xrandr && !did_xrandr) {
|
|
initialize_xrandr();
|
|
did_xrandr = 1;
|
|
}
|
|
if (vnc_connect && !did_vnc_connect_prop) {
|
|
initialize_vnc_connect_prop();
|
|
did_vnc_connect_prop = 1;
|
|
}
|
|
if (vnc_connect && !did_x11vnc_remote_prop) {
|
|
initialize_x11vnc_remote_prop();
|
|
did_x11vnc_remote_prop = 1;
|
|
}
|
|
if (run_gui_pid > 0) {
|
|
kill(run_gui_pid, SIGUSR1);
|
|
run_gui_pid = 0;
|
|
}
|
|
if (!did_clipboard_atom) {
|
|
initialize_clipboard_atom();
|
|
did_clipboard_atom = 1;
|
|
}
|
|
if (xfixes_present && use_xfixes && !did_xfixes) {
|
|
initialize_xfixes();
|
|
did_xfixes = 1;
|
|
}
|
|
if (xdamage_present && !did_xdamage) {
|
|
initialize_xdamage();
|
|
did_xdamage = 1;
|
|
}
|
|
}
|
|
|
|
static void print_xevent_bases(void) {
|
|
fprintf(stderr, "X event bases: xkb=%d, xtest=%d, xrandr=%d, "
|
|
"xfixes=%d, xdamage=%d, xtrap=%d\n", xkb_base_event_type,
|
|
xtest_base_event_type, xrandr_base_event_type,
|
|
xfixes_base_event_type, xdamage_base_event_type,
|
|
xtrap_base_event_type);
|
|
fprintf(stderr, " MapNotify=%d, ClientMsg=%d PropNotify=%d "
|
|
"SelNotify=%d, SelRequest=%d\n", MappingNotify, ClientMessage,
|
|
PropertyNotify, SelectionNotify, SelectionRequest);
|
|
fprintf(stderr, " SelClear=%d, Expose=%d\n", SelectionClear, Expose);
|
|
}
|
|
|
|
static void get_prop(char *str, int len, Atom prop) {
|
|
Atom type;
|
|
int format, slen, dlen, i;
|
|
unsigned long nitems = 0, bytes_after = 0;
|
|
unsigned char* data = NULL;
|
|
|
|
for (i=0; i<len; i++) {
|
|
str[i] = '\0';
|
|
}
|
|
if (prop == None) {
|
|
return;
|
|
}
|
|
RAWFB_RET_VOID
|
|
|
|
slen = 0;
|
|
|
|
do {
|
|
if (XGetWindowProperty(dpy, DefaultRootWindow(dpy),
|
|
prop, nitems/4, len/16, False,
|
|
AnyPropertyType, &type, &format, &nitems, &bytes_after,
|
|
&data) == Success) {
|
|
|
|
dlen = nitems * (format/8);
|
|
if (slen + dlen > len) {
|
|
/* too big */
|
|
XFree(data);
|
|
break;
|
|
}
|
|
memcpy(str+slen, data, dlen);
|
|
slen += dlen;
|
|
str[slen] = '\0';
|
|
XFree(data);
|
|
}
|
|
} while (bytes_after > 0);
|
|
}
|
|
|
|
static void bust_grab(int reset) {
|
|
static int bust_count = 0;
|
|
static time_t last_bust = 0;
|
|
time_t now = time(0);
|
|
KeyCode key;
|
|
int button, x, y, nb;
|
|
|
|
if (now > last_bust + 180) {
|
|
bust_count = 0;
|
|
}
|
|
if (reset) {
|
|
bust_count = 0;
|
|
return;
|
|
}
|
|
|
|
x = 0;
|
|
y = 0;
|
|
button = 0;
|
|
key = NoSymbol;
|
|
|
|
nb = 8;
|
|
if (bust_count >= 3 * nb) {
|
|
fprintf(stderr, "too many bust_grab's %d for me\n", bust_count);
|
|
exit(0);
|
|
}
|
|
if (bust_count % nb == 0) {
|
|
button = 1;
|
|
} else if (bust_count % nb == 1) {
|
|
button = 1;
|
|
} else if (bust_count % nb == 2) {
|
|
key = XKeysymToKeycode(dpy, XK_Escape);
|
|
} else if (bust_count % nb == 3) {
|
|
button = 3;
|
|
} else if (bust_count % nb == 4) {
|
|
key = XKeysymToKeycode(dpy, XK_space);
|
|
} else if (bust_count % nb == 5) {
|
|
x = bust_count * 23;
|
|
y = bust_count * 17;
|
|
} else if (bust_count % nb == 5) {
|
|
button = 2;
|
|
} else if (bust_count % nb == 6) {
|
|
key = XKeysymToKeycode(dpy, XK_a);
|
|
}
|
|
|
|
if (key == NoSymbol) {
|
|
key = XKeysymToKeycode(dpy, XK_a);
|
|
if (key == NoSymbol) {
|
|
button = 1;
|
|
}
|
|
}
|
|
|
|
bust_count++;
|
|
|
|
if (button) {
|
|
/* try button press+release */
|
|
fprintf(stderr, "**bust_grab: button%d %.4f\n",
|
|
button, dnowx());
|
|
XTestFakeButtonEvent_wr(dpy, button, True, CurrentTime);
|
|
XFlush_wr(dpy);
|
|
usleep(50 * 1000);
|
|
XTestFakeButtonEvent_wr(dpy, button, False, CurrentTime);
|
|
} else if (x > 0) {
|
|
/* try button motion*/
|
|
int scr = DefaultScreen(dpy);
|
|
|
|
fprintf(stderr, "**bust_grab: x=%d y=%d %.4f\n", x, y,
|
|
dnowx());
|
|
XTestFakeMotionEvent_wr(dpy, scr, x, y, CurrentTime);
|
|
XFlush_wr(dpy);
|
|
usleep(50 * 1000);
|
|
|
|
/* followed by button press */
|
|
button = 1;
|
|
fprintf(stderr, "**bust_grab: button%d\n", button);
|
|
XTestFakeButtonEvent_wr(dpy, button, True, CurrentTime);
|
|
XFlush_wr(dpy);
|
|
usleep(50 * 1000);
|
|
XTestFakeButtonEvent_wr(dpy, button, False, CurrentTime);
|
|
} else {
|
|
/* try Escape or Space press+release */
|
|
fprintf(stderr, "**bust_grab: keycode: %d %.4f\n",
|
|
(int) key, dnowx());
|
|
XTestFakeKeyEvent_wr(dpy, key, True, CurrentTime);
|
|
XFlush_wr(dpy);
|
|
usleep(50 * 1000);
|
|
XTestFakeKeyEvent_wr(dpy, key, False, CurrentTime);
|
|
}
|
|
XFlush_wr(dpy);
|
|
last_bust = time(0);
|
|
}
|
|
|
|
typedef struct _grabwatch {
|
|
int pid;
|
|
int tick;
|
|
unsigned long time;
|
|
time_t change;
|
|
} grabwatch_t;
|
|
#define GRABWATCH 16
|
|
|
|
static int grab_buster_delay = 20;
|
|
static pid_t grab_buster_pid = 0;
|
|
|
|
static int grab_npids = 1;
|
|
|
|
static int process_watch(char *str, int parent, int db) {
|
|
int i, pid, ticker, npids;
|
|
char diff[128];
|
|
unsigned long xtime;
|
|
static grabwatch_t watches[GRABWATCH];
|
|
static int first = 1;
|
|
time_t now = time(0);
|
|
static time_t last_bust = 0;
|
|
int too_long, problems = 0;
|
|
|
|
if (first) {
|
|
for (i=0; i < GRABWATCH; i++) {
|
|
watches[i].pid = 0;
|
|
watches[i].tick = 0;
|
|
watches[i].time = 0;
|
|
watches[i].change = 0;
|
|
}
|
|
first = 0;
|
|
}
|
|
|
|
/* record latest value of prop */
|
|
if (str && *str != '\0') {
|
|
if (sscanf(str, "%d/%d/%lu/%s", &pid, &ticker, &xtime, diff)
|
|
== 4) {
|
|
int got = -1, free = -1;
|
|
|
|
if (db) fprintf(stderr, "grab_buster %d - %d - %lu - %s"
|
|
"\n", pid, ticker, xtime, diff);
|
|
|
|
if (pid == parent && !strcmp(diff, "QUIT")) {
|
|
/* that's it. */
|
|
return 0;
|
|
}
|
|
if (pid == 0 || ticker == 0 || xtime == 0) {
|
|
/* bad prop read. */
|
|
goto badtickerstr;
|
|
}
|
|
for (i=0; i < GRABWATCH; i++) {
|
|
if (watches[i].pid == pid) {
|
|
got = i;
|
|
break;
|
|
}
|
|
if (free == -1 && watches[i].pid == 0) {
|
|
free = i;
|
|
}
|
|
}
|
|
if (got == -1) {
|
|
if (free == -1) {
|
|
/* bad news */;
|
|
free = GRABWATCH - 1;
|
|
}
|
|
watches[free].pid = pid;
|
|
watches[free].tick = ticker;
|
|
watches[free].time = xtime;
|
|
watches[free].change = now;
|
|
if (db) fprintf(stderr, "grab_buster free slot: %d\n", free);
|
|
} else {
|
|
if (db) fprintf(stderr, "grab_buster got slot: %d\n", got);
|
|
if (watches[got].tick != ticker) {
|
|
watches[got].change = now;
|
|
}
|
|
if (watches[got].time != xtime) {
|
|
watches[got].change = now;
|
|
}
|
|
watches[got].tick = ticker;
|
|
watches[got].time = xtime;
|
|
}
|
|
} else {
|
|
if (db) fprintf(stderr, "grab_buster bad prop str: %s\n", str);
|
|
}
|
|
}
|
|
|
|
badtickerstr:
|
|
|
|
too_long = grab_buster_delay;
|
|
if (too_long < 3 * sync_tod_delay) {
|
|
too_long = 3 * sync_tod_delay;
|
|
}
|
|
|
|
npids = 0;
|
|
for (i=0; i < GRABWATCH; i++) {
|
|
if (watches[i].pid) {
|
|
npids++;
|
|
}
|
|
}
|
|
grab_npids = npids;
|
|
if (npids > 4) {
|
|
npids = 4;
|
|
}
|
|
|
|
/* now check everyone we are tracking */
|
|
for (i=0; i < GRABWATCH; i++) {
|
|
int fac = 1;
|
|
if (!watches[i].pid) {
|
|
continue;
|
|
}
|
|
if (watches[i].change == 0) {
|
|
watches[i].change = now; /* just to be sure */
|
|
continue;
|
|
}
|
|
|
|
pid = watches[i].pid;
|
|
|
|
if (pid != parent) {
|
|
fac = 2;
|
|
}
|
|
if (npids > 0) {
|
|
fac *= npids;
|
|
}
|
|
|
|
if (now > watches[i].change + fac*too_long) {
|
|
int process_alive = 1;
|
|
|
|
fprintf(stderr, "grab_buster: problem with pid: "
|
|
"%d - %d/%d/%d\n", pid, (int) now,
|
|
(int) watches[i].change, too_long);
|
|
|
|
if (kill((pid_t) pid, 0) != 0) {
|
|
if (1 || errno == ESRCH) {
|
|
process_alive = 0;
|
|
}
|
|
}
|
|
|
|
if (!process_alive) {
|
|
watches[i].pid = 0;
|
|
watches[i].tick = 0;
|
|
watches[i].time = 0;
|
|
watches[i].change = 0;
|
|
fprintf(stderr, "grab_buster: pid gone: %d\n",
|
|
pid);
|
|
if (pid == parent) {
|
|
/* that's it */
|
|
return 0;
|
|
}
|
|
} else {
|
|
int sleep = sync_tod_delay * 1000 * 1000;
|
|
|
|
bust_grab(0);
|
|
problems++;
|
|
last_bust = now;
|
|
usleep(1 * sleep);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!problems) {
|
|
bust_grab(1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void grab_buster_watch(int parent, char *dstr) {
|
|
Atom ticker_atom = None;
|
|
int sleep = sync_tod_delay * 921 * 1000;
|
|
char propval[200];
|
|
int ev, er, maj, min;
|
|
int db = 0;
|
|
|
|
RAWFB_RET_VOID
|
|
|
|
if (grab_buster > 1) {
|
|
db = 1;
|
|
}
|
|
|
|
/* overwrite original dpy, we let orig connection sit unused. */
|
|
dpy = XOpenDisplay(dstr);
|
|
if (!dpy) {
|
|
fprintf(stderr, "grab_buster_watch: could not reopen: %s\n",
|
|
dstr);
|
|
return;
|
|
}
|
|
rfbLogEnable(0);
|
|
|
|
/* check for XTEST, etc, and then disable grabs for us */
|
|
if (! XTestQueryExtension_wr(dpy, &ev, &er, &maj, &min)) {
|
|
xtest_present = 0;
|
|
} else {
|
|
xtest_present = 1;
|
|
}
|
|
if (! XETrapQueryExtension_wr(dpy, &ev, &er, &maj)) {
|
|
xtrap_present = 0;
|
|
} else {
|
|
xtrap_present = 1;
|
|
}
|
|
|
|
if (! xtest_present && ! xtrap_present) {
|
|
fprintf(stderr, "grab_buster_watch: no grabserver "
|
|
"protection on display: %s\n", dstr);
|
|
return;
|
|
}
|
|
disable_grabserver(dpy, 0);
|
|
|
|
usleep(3 * sleep);
|
|
|
|
ticker_atom = XInternAtom(dpy, "X11VNC_TICKER", False);
|
|
if (! ticker_atom) {
|
|
fprintf(stderr, "grab_buster_watch: no ticker atom\n");
|
|
return;
|
|
}
|
|
|
|
while(1) {
|
|
int slp = sleep;
|
|
if (grab_npids > 1) {
|
|
slp = slp / 8;
|
|
}
|
|
usleep(slp);
|
|
usleep((int) (0.60 * rfac() * slp));
|
|
|
|
if (kill((pid_t) parent, 0) != 0) {
|
|
break;
|
|
}
|
|
|
|
get_prop(propval, 128, ticker_atom);
|
|
if (db) fprintf(stderr, "got_prop: %s\n", propval);
|
|
|
|
if (!process_watch(propval, parent, db)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void spawn_grab_buster(void) {
|
|
#if LIBVNCSERVER_HAVE_FORK
|
|
pid_t pid;
|
|
int parent = (int) getpid();
|
|
char *dstr = strdup(DisplayString(dpy));
|
|
|
|
RAWFB_RET_VOID
|
|
|
|
XCloseDisplay(dpy);
|
|
dpy = NULL;
|
|
|
|
if ((pid = fork()) > 0) {
|
|
grab_buster_pid = pid;
|
|
if (! quiet) {
|
|
rfbLog("grab_buster pid is: %d\n", (int) pid);
|
|
}
|
|
} else if (pid == -1) {
|
|
fprintf(stderr, "spawn_grab_buster: could not fork\n");
|
|
rfbLogPerror("fork");
|
|
} else {
|
|
signal(SIGHUP, SIG_DFL);
|
|
signal(SIGINT, SIG_DFL);
|
|
signal(SIGQUIT, SIG_DFL);
|
|
signal(SIGTERM, SIG_DFL);
|
|
|
|
grab_buster_watch(parent, dstr);
|
|
exit(0);
|
|
}
|
|
|
|
dpy = XOpenDisplay(dstr);
|
|
if (!dpy) {
|
|
rfbLog("failed to reopen display %s in spawn_grab_buster\n",
|
|
dstr);
|
|
exit(1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void sync_tod_with_servertime(void) {
|
|
static Atom ticker_atom = None;
|
|
XEvent xev;
|
|
char diff[128];
|
|
static int seq = 0;
|
|
static unsigned long xserver_ticks = 1;
|
|
int i, db = 0;
|
|
|
|
RAWFB_RET_VOID
|
|
|
|
if (! ticker_atom) {
|
|
ticker_atom = XInternAtom(dpy, "X11VNC_TICKER", False);
|
|
}
|
|
if (! ticker_atom) {
|
|
return;
|
|
}
|
|
|
|
XSync(dpy, False);
|
|
while (XCheckTypedEvent(dpy, PropertyNotify, &xev)) {
|
|
;
|
|
}
|
|
|
|
snprintf(diff, 128, "%d/%08d/%lu/%.6f", (int) getpid(), seq++,
|
|
xserver_ticks, servertime_diff);
|
|
XChangeProperty(dpy, rootwin, ticker_atom, XA_STRING, 8,
|
|
PropModeReplace, (unsigned char *) diff, strlen(diff));
|
|
XSync(dpy, False);
|
|
|
|
for (i=0; i < 10; i++) {
|
|
int k, got = 0;
|
|
|
|
for (k=0; k < 5; k++) {
|
|
while (XCheckTypedEvent(dpy, PropertyNotify, &xev)) {
|
|
if (xev.xproperty.atom == ticker_atom) {
|
|
double stime;
|
|
|
|
xserver_ticks = xev.xproperty.time;
|
|
stime = (double) xev.xproperty.time;
|
|
stime = stime/1000.0;
|
|
servertime_diff = dnow() - stime;
|
|
if (db) rfbLog("set servertime_diff: "
|
|
"%.6f\n", servertime_diff);
|
|
got = 1;
|
|
}
|
|
}
|
|
}
|
|
if (got) {
|
|
break;
|
|
}
|
|
usleep(1000);
|
|
}
|
|
}
|
|
|
|
void check_keycode_state(void) {
|
|
static time_t last_check = 0;
|
|
int delay = 10, noinput = 3;
|
|
time_t now = time(0);
|
|
|
|
if (! client_count) {
|
|
return;
|
|
}
|
|
|
|
RAWFB_RET_VOID
|
|
|
|
if (unixpw_in_progress) return;
|
|
|
|
/*
|
|
* periodically update our model of the keycode_state[]
|
|
* by correlating with the Xserver. wait for a pause in
|
|
* keyboard input to be on the safe side. the idea here
|
|
* is to remove stale keycode state, not to be perfectly
|
|
* in sync with the Xserver at every instant of time.
|
|
*/
|
|
if (now > last_check + delay && now > last_keyboard_input + noinput) {
|
|
init_track_keycode_state();
|
|
last_check = now;
|
|
}
|
|
}
|
|
|
|
void check_autorepeat(void) {
|
|
static time_t last_check = 0;
|
|
time_t now = time(0);
|
|
int autorepeat_is_on, autorepeat_initially_on, idle_timeout = 300;
|
|
static int idle_reset = 0;
|
|
|
|
if (! no_autorepeat || ! client_count) {
|
|
return;
|
|
}
|
|
if (now <= last_check + 1) {
|
|
return;
|
|
}
|
|
|
|
if (unixpw_in_progress) return;
|
|
|
|
last_check = now;
|
|
|
|
autorepeat_is_on = get_autorepeat_state();
|
|
autorepeat_initially_on = get_initial_autorepeat_state();
|
|
|
|
if (view_only) {
|
|
if (! autorepeat_is_on) {
|
|
autorepeat(1, 1);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (now > last_keyboard_input + idle_timeout) {
|
|
/* autorepeat should be on when idle */
|
|
if (! autorepeat_is_on && autorepeat_initially_on) {
|
|
static time_t last_msg = 0;
|
|
static int cnt = 0;
|
|
if (now > last_msg + idle_timeout && cnt++ < 5) {
|
|
rfbLog("idle keyboard: turning X autorepeat"
|
|
" back on.\n");
|
|
last_msg = now;
|
|
}
|
|
autorepeat(1, 1);
|
|
idle_reset = 1;
|
|
}
|
|
} else {
|
|
if (idle_reset) {
|
|
static time_t last_msg = 0;
|
|
static int cnt = 0;
|
|
if (now > last_msg + idle_timeout && cnt++ < 5) {
|
|
rfbLog("active keyboard: turning X autorepeat"
|
|
" off.\n");
|
|
last_msg = now;
|
|
}
|
|
autorepeat(0, 1);
|
|
idle_reset = 0;
|
|
|
|
} else if (no_repeat_countdown && autorepeat_is_on) {
|
|
int n = no_repeat_countdown - 1;
|
|
if (n >= 0) {
|
|
rfbLog("Battling with something for "
|
|
"-norepeat!! (%d resets left)\n", n);
|
|
} else {
|
|
rfbLog("Battling with something for "
|
|
"-norepeat!!\n");
|
|
}
|
|
if (no_repeat_countdown > 0) {
|
|
no_repeat_countdown--;
|
|
}
|
|
autorepeat(1, 0);
|
|
autorepeat(0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This routine is periodically called to check for selection related
|
|
* and other X11 events and respond to them as needed.
|
|
*/
|
|
void check_xevents(int reset) {
|
|
XEvent xev;
|
|
int tmp, have_clients = 0;
|
|
static int sent_some_sel = 0;
|
|
static time_t last_call = 0;
|
|
static time_t last_bell = 0;
|
|
static time_t last_init_check = 0;
|
|
static time_t last_sync = 0;
|
|
static time_t last_time_sync = 0;
|
|
time_t now = time(0);
|
|
static double last_request = 0.0;
|
|
XErrorHandler old_handler;
|
|
|
|
RAWFB_RET_VOID
|
|
|
|
if (unixpw_in_progress) return;
|
|
|
|
if (now > last_init_check+1 || reset) {
|
|
last_init_check = now;
|
|
initialize_xevents(reset);
|
|
if (reset) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (screen && screen->clientHead) {
|
|
have_clients = 1;
|
|
}
|
|
|
|
X_LOCK;
|
|
/*
|
|
* There is a bug where we have to wait before sending text to
|
|
* the client... so instead of sending right away we wait a
|
|
* the few seconds.
|
|
*/
|
|
|
|
if (have_clients && watch_selection && !sent_some_sel
|
|
&& now > last_client + sel_waittime) {
|
|
if (XGetSelectionOwner(dpy, XA_PRIMARY) == None) {
|
|
cutbuffer_send();
|
|
}
|
|
sent_some_sel = 1;
|
|
}
|
|
if (! have_clients) {
|
|
/*
|
|
* If we don't have clients we can miss the X server
|
|
* going away until a client connects.
|
|
*/
|
|
static time_t last_X_ping = 0;
|
|
if (now > last_X_ping + 5) {
|
|
last_X_ping = now;
|
|
XGetSelectionOwner(dpy, XA_PRIMARY);
|
|
}
|
|
}
|
|
|
|
if (now > last_call+1) {
|
|
/* we only check these once a second or so. */
|
|
int n = 0;
|
|
|
|
trapped_xerror = 0;
|
|
old_handler = XSetErrorHandler(trap_xerror);
|
|
|
|
while (XCheckTypedEvent(dpy, MappingNotify, &xev)) {
|
|
XRefreshKeyboardMapping((XMappingEvent *) &xev);
|
|
n++;
|
|
}
|
|
if (n && use_modifier_tweak) {
|
|
X_UNLOCK;
|
|
initialize_modtweak();
|
|
X_LOCK;
|
|
}
|
|
if (xtrap_base_event_type) {
|
|
int base = xtrap_base_event_type;
|
|
while (XCheckTypedEvent(dpy, base, &xev)) {
|
|
;
|
|
}
|
|
}
|
|
if (xtest_base_event_type) {
|
|
int base = xtest_base_event_type;
|
|
while (XCheckTypedEvent(dpy, base, &xev)) {
|
|
;
|
|
}
|
|
}
|
|
/*
|
|
* we can get ClientMessage from our XSendEvent() call in
|
|
* selection_request().
|
|
*/
|
|
while (XCheckTypedEvent(dpy, ClientMessage, &xev)) {
|
|
;
|
|
}
|
|
|
|
XSetErrorHandler(old_handler);
|
|
trapped_xerror = 0;
|
|
}
|
|
|
|
/* check for CUT_BUFFER0 and VNC_CONNECT changes: */
|
|
if (XCheckTypedEvent(dpy, PropertyNotify, &xev)) {
|
|
if (xev.type == PropertyNotify) {
|
|
if (xev.xproperty.atom == XA_CUT_BUFFER0) {
|
|
/*
|
|
* Go retrieve CUT_BUFFER0 and send it.
|
|
*
|
|
* set_cutbuffer is a flag to try to avoid
|
|
* processing our own cutbuffer changes.
|
|
*/
|
|
if (have_clients && watch_selection
|
|
&& ! set_cutbuffer) {
|
|
cutbuffer_send();
|
|
sent_some_sel = 1;
|
|
}
|
|
set_cutbuffer = 0;
|
|
} else if (vnc_connect && vnc_connect_prop != None
|
|
&& xev.xproperty.atom == vnc_connect_prop) {
|
|
/*
|
|
* Go retrieve VNC_CONNECT string.
|
|
*/
|
|
read_vnc_connect_prop(0);
|
|
} else if (vnc_connect && x11vnc_remote_prop != None
|
|
&& xev.xproperty.atom == x11vnc_remote_prop) {
|
|
/*
|
|
* Go retrieve X11VNC_REMOTE string.
|
|
*/
|
|
read_x11vnc_remote_prop(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* do this now that we have just cleared PropertyNotify */
|
|
tmp = 0;
|
|
if (rfac() < 0.6) {
|
|
tmp = 1;
|
|
}
|
|
if (now > last_time_sync + sync_tod_delay + tmp) {
|
|
sync_tod_with_servertime();
|
|
last_time_sync = now;
|
|
}
|
|
|
|
#if LIBVNCSERVER_HAVE_LIBXRANDR
|
|
if (xrandr) {
|
|
check_xrandr_event("check_xevents");
|
|
}
|
|
#endif
|
|
#if LIBVNCSERVER_HAVE_LIBXFIXES
|
|
if (xfixes_present && use_xfixes && xfixes_base_event_type) {
|
|
if (XCheckTypedEvent(dpy, xfixes_base_event_type +
|
|
XFixesCursorNotify, &xev)) {
|
|
got_xfixes_cursor_notify++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* check for our PRIMARY request notification: */
|
|
if (watch_primary || watch_clipboard) {
|
|
int doprimary = 1, doclipboard = 2, which, own = 0;
|
|
double delay = 1.0;
|
|
Atom atom;
|
|
char *req;
|
|
|
|
if (XCheckTypedEvent(dpy, SelectionNotify, &xev)) {
|
|
if (xev.type == SelectionNotify &&
|
|
xev.xselection.requestor == selwin &&
|
|
xev.xselection.property != None &&
|
|
xev.xselection.target == XA_STRING) {
|
|
Atom s = xev.xselection.selection;
|
|
if (s == XA_PRIMARY || s == clipboard_atom) {
|
|
/* go retrieve it and check it */
|
|
if (now > last_client + sel_waittime
|
|
|| sent_some_sel) {
|
|
selection_send(&xev);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* Every second or so, request PRIMARY or CLIPBOARD,
|
|
* unless we already own it or there is no owner or we
|
|
* have no clients.
|
|
* TODO: even at this low rate we should look into
|
|
* and performance problems in odds cases (large text,
|
|
* modem, etc.)
|
|
*/
|
|
which = 0;
|
|
if (watch_primary && watch_clipboard && ! own_clipboard &&
|
|
! own_primary) {
|
|
delay = 0.6;
|
|
}
|
|
if (dnow() > last_request + delay) {
|
|
/*
|
|
* It is not a good idea to do both at the same
|
|
* time so we must choose one:
|
|
*/
|
|
if (watch_primary && watch_clipboard) {
|
|
static int count = 0;
|
|
if (own_clipboard) {
|
|
which = doprimary;
|
|
} else if (own_primary) {
|
|
which = doclipboard;
|
|
} else if (count++ % 3 == 0) {
|
|
which = doclipboard;
|
|
} else {
|
|
which = doprimary;
|
|
}
|
|
} else if (watch_primary) {
|
|
which = doprimary;
|
|
} else if (watch_clipboard) {
|
|
which = doclipboard;
|
|
}
|
|
last_request = dnow();
|
|
}
|
|
atom = None;
|
|
req = "none";
|
|
if (which == doprimary) {
|
|
own = own_primary;
|
|
atom = XA_PRIMARY;
|
|
req = "PRIMARY";
|
|
} else if (which == doclipboard) {
|
|
own = own_clipboard;
|
|
atom = clipboard_atom;
|
|
req = "CLIPBOARD";
|
|
}
|
|
if (which != 0 && ! own && have_clients &&
|
|
XGetSelectionOwner(dpy, atom) != None) {
|
|
XConvertSelection(dpy, atom, XA_STRING, XA_STRING,
|
|
selwin, CurrentTime);
|
|
if (debug_sel) {
|
|
rfbLog("request %s\n", req);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (own_primary || own_clipboard) {
|
|
/* we own PRIMARY or CLIPBOARD, see if someone requested it: */
|
|
trapped_xerror = 0;
|
|
old_handler = XSetErrorHandler(trap_xerror);
|
|
|
|
if (XCheckTypedEvent(dpy, SelectionRequest, &xev)) {
|
|
if (own_primary && xev.type == SelectionRequest &&
|
|
xev.xselectionrequest.selection == XA_PRIMARY) {
|
|
selection_request(&xev, "PRIMARY");
|
|
}
|
|
if (own_clipboard && xev.type == SelectionRequest &&
|
|
xev.xselectionrequest.selection == clipboard_atom) {
|
|
selection_request(&xev, "CLIPBOARD");
|
|
}
|
|
}
|
|
|
|
/* we own PRIMARY or CLIPBOARD, see if we no longer own it: */
|
|
if (XCheckTypedEvent(dpy, SelectionClear, &xev)) {
|
|
if (own_primary && xev.type == SelectionClear &&
|
|
xev.xselectionclear.selection == XA_PRIMARY) {
|
|
own_primary = 0;
|
|
if (xcut_str_primary) {
|
|
free(xcut_str_primary);
|
|
xcut_str_primary = NULL;
|
|
}
|
|
if (debug_sel) {
|
|
rfbLog("Released PRIMARY.\n");
|
|
}
|
|
}
|
|
if (own_clipboard && xev.type == SelectionClear &&
|
|
xev.xselectionclear.selection == clipboard_atom) {
|
|
own_clipboard = 0;
|
|
if (xcut_str_clipboard) {
|
|
free(xcut_str_clipboard);
|
|
xcut_str_clipboard = NULL;
|
|
}
|
|
if (debug_sel) {
|
|
rfbLog("Released CLIPBOARD.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
XSetErrorHandler(old_handler);
|
|
trapped_xerror = 0;
|
|
}
|
|
|
|
if (watch_bell || now > last_bell+1) {
|
|
last_bell = now;
|
|
check_bell_event();
|
|
}
|
|
if (tray_request != None) {
|
|
static time_t last_tray_request = 0;
|
|
if (now > last_tray_request + 2) {
|
|
last_tray_request = now;
|
|
if (tray_embed(tray_request, tray_unembed)) {
|
|
tray_window = tray_request;
|
|
tray_request = None;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef DEBUG_XEVENTS
|
|
#define DEBUG_XEVENTS 1
|
|
#endif
|
|
#if DEBUG_XEVENTS
|
|
if (debug_xevents) {
|
|
static time_t last_check = 0;
|
|
static time_t reminder = 0;
|
|
static int freq = 0;
|
|
|
|
if (! freq) {
|
|
if (getenv("X11VNC_REMINDER_RATE")) {
|
|
freq = atoi(getenv("X11VNC_REMINDER_RATE"));
|
|
} else {
|
|
freq = 300;
|
|
}
|
|
}
|
|
|
|
if (now > last_check + 1) {
|
|
int ev_type_max = 300, ev_size = 400;
|
|
XEvent xevs[400];
|
|
int i, tot = XEventsQueued(dpy, QueuedAlready);
|
|
|
|
if (reminder == 0 || (tot && now > reminder + freq)) {
|
|
print_xevent_bases();
|
|
reminder = now;
|
|
}
|
|
last_check = now;
|
|
|
|
if (tot) {
|
|
fprintf(stderr, "Total events queued: %d\n",
|
|
tot);
|
|
}
|
|
for (i=1; i<ev_type_max; i++) {
|
|
int k, n = 0;
|
|
while (XCheckTypedEvent(dpy, i, xevs+n)) {
|
|
if (++n >= ev_size) {
|
|
break;
|
|
}
|
|
}
|
|
if (n) {
|
|
fprintf(stderr, " %d%s events of type"
|
|
" %d queued\n", n,
|
|
(n >= ev_size) ? "+" : "", i);
|
|
}
|
|
for (k=n-1; k >= 0; k--) {
|
|
XPutBackEvent(dpy, xevs+k);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (now > last_sync + 1200) {
|
|
/* kludge for any remaining event leaks */
|
|
int bugout = use_xdamage ? 500 : 50;
|
|
int qlen, i;
|
|
if (last_sync != 0) {
|
|
qlen = XEventsQueued(dpy, QueuedAlready);
|
|
if (qlen >= bugout) {
|
|
rfbLog("event leak: %d queued, "
|
|
" calling XSync(dpy, True)\n", qlen);
|
|
rfbLog(" for diagnostics run: 'x11vnc -R"
|
|
" debug_xevents:1'\n");
|
|
XSync(dpy, True);
|
|
}
|
|
}
|
|
last_sync = now;
|
|
|
|
/* clear these, we don't want any events on them */
|
|
if (rdpy_ctrl) {
|
|
qlen = XEventsQueued(rdpy_ctrl, QueuedAlready);
|
|
for (i=0; i<qlen; i++) {
|
|
XNextEvent(rdpy_ctrl, &xev);
|
|
}
|
|
}
|
|
if (gdpy_ctrl) {
|
|
qlen = XEventsQueued(gdpy_ctrl, QueuedAlready);
|
|
for (i=0; i<qlen; i++) {
|
|
XNextEvent(gdpy_ctrl, &xev);
|
|
}
|
|
}
|
|
}
|
|
X_UNLOCK;
|
|
|
|
last_call = now;
|
|
}
|
|
|
|
/*
|
|
* hook called when a VNC client sends us some "XCut" text (rfbClientCutText).
|
|
*/
|
|
void xcut_receive(char *text, int len, rfbClientPtr cl) {
|
|
allowed_input_t input;
|
|
|
|
RAWFB_RET_VOID
|
|
|
|
if (unixpw_in_progress) return;
|
|
|
|
if (!watch_selection) {
|
|
return;
|
|
}
|
|
if (view_only) {
|
|
return;
|
|
}
|
|
if (text == NULL || len == 0) {
|
|
return;
|
|
}
|
|
get_allowed_input(cl, &input);
|
|
if (!input.clipboard) {
|
|
return;
|
|
}
|
|
|
|
if (! check_sel_direction("recv", "xcut_receive", text, len)) {
|
|
return;
|
|
}
|
|
|
|
X_LOCK;
|
|
|
|
/* associate this text with PRIMARY (and SECONDARY...) */
|
|
if (set_primary && ! own_primary) {
|
|
own_primary = 1;
|
|
/* we need to grab the PRIMARY selection */
|
|
XSetSelectionOwner(dpy, XA_PRIMARY, selwin, CurrentTime);
|
|
XFlush_wr(dpy);
|
|
if (debug_sel) {
|
|
rfbLog("Own PRIMARY.\n");
|
|
}
|
|
}
|
|
|
|
if (set_clipboard && ! own_clipboard && clipboard_atom != None) {
|
|
own_clipboard = 1;
|
|
/* we need to grab the CLIPBOARD selection */
|
|
XSetSelectionOwner(dpy, clipboard_atom, selwin, CurrentTime);
|
|
XFlush_wr(dpy);
|
|
if (debug_sel) {
|
|
rfbLog("Own CLIPBOARD.\n");
|
|
}
|
|
}
|
|
|
|
/* duplicate the text string for our own use. */
|
|
if (set_primary) {
|
|
if (xcut_str_primary != NULL) {
|
|
free(xcut_str_primary);
|
|
xcut_str_primary = NULL;
|
|
}
|
|
xcut_str_primary = (char *) malloc((size_t) (len+1));
|
|
strncpy(xcut_str_primary, text, len);
|
|
xcut_str_primary[len] = '\0'; /* make sure null terminated */
|
|
if (debug_sel) {
|
|
rfbLog("Set PRIMARY '%s'\n", xcut_str_primary);
|
|
}
|
|
}
|
|
if (set_clipboard) {
|
|
if (xcut_str_clipboard != NULL) {
|
|
free(xcut_str_clipboard);
|
|
xcut_str_clipboard = NULL;
|
|
}
|
|
xcut_str_clipboard = (char *) malloc((size_t) (len+1));
|
|
strncpy(xcut_str_clipboard, text, len);
|
|
xcut_str_clipboard[len] = '\0'; /* make sure null terminated */
|
|
if (debug_sel) {
|
|
rfbLog("Set CLIPBOARD '%s'\n", xcut_str_clipboard);
|
|
}
|
|
}
|
|
|
|
/* copy this text to CUT_BUFFER0 as well: */
|
|
XChangeProperty(dpy, rootwin, XA_CUT_BUFFER0, XA_STRING, 8,
|
|
PropModeReplace, (unsigned char *) text, len);
|
|
XFlush_wr(dpy);
|
|
|
|
X_UNLOCK;
|
|
|
|
set_cutbuffer = 1;
|
|
}
|
|
|
|
|