1/*
2   Copyright (C) 2002-2010 Karl J. Runge <runge@karlrunge.com>
3   All rights reserved.
4
5This file is part of x11vnc.
6
7x11vnc is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation; either version 2 of the License, or (at
10your option) any later version.
11
12x11vnc is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with x11vnc; if not, write to the Free Software
19Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
20or see <http://www.gnu.org/licenses/>.
21
22In addition, as a special exception, Karl J. Runge
23gives permission to link the code of its release of x11vnc with the
24OpenSSL project's "OpenSSL" library (or with modified versions of it
25that use the same license as the "OpenSSL" library), and distribute
26the linked executables.  You must obey the GNU General Public License
27in all respects for all of the code used other than "OpenSSL".  If you
28modify this file, you may extend this exception to your version of the
29file, but you are not obligated to do so.  If you do not wish to do
30so, delete this exception statement from your version.
31*/
32
33/* -- selection.c -- */
34
35#include "x11vnc.h"
36#include "cleanup.h"
37#include "connections.h"
38#include "unixpw.h"
39#include "win_utils.h"
40#include "xwrappers.h"
41
42/*
43 * Selection/Cutbuffer/Clipboard handlers.
44 */
45
46int own_primary = 0;	/* whether we currently own PRIMARY or not */
47int set_primary = 1;
48int own_clipboard = 0;	/* whether we currently own CLIPBOARD or not */
49int set_clipboard = 1;
50int set_cutbuffer = 0;	/* to avoid bouncing the CutText right back */
51int sel_waittime = 15;	/* some seconds to skip before first send */
52Window selwin = None;	/* special window for our selection */
53Atom clipboard_atom = None;
54
55/*
56 * This is where we keep our selection: the string sent TO us from VNC
57 * clients, and the string sent BY us to requesting X11 clients.
58 */
59char *xcut_str_primary = NULL;
60char *xcut_str_clipboard = NULL;
61
62
63void selection_request(XEvent *ev, char *type);
64int check_sel_direction(char *dir, char *label, char *sel, int len);
65void cutbuffer_send(void);
66void selection_send(XEvent *ev);
67void resend_selection(char *type);
68
69
70/*
71 * Our callbacks instruct us to check for changes in the cutbuffer
72 * and PRIMARY and CLIPBOARD selection on the local X11 display.
73 *
74 * TODO: check if malloc does not cause performance issues (esp. WRT
75 * SelectionNotify handling).
76 */
77static char cutbuffer_str[PROP_MAX+1];
78static char primary_str[PROP_MAX+1];
79static char clipboard_str[PROP_MAX+1];
80static int cutbuffer_len = 0;
81static int primary_len   = 0;
82static int clipboard_len = 0;
83
84/*
85 * An X11 (not VNC) client on the local display has requested the selection
86 * from us (because we are the current owner).
87 *
88 * n.b.: our caller already has the X_LOCK.
89 */
90void selection_request(XEvent *ev, char *type) {
91#if NO_X11
92	RAWFB_RET_VOID
93	if (!ev || !type) {}
94	return;
95#else
96	XSelectionEvent notify_event;
97	XSelectionRequestEvent *req_event;
98	XErrorHandler old_handler;
99	char *str;
100	unsigned int length;
101	unsigned char *data;
102	static Atom xa_targets = None;
103	static int sync_it = -1;
104# ifndef XA_LENGTH
105	unsigned long XA_LENGTH;
106# endif
107	RAWFB_RET_VOID
108
109# ifndef XA_LENGTH
110	XA_LENGTH = XInternAtom(dpy, "LENGTH", True);
111# endif
112
113	if (sync_it < 0) {
114		if (getenv("X11VNC_SENDEVENT_SYNC")) {
115			sync_it = 1;
116		} else {
117			sync_it = 0;
118		}
119	}
120
121	req_event = &(ev->xselectionrequest);
122	notify_event.type 	= SelectionNotify;
123	notify_event.display	= req_event->display;
124	notify_event.requestor	= req_event->requestor;
125	notify_event.selection	= req_event->selection;
126	notify_event.target	= req_event->target;
127	notify_event.time	= req_event->time;
128
129	if (req_event->property == None) {
130		notify_event.property = req_event->target;
131	} else {
132		notify_event.property = req_event->property;
133	}
134
135	if (!strcmp(type, "PRIMARY")) {
136		str = xcut_str_primary;
137	} else if (!strcmp(type, "CLIPBOARD")) {
138		str = xcut_str_clipboard;
139	} else {
140		return;
141	}
142	if (str) {
143		length = strlen(str);
144	} else {
145		length = 0;
146	}
147	if (debug_sel) {
148		rfbLog("%s\trequest event:   owner=0x%x requestor=0x%x sel=%03d targ=%d prop=%d\n",
149			type, req_event->owner, req_event->requestor, req_event->selection,
150			req_event->target, req_event->property);
151	}
152
153	if (xa_targets == None) {
154		xa_targets = XInternAtom(dpy, "TARGETS", False);
155	}
156
157	/* the window may have gone away, so trap errors */
158	trapped_xerror = 0;
159	old_handler = XSetErrorHandler(trap_xerror);
160
161	if (ev->xselectionrequest.target == XA_LENGTH) {
162		/* length request */
163		int ret;
164		long llength = (long) length;
165
166		ret = XChangeProperty(ev->xselectionrequest.display,
167		    ev->xselectionrequest.requestor,
168		    ev->xselectionrequest.property,
169		    ev->xselectionrequest.target, 32, PropModeReplace,
170		    (unsigned char *) &llength, 1);	/* had sizeof(unsigned int) = 4 before... */
171		if (debug_sel) {
172			rfbLog("LENGTH: XChangeProperty() -> %d\n", ret);
173		}
174
175	} else if (xa_targets != None && ev->xselectionrequest.target == xa_targets) {
176		/* targets request */
177		int ret;
178		Atom targets[2];
179		targets[0] = (Atom) xa_targets;
180		targets[1] = (Atom) XA_STRING;
181
182		ret = XChangeProperty(ev->xselectionrequest.display,
183		    ev->xselectionrequest.requestor,
184		    ev->xselectionrequest.property,
185		    ev->xselectionrequest.target, 32, PropModeReplace,
186		    (unsigned char *) targets, 2);
187		if (debug_sel) {
188			rfbLog("TARGETS: XChangeProperty() -> %d -- sz1: %d  sz2: %d\n",
189			    ret, sizeof(targets[0]), sizeof(targets)/sizeof(targets[0]));
190		}
191
192	} else {
193		/* data request */
194		int ret;
195
196		data = (unsigned char *)str;
197
198		ret = XChangeProperty(ev->xselectionrequest.display,
199		    ev->xselectionrequest.requestor,
200		    ev->xselectionrequest.property,
201		    ev->xselectionrequest.target, 8, PropModeReplace,
202		    data, length);
203		if (debug_sel) {
204			rfbLog("DATA: XChangeProperty() -> %d\n", ret);
205		}
206	}
207
208	if (! trapped_xerror) {
209		int ret = -2, skip_it = 0, ms = 0;
210		double now = dnow();
211		static double last_check = 0.0;
212
213		if (now > last_check + 0.2) {
214			XFlush_wr(dpy);
215			if (!valid_window(req_event->requestor , NULL, 1)) {
216				sync_it = 1;
217				skip_it = 1;
218				if (debug_sel) {
219					rfbLog("selection_request: not a valid window: 0x%x\n",
220					    req_event->requestor);
221				}
222				ms = 10;
223			}
224			if (trapped_xerror) {
225				sync_it = 1;
226				skip_it = 1;
227			}
228			last_check = dnow();
229		}
230
231		if (!skip_it) {
232			ret = XSendEvent(req_event->display, req_event->requestor, False, 0,
233			    (XEvent *)&notify_event);
234		}
235		if (debug_sel) {
236			rfbLog("XSendEvent() -> %d\n", ret);
237		}
238		if (ms > 0) {
239			usleep(ms * 1000);
240		}
241	}
242	if (trapped_xerror) {
243		rfbLog("selection_request: ignored XError while sending "
244		    "%s selection to 0x%x.\n", type, req_event->requestor);
245	}
246
247	XFlush_wr(dpy);
248	if (sync_it) {
249		usleep(10 * 1000);
250		XSync(dpy, False);
251	}
252
253	XSetErrorHandler(old_handler);
254	trapped_xerror = 0;
255
256#endif	/* NO_X11 */
257}
258
259int check_sel_direction(char *dir, char *label, char *sel, int len) {
260	int db = 0, ok = 1;
261	if (debug_sel) {
262		db = 1;
263	}
264	if (sel_direction) {
265		if (strstr(sel_direction, "debug")) {
266			db = 1;
267		}
268		if (strcmp(sel_direction, "debug")) {
269			if (strstr(sel_direction, dir) == NULL) {
270				ok = 0;
271			}
272		}
273	}
274	if (db) {
275		char str[40];
276		int n = 40;
277		strncpy(str, sel, n);
278		str[n-1] = '\0';
279		if (len < n) {
280			str[len] = '\0';
281		}
282		rfbLog("%s: '%s'\n", label, str);
283		if (ok) {
284			rfbLog("%s: %s-ing it.\n", label, dir);
285		} else {
286			rfbLog("%s: NOT %s-ing it.\n", label, dir);
287		}
288	}
289	return ok;
290}
291
292/*
293 * CUT_BUFFER0 property on the local display has changed, we read and
294 * store it and send it out to any connected VNC clients.
295 *
296 * n.b.: our caller already has the X_LOCK.
297 */
298void cutbuffer_send(void) {
299#if NO_X11
300	RAWFB_RET_VOID
301	return;
302#else
303	Atom type;
304	int format, slen, dlen, len;
305	unsigned long nitems = 0, bytes_after = 0;
306	unsigned char* data = NULL;
307
308	cutbuffer_str[0] = '\0';
309	slen = 0;
310
311	RAWFB_RET_VOID
312
313	/* read the property value into cutbuffer_str: */
314	do {
315		if (XGetWindowProperty(dpy, DefaultRootWindow(dpy),
316		    XA_CUT_BUFFER0, nitems/4, PROP_MAX/16, False,
317		    AnyPropertyType, &type, &format, &nitems, &bytes_after,
318		    &data) == Success) {
319
320			dlen = nitems * (format/8);
321			if (slen + dlen > PROP_MAX) {
322				/* too big */
323				rfbLog("warning: truncating large CUT_BUFFER0"
324				   " selection > %d bytes.\n", PROP_MAX);
325				XFree_wr(data);
326				break;
327			}
328			memcpy(cutbuffer_str+slen, data, dlen);
329			slen += dlen;
330			cutbuffer_str[slen] = '\0';
331			XFree_wr(data);
332		}
333	} while (bytes_after > 0);
334
335	cutbuffer_str[PROP_MAX] = '\0';
336
337	if (debug_sel) {
338		rfbLog("cutbuffer_send: '%s'\n", cutbuffer_str);
339	}
340
341	if (! all_clients_initialized()) {
342		rfbLog("cutbuffer_send: no send: uninitialized clients\n");
343		return; /* some clients initializing, cannot send */
344	}
345	if (unixpw_in_progress) {
346		return;
347	}
348
349	/* now send it to any connected VNC clients (rfbServerCutText) */
350	if (!screen) {
351		return;
352	}
353	cutbuffer_len = len = strlen(cutbuffer_str);
354	if (check_sel_direction("send", "cutbuffer_send", cutbuffer_str, len)) {
355		rfbSendServerCutText(screen, cutbuffer_str, len);
356	}
357#endif	/* NO_X11 */
358}
359
360/*
361 * "callback" for our SelectionNotify polling.  We try to determine if
362 * the PRIMARY selection has changed (checking length and first CHKSZ bytes)
363 * and if it has we store it and send it off to any connected VNC clients.
364 *
365 * n.b.: our caller already has the X_LOCK.
366 *
367 * TODO: if we were willing to use libXt, we could perhaps get selection
368 * timestamps to speed up the checking... XtGetSelectionValue().
369 *
370 * Also: XFIXES has XFixesSelectSelectionInput().
371 */
372#define CHKSZ 32
373
374void selection_send(XEvent *ev) {
375#if NO_X11
376	RAWFB_RET_VOID
377	if (!ev) {}
378	return;
379#else
380	Atom type;
381	int format, slen, dlen, oldlen, newlen, toobig = 0, len;
382	static int err = 0, sent_one = 0;
383	char before[CHKSZ], after[CHKSZ];
384	unsigned long nitems = 0, bytes_after = 0;
385	unsigned char* data = NULL;
386	char *selection_str;
387
388	RAWFB_RET_VOID
389	/*
390	 * remember info about our last value of PRIMARY (or CUT_BUFFER0)
391	 * so we can check for any changes below.
392	 */
393	if (ev->xselection.selection == XA_PRIMARY) {
394		if (! watch_primary) {
395			return;
396		}
397		selection_str = primary_str;
398		if (debug_sel) {
399			rfbLog("selection_send: event PRIMARY   prop: %d  requestor: 0x%x  atom: %d\n",
400			    ev->xselection.property, ev->xselection.requestor, ev->xselection.selection);
401		}
402	} else if (clipboard_atom && ev->xselection.selection == clipboard_atom)  {
403		if (! watch_clipboard) {
404			return;
405		}
406		selection_str = clipboard_str;
407		if (debug_sel) {
408			rfbLog("selection_send: event CLIPBOARD prop: %d  requestor: 0x%x atom: %d\n",
409			    ev->xselection.property, ev->xselection.requestor, ev->xselection.selection);
410		}
411	} else {
412		return;
413	}
414
415	oldlen = strlen(selection_str);
416	strncpy(before, selection_str, CHKSZ);
417
418	selection_str[0] = '\0';
419	slen = 0;
420
421	/* read in the current value of PRIMARY or CLIPBOARD: */
422	do {
423		if (XGetWindowProperty(dpy, ev->xselection.requestor,
424		    ev->xselection.property, nitems/4, PROP_MAX/16, True,
425		    AnyPropertyType, &type, &format, &nitems, &bytes_after,
426		    &data) == Success) {
427
428			dlen = nitems * (format/8);
429			if (slen + dlen > PROP_MAX) {
430				/* too big */
431				toobig = 1;
432				XFree_wr(data);
433				if (err) {	/* cut down on messages */
434					break;
435				} else {
436					err = 5;
437				}
438				rfbLog("warning: truncating large PRIMARY"
439				    "/CLIPBOARD selection > %d bytes.\n",
440				    PROP_MAX);
441				break;
442			}
443if (debug_sel) fprintf(stderr, "selection_send: data: '%s' dlen: %d nitems: %lu ba: %lu\n", data, dlen, nitems, bytes_after);
444			memcpy(selection_str+slen, data, dlen);
445			slen += dlen;
446			selection_str[slen] = '\0';
447			XFree_wr(data);
448		}
449	} while (bytes_after > 0);
450
451	if (! toobig) {
452		err = 0;
453	} else if (err) {
454		err--;
455	}
456
457	if (! sent_one) {
458		/* try to force a send first time in */
459		oldlen = -1;
460		sent_one = 1;
461	}
462	if (debug_sel) {
463		rfbLog("selection_send:  %s '%s'\n",
464		    ev->xselection.selection == XA_PRIMARY ? "PRIMARY  " : "CLIPBOARD",
465		    selection_str);
466	}
467
468	/* look for changes in the new value */
469	newlen = strlen(selection_str);
470	strncpy(after, selection_str, CHKSZ);
471
472	if (oldlen == newlen && strncmp(before, after, CHKSZ) == 0) {
473		/* evidently no change */
474		if (debug_sel) {
475			rfbLog("selection_send:  no change.\n");
476		}
477		return;
478	}
479	if (newlen == 0) {
480		/* do not bother sending a null string out */
481		return;
482	}
483
484	if (! all_clients_initialized()) {
485		rfbLog("selection_send: no send: uninitialized clients\n");
486		return; /* some clients initializing, cannot send */
487	}
488
489	if (unixpw_in_progress) {
490		return;
491	}
492
493	/* now send it to any connected VNC clients (rfbServerCutText) */
494	if (!screen) {
495		return;
496	}
497
498	len = newlen;
499	if (ev->xselection.selection == XA_PRIMARY) {
500		primary_len = len;
501	} else if (clipboard_atom && ev->xselection.selection == clipboard_atom)  {
502		clipboard_len = len;
503	}
504	if (check_sel_direction("send", "selection_send", selection_str, len)) {
505		rfbSendServerCutText(screen, selection_str, len);
506	}
507#endif	/* NO_X11 */
508}
509
510void resend_selection(char *type) {
511#if NO_X11
512	RAWFB_RET_VOID
513	if (!type) {}
514	return;
515#else
516	char *selection_str = "";
517	int len = 0;
518
519	RAWFB_RET_VOID
520
521	if (! all_clients_initialized()) {
522		rfbLog("selection_send: no send: uninitialized clients\n");
523		return; /* some clients initializing, cannot send */
524	}
525	if (unixpw_in_progress) {
526		return;
527	}
528	if (!screen) {
529		return;
530	}
531
532	if (!strcmp(type, "cutbuffer")) {
533		selection_str = cutbuffer_str;
534		len = cutbuffer_len;
535	} else if (!strcmp(type, "clipboard")) {
536		selection_str = clipboard_str;
537		len = clipboard_len;
538	} else if (!strcmp(type, "primary")) {
539		selection_str = primary_str;
540		len = primary_len;
541	}
542	if (check_sel_direction("send", "selection_send", selection_str, len)) {
543		rfbSendServerCutText(screen, selection_str, len);
544	}
545#endif	/* NO_X11 */
546}
547
548
549