1 /*
2 Copyright (C) 2002-2010 Karl J. Runge <runge@karlrunge.com>
3 All rights reserved.
4
5 This file is part of x11vnc.
6
7 x11vnc is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or (at
10 your option) any later version.
11
12 x11vnc is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with x11vnc; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
20 or see <http://www.gnu.org/licenses/>.
21
22 In addition, as a special exception, Karl J. Runge
23 gives permission to link the code of its release of x11vnc with the
24 OpenSSL project's "OpenSSL" library (or with modified versions of it
25 that use the same license as the "OpenSSL" library), and distribute
26 the linked executables. You must obey the GNU General Public License
27 in all respects for all of the code used other than "OpenSSL". If you
28 modify this file, you may extend this exception to your version of the
29 file, but you are not obligated to do so. If you do not wish to do
30 so, 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
46 int own_primary = 0; /* whether we currently own PRIMARY or not */
47 int set_primary = 1;
48 int own_clipboard = 0; /* whether we currently own CLIPBOARD or not */
49 int set_clipboard = 1;
50 int set_cutbuffer = 0; /* to avoid bouncing the CutText right back */
51 int sel_waittime = 15; /* some seconds to skip before first send */
52 Window selwin = None; /* special window for our selection */
53 Atom 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 */
59 char *xcut_str_primary = NULL;
60 char *xcut_str_clipboard = NULL;
61
62
63 void selection_request(XEvent *ev, char *type);
64 int check_sel_direction(char *dir, char *label, char *sel, int len);
65 void cutbuffer_send(void);
66 void selection_send(XEvent *ev);
67 void 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 */
77 static char cutbuffer_str[PROP_MAX+1];
78 static char primary_str[PROP_MAX+1];
79 static char clipboard_str[PROP_MAX+1];
80 static int cutbuffer_len = 0;
81 static int primary_len = 0;
82 static 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 */
selection_request(XEvent * ev,char * type)90 void 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 *)¬ify_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
check_sel_direction(char * dir,char * label,char * sel,int len)259 int 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 */
cutbuffer_send(void)298 void 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
selection_send(XEvent * ev)374 void 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 }
443 if (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
resend_selection(char * type)510 void 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