build-sys: remove prefix from filenames
[vd_agent/hramrach.git] / src / vdagent / x11.c
blobe88a8ea4f87a8e5467884bddd7d35d85e3abc58e
1 /* vdagent-x11.c vdagent x11 code
3 Copyright 2010-2011 Red Hat, Inc.
5 Red Hat Authors:
6 Hans de Goede <hdegoede@redhat.com>
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 /* Note: Our event loop is only called when there is data to be read from the
23 X11 socket. If events have arrived and have already been read by libX11 from
24 the socket triggered by other libX11 calls from this file, the select for
25 read in the main loop, won't see these and our event loop won't get called!
27 Thus we must make sure that all queued events have been consumed, whenever
28 we return to the main loop. IOW all (externally callable) functions in this
29 file must end with calling XPending and consuming all queued events.
31 Calling XPending when-ever we return to the mainloop also ensures any
32 pending writes are flushed. */
34 #include <glib.h>
35 #include <stdlib.h>
36 #include <limits.h>
37 #include <string.h>
38 #include <syslog.h>
39 #include <assert.h>
40 #include <unistd.h>
41 #include <X11/Xatom.h>
42 #include <X11/Xlib.h>
43 #include <X11/extensions/Xfixes.h>
44 #include "vdagentd-proto.h"
45 #include "x11.h"
46 #include "x11-priv.h"
48 /* Stupid X11 API, there goes our encapsulate all data in a struct design */
49 int (*vdagent_x11_prev_error_handler)(Display *, XErrorEvent *);
50 int vdagent_x11_caught_error;
52 static void vdagent_x11_handle_selection_notify(struct vdagent_x11 *x11,
53 XEvent *event, int incr);
54 static void vdagent_x11_handle_selection_request(struct vdagent_x11 *x11);
55 static void vdagent_x11_handle_targets_notify(struct vdagent_x11 *x11,
56 XEvent *event);
57 static void vdagent_x11_handle_property_delete_notify(struct vdagent_x11 *x11,
58 XEvent *del_event);
59 static void vdagent_x11_send_selection_notify(struct vdagent_x11 *x11,
60 Atom prop, struct vdagent_x11_selection_request *request);
61 static void vdagent_x11_set_clipboard_owner(struct vdagent_x11 *x11,
62 uint8_t selection, int new_owner);
64 static const char *vdagent_x11_sel_to_str(uint8_t selection) {
65 switch (selection) {
66 case VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD:
67 return "clipboard";
68 case VD_AGENT_CLIPBOARD_SELECTION_PRIMARY:
69 return "primary";
70 case VD_AGENT_CLIPBOARD_SELECTION_SECONDARY:
71 return "secondary";
72 default:
73 return "unknown";
77 static int vdagent_x11_debug_error_handler(
78 Display *display, XErrorEvent *error)
80 abort();
83 /* With the clipboard we're sometimes dealing with Properties on another apps
84 Window. which can go away at any time. */
85 static int vdagent_x11_ignore_bad_window_handler(
86 Display *display, XErrorEvent *error)
88 if (error->error_code == BadWindow)
89 return 0;
91 return vdagent_x11_prev_error_handler(display, error);
94 void vdagent_x11_set_error_handler(struct vdagent_x11 *x11,
95 int (*handler)(Display *, XErrorEvent *))
97 XSync(x11->display, False);
98 vdagent_x11_caught_error = 0;
99 vdagent_x11_prev_error_handler = XSetErrorHandler(handler);
102 int vdagent_x11_restore_error_handler(struct vdagent_x11 *x11)
104 int error;
106 XSync(x11->display, False);
107 XSetErrorHandler(vdagent_x11_prev_error_handler);
108 error = vdagent_x11_caught_error;
109 vdagent_x11_caught_error = 0;
111 return error;
114 static void vdagent_x11_get_wm_name(struct vdagent_x11 *x11)
116 Atom type_ret;
117 int format_ret;
118 unsigned long len, remain;
119 unsigned char *data = NULL;
120 Window sup_window = None;
122 /* XGetWindowProperty can throw a BadWindow error. One way we can trigger
123 this is when the display-manager (ie gdm) has set, and not cleared the
124 _NET_SUPPORTING_WM_CHECK property, and the window manager running in
125 the user session has not yet updated it to point to its window, so its
126 pointing to a non existing window. */
127 vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler);
129 /* Get the window manager SUPPORTING_WM_CHECK window */
130 if (XGetWindowProperty(x11->display, x11->root_window[0],
131 XInternAtom(x11->display, "_NET_SUPPORTING_WM_CHECK", False), 0,
132 LONG_MAX, False, XA_WINDOW, &type_ret, &format_ret, &len,
133 &remain, &data) == Success) {
134 if (type_ret == XA_WINDOW)
135 sup_window = *((Window *)data);
136 XFree(data);
138 if (sup_window == None &&
139 XGetWindowProperty(x11->display, x11->root_window[0],
140 XInternAtom(x11->display, "_WIN_SUPPORTING_WM_CHECK", False), 0,
141 LONG_MAX, False, XA_CARDINAL, &type_ret, &format_ret, &len,
142 &remain, &data) == Success) {
143 if (type_ret == XA_CARDINAL)
144 sup_window = *((Window *)data);
145 XFree(data);
147 /* So that we can get the net_wm_name */
148 if (sup_window != None) {
149 Atom utf8 = XInternAtom(x11->display, "UTF8_STRING", False);
150 if (XGetWindowProperty(x11->display, sup_window,
151 XInternAtom(x11->display, "_NET_WM_NAME", False), 0,
152 LONG_MAX, False, utf8, &type_ret, &format_ret, &len,
153 &remain, &data) == Success) {
154 if (type_ret == utf8) {
155 x11->net_wm_name =
156 g_strndup((char *)data, (format_ret / 8) * len);
158 XFree(data);
160 if (x11->net_wm_name == NULL &&
161 XGetWindowProperty(x11->display, sup_window,
162 XInternAtom(x11->display, "_NET_WM_NAME", False), 0,
163 LONG_MAX, False, XA_STRING, &type_ret, &format_ret, &len,
164 &remain, &data) == Success) {
165 if (type_ret == XA_STRING) {
166 x11->net_wm_name =
167 g_strndup((char *)data, (format_ret / 8) * len);
169 XFree(data);
173 vdagent_x11_restore_error_handler(x11);
176 struct vdagent_x11 *vdagent_x11_create(struct udscs_connection *vdagentd,
177 int debug, int sync)
179 struct vdagent_x11 *x11;
180 XWindowAttributes attrib;
181 int i, j, major, minor;
183 x11 = calloc(1, sizeof(*x11));
184 if (!x11) {
185 syslog(LOG_ERR, "out of memory allocating vdagent_x11 struct");
186 return NULL;
189 x11->vdagentd = vdagentd;
190 x11->debug = debug;
192 x11->display = XOpenDisplay(NULL);
193 if (!x11->display) {
194 syslog(LOG_ERR, "could not connect to X-server");
195 free(x11);
196 return NULL;
199 x11->screen_count = ScreenCount(x11->display);
200 if (x11->screen_count > MAX_SCREENS) {
201 syslog(LOG_ERR, "Error too much screens: %d > %d",
202 x11->screen_count, MAX_SCREENS);
203 XCloseDisplay(x11->display);
204 free(x11);
205 return NULL;
208 if (sync) {
209 XSetErrorHandler(vdagent_x11_debug_error_handler);
210 XSynchronize(x11->display, True);
213 for (i = 0; i < x11->screen_count; i++)
214 x11->root_window[i] = RootWindow(x11->display, i);
215 x11->fd = ConnectionNumber(x11->display);
216 x11->clipboard_atom = XInternAtom(x11->display, "CLIPBOARD", False);
217 x11->clipboard_primary_atom = XInternAtom(x11->display, "PRIMARY", False);
218 x11->targets_atom = XInternAtom(x11->display, "TARGETS", False);
219 x11->incr_atom = XInternAtom(x11->display, "INCR", False);
220 x11->multiple_atom = XInternAtom(x11->display, "MULTIPLE", False);
221 x11->timestamp_atom = XInternAtom(x11->display, "TIMESTAMP", False);
222 for(i = 0; i < clipboard_format_count; i++) {
223 x11->clipboard_formats[i].type = clipboard_format_templates[i].type;
224 for(j = 0; clipboard_format_templates[i].atom_names[j]; j++) {
225 x11->clipboard_formats[i].atoms[j] =
226 XInternAtom(x11->display,
227 clipboard_format_templates[i].atom_names[j],
228 False);
230 x11->clipboard_formats[i].atom_count = j;
233 /* We should not store properties (for selections) on the root window */
234 x11->selection_window = XCreateSimpleWindow(x11->display, x11->root_window[0],
235 0, 0, 1, 1, 0, 0, 0);
236 if (x11->debug)
237 syslog(LOG_DEBUG, "Selection window: %u", (int)x11->selection_window);
239 vdagent_x11_randr_init(x11);
241 if (XFixesQueryExtension(x11->display, &x11->xfixes_event_base, &i) &&
242 XFixesQueryVersion(x11->display, &major, &minor) && major >= 1) {
243 x11->has_xfixes = 1;
244 XFixesSelectSelectionInput(x11->display, x11->root_window[0],
245 x11->clipboard_atom,
246 XFixesSetSelectionOwnerNotifyMask|
247 XFixesSelectionWindowDestroyNotifyMask|
248 XFixesSelectionClientCloseNotifyMask);
249 XFixesSelectSelectionInput(x11->display, x11->root_window[0],
250 x11->clipboard_primary_atom,
251 XFixesSetSelectionOwnerNotifyMask|
252 XFixesSelectionWindowDestroyNotifyMask|
253 XFixesSelectionClientCloseNotifyMask);
254 } else
255 syslog(LOG_ERR, "no xfixes, no guest -> client copy paste support");
257 x11->max_prop_size = XExtendedMaxRequestSize(x11->display);
258 if (x11->max_prop_size) {
259 x11->max_prop_size -= 100;
260 } else {
261 x11->max_prop_size = XMaxRequestSize(x11->display) - 100;
263 /* Be a good X11 citizen and maximize the amount of data we send at once */
264 if (x11->max_prop_size > 262144)
265 x11->max_prop_size = 262144;
267 for (i = 0; i < x11->screen_count; i++) {
268 /* Catch resolution changes */
269 XSelectInput(x11->display, x11->root_window[i], StructureNotifyMask);
271 /* Get the current resolution */
272 XGetWindowAttributes(x11->display, x11->root_window[i], &attrib);
273 x11->width[i] = attrib.width;
274 x11->height[i] = attrib.height;
276 vdagent_x11_send_daemon_guest_xorg_res(x11, 1);
278 /* Get net_wm_name, since we are started at the same time as the wm,
279 sometimes we need to wait a bit for it to show up. */
280 i = 10;
281 vdagent_x11_get_wm_name(x11);
282 while (x11->net_wm_name == NULL && --i > 0) {
283 usleep(100000);
284 vdagent_x11_get_wm_name(x11);
286 if (x11->debug && x11->net_wm_name)
287 syslog(LOG_DEBUG, "net_wm_name: \"%s\", has icons: %d",
288 x11->net_wm_name, vdagent_x11_has_icons_on_desktop(x11));
290 /* Flush output buffers and consume any pending events */
291 vdagent_x11_do_read(x11);
293 return x11;
296 void vdagent_x11_destroy(struct vdagent_x11 *x11, int vdagentd_disconnected)
298 uint8_t sel;
300 if (!x11)
301 return;
303 if (vdagentd_disconnected)
304 x11->vdagentd = NULL;
306 for (sel = 0; sel < VD_AGENT_CLIPBOARD_SELECTION_SECONDARY; ++sel) {
307 vdagent_x11_set_clipboard_owner(x11, sel, owner_none);
310 XCloseDisplay(x11->display);
311 g_free(x11->net_wm_name);
312 free(x11->randr.failed_conf);
313 free(x11);
316 int vdagent_x11_get_fd(struct vdagent_x11 *x11)
318 return x11->fd;
321 static void vdagent_x11_next_selection_request(struct vdagent_x11 *x11)
323 struct vdagent_x11_selection_request *selection_request;
324 selection_request = x11->selection_req;
325 x11->selection_req = selection_request->next;
326 free(selection_request);
329 static void vdagent_x11_next_conversion_request(struct vdagent_x11 *x11)
331 struct vdagent_x11_conversion_request *conversion_req;
332 conversion_req = x11->conversion_req;
333 x11->conversion_req = conversion_req->next;
334 free(conversion_req);
337 static void vdagent_x11_set_clipboard_owner(struct vdagent_x11 *x11,
338 uint8_t selection, int new_owner)
340 struct vdagent_x11_selection_request *prev_sel, *curr_sel, *next_sel;
341 struct vdagent_x11_conversion_request *prev_conv, *curr_conv, *next_conv;
342 int once;
344 /* Clear pending requests and clipboard data */
345 once = 1;
346 prev_sel = NULL;
347 next_sel = x11->selection_req;
348 while (next_sel) {
349 curr_sel = next_sel;
350 next_sel = curr_sel->next;
351 if (curr_sel->selection == selection) {
352 if (once) {
353 SELPRINTF("selection requests pending on clipboard ownership "
354 "change, clearing");
355 once = 0;
357 vdagent_x11_send_selection_notify(x11, None, curr_sel);
358 if (curr_sel == x11->selection_req) {
359 x11->selection_req = next_sel;
360 free(x11->selection_req_data);
361 x11->selection_req_data = NULL;
362 x11->selection_req_data_pos = 0;
363 x11->selection_req_data_size = 0;
364 x11->selection_req_atom = None;
365 } else {
366 prev_sel->next = next_sel;
368 free(curr_sel);
369 } else {
370 prev_sel = curr_sel;
374 once = 1;
375 prev_conv = NULL;
376 next_conv = x11->conversion_req;
377 while (next_conv) {
378 curr_conv = next_conv;
379 next_conv = curr_conv->next;
380 if (curr_conv->selection == selection) {
381 if (once) {
382 SELPRINTF("client clipboard request pending on clipboard "
383 "ownership change, clearing");
384 once = 0;
386 if (x11->vdagentd)
387 udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_DATA, selection,
388 VD_AGENT_CLIPBOARD_NONE, NULL, 0);
389 if (curr_conv == x11->conversion_req) {
390 x11->conversion_req = next_conv;
391 x11->clipboard_data_size = 0;
392 x11->expect_property_notify = 0;
393 } else {
394 prev_conv->next = next_conv;
396 free(curr_conv);
397 } else {
398 prev_conv = curr_conv;
402 if (new_owner == owner_none) {
403 /* When going from owner_guest to owner_none we need to send a
404 clipboard release message to the client */
405 if (x11->clipboard_owner[selection] == owner_guest && x11->vdagentd) {
406 udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_RELEASE, selection,
407 0, NULL, 0);
409 x11->clipboard_type_count[selection] = 0;
411 x11->clipboard_owner[selection] = new_owner;
414 static int vdagent_x11_get_clipboard_atom(struct vdagent_x11 *x11, uint8_t selection, Atom* clipboard)
416 if (selection == VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD) {
417 *clipboard = x11->clipboard_atom;
418 } else if (selection == VD_AGENT_CLIPBOARD_SELECTION_PRIMARY) {
419 *clipboard = x11->clipboard_primary_atom;
420 } else {
421 syslog(LOG_ERR, "get_clipboard_atom: unknown selection");
422 return -1;
425 return 0;
428 static int vdagent_x11_get_clipboard_selection(struct vdagent_x11 *x11,
429 XEvent *event, uint8_t *selection)
431 Atom atom;
433 if (event->type == x11->xfixes_event_base) {
434 XFixesSelectionNotifyEvent *xfev = (XFixesSelectionNotifyEvent *)event;
435 atom = xfev->selection;
436 } else if (event->type == SelectionNotify) {
437 atom = event->xselection.selection;
438 } else if (event->type == SelectionRequest) {
439 atom = event->xselectionrequest.selection;
440 } else {
441 syslog(LOG_ERR, "get_clipboard_selection: unknown event type");
442 return -1;
445 if (atom == x11->clipboard_atom) {
446 *selection = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
447 } else if (atom == x11->clipboard_primary_atom) {
448 *selection = VD_AGENT_CLIPBOARD_SELECTION_PRIMARY;
449 } else {
450 syslog(LOG_ERR, "get_clipboard_selection: unknown selection");
451 return -1;
454 return 0;
457 static void vdagent_x11_handle_event(struct vdagent_x11 *x11, XEvent event)
459 int i, handled = 0;
460 uint8_t selection;
462 if (event.type == x11->xfixes_event_base) {
463 union {
464 XEvent ev;
465 XFixesSelectionNotifyEvent xfev;
466 } ev;
468 if (vdagent_x11_get_clipboard_selection(x11, &event, &selection)) {
469 return;
472 ev.ev = event;
473 switch (ev.xfev.subtype) {
474 case XFixesSetSelectionOwnerNotify:
475 break;
476 /* Treat ... as a SelectionOwnerNotify None */
477 case XFixesSelectionWindowDestroyNotify:
478 case XFixesSelectionClientCloseNotify:
479 ev.xfev.owner = None;
480 break;
481 default:
482 VSELPRINTF("unexpected xfix event subtype %d window %d",
483 (int)ev.xfev.subtype, (int)event.xany.window);
484 return;
486 VSELPRINTF("New selection owner: %u", (unsigned int)ev.xfev.owner);
488 /* Ignore becoming the owner ourselves */
489 if (ev.xfev.owner == x11->selection_window)
490 return;
492 /* If the clipboard owner is changed we no longer own it */
493 vdagent_x11_set_clipboard_owner(x11, selection, owner_none);
495 if (ev.xfev.owner == None)
496 return;
498 /* Request the supported targets from the new owner */
499 XConvertSelection(x11->display, ev.xfev.selection, x11->targets_atom,
500 x11->targets_atom, x11->selection_window,
501 CurrentTime);
502 x11->expected_targets_notifies[selection]++;
503 return;
506 if (vdagent_x11_randr_handle_event(x11, event))
507 return;
509 switch (event.type) {
510 case ConfigureNotify:
511 for (i = 0; i < x11->screen_count; i++)
512 if (event.xconfigure.window == x11->root_window[i])
513 break;
514 if (i == x11->screen_count)
515 break;
517 handled = 1;
518 vdagent_x11_randr_handle_root_size_change(x11, i,
519 event.xconfigure.width, event.xconfigure.height);
520 break;
521 case MappingNotify:
522 /* These are uninteresting */
523 handled = 1;
524 break;
525 case SelectionNotify:
526 if (event.xselection.target == x11->targets_atom)
527 vdagent_x11_handle_targets_notify(x11, &event);
528 else
529 vdagent_x11_handle_selection_notify(x11, &event, 0);
531 handled = 1;
532 break;
533 case PropertyNotify:
534 if (x11->expect_property_notify &&
535 event.xproperty.state == PropertyNewValue) {
536 vdagent_x11_handle_selection_notify(x11, &event, 1);
538 if (x11->selection_req_data &&
539 event.xproperty.state == PropertyDelete) {
540 vdagent_x11_handle_property_delete_notify(x11, &event);
542 /* Always mark as handled, since we cannot unselect input for property
543 notifications once we are done with handling the incr transfer. */
544 handled = 1;
545 break;
546 case SelectionClear:
547 /* Do nothing the clipboard ownership will get updated through
548 the XFixesSetSelectionOwnerNotify event */
549 handled = 1;
550 break;
551 case SelectionRequest: {
552 struct vdagent_x11_selection_request *req, *new_req;
554 if (vdagent_x11_get_clipboard_selection(x11, &event, &selection)) {
555 return;
558 new_req = malloc(sizeof(*new_req));
559 if (!new_req) {
560 SELPRINTF("out of memory on SelectionRequest, ignoring.");
561 break;
564 handled = 1;
566 new_req->event = event;
567 new_req->selection = selection;
568 new_req->next = NULL;
570 if (!x11->selection_req) {
571 x11->selection_req = new_req;
572 vdagent_x11_handle_selection_request(x11);
573 break;
576 /* maybe we should limit the selection_request stack depth ? */
577 req = x11->selection_req;
578 while (req->next)
579 req = req->next;
581 req->next = new_req;
582 break;
585 if (!handled && x11->debug)
586 syslog(LOG_DEBUG, "unhandled x11 event, type %d, window %d",
587 (int)event.type, (int)event.xany.window);
590 void vdagent_x11_do_read(struct vdagent_x11 *x11)
592 XEvent event;
594 while (XPending(x11->display)) {
595 XNextEvent(x11->display, &event);
596 vdagent_x11_handle_event(x11, event);
600 static const char *vdagent_x11_get_atom_name(struct vdagent_x11 *x11, Atom a)
602 if (a == None)
603 return "None";
605 return XGetAtomName(x11->display, a);
608 static int vdagent_x11_get_selection(struct vdagent_x11 *x11, XEvent *event,
609 uint8_t selection, Atom type, Atom prop, int format,
610 unsigned char **data_ret, int incr)
612 Bool del = incr ? True: False;
613 Atom type_ret;
614 int format_ret, ret_val = -1;
615 unsigned long len, remain;
616 unsigned char *data = NULL;
618 *data_ret = NULL;
620 if (!incr) {
621 if (event->xselection.property == None) {
622 VSELPRINTF("XConvertSelection refused by clipboard owner");
623 goto exit;
626 if (event->xselection.requestor != x11->selection_window ||
627 event->xselection.property != prop) {
628 SELPRINTF("SelectionNotify parameters mismatch");
629 goto exit;
633 if (XGetWindowProperty(x11->display, x11->selection_window, prop, 0,
634 LONG_MAX, del, type, &type_ret, &format_ret, &len,
635 &remain, &data) != Success) {
636 SELPRINTF("XGetWindowProperty failed");
637 goto exit;
640 if (!incr && prop != x11->targets_atom) {
641 if (type_ret == x11->incr_atom) {
642 int prop_min_size = *(uint32_t*)data;
644 if (x11->expect_property_notify) {
645 SELPRINTF("received an incr SelectionNotify while "
646 "still reading another incr property");
647 goto exit;
650 if (x11->clipboard_data_space < prop_min_size) {
651 free(x11->clipboard_data);
652 x11->clipboard_data = malloc(prop_min_size);
653 if (!x11->clipboard_data) {
654 SELPRINTF("out of memory allocating clipboard buffer");
655 x11->clipboard_data_space = 0;
656 goto exit;
658 x11->clipboard_data_space = prop_min_size;
660 x11->expect_property_notify = 1;
661 XSelectInput(x11->display, x11->selection_window,
662 PropertyChangeMask);
663 XDeleteProperty(x11->display, x11->selection_window, prop);
664 XFree(data);
665 return 0; /* Wait for more data */
667 XDeleteProperty(x11->display, x11->selection_window, prop);
670 if (type_ret != type) {
671 SELPRINTF("expected property type: %s, got: %s",
672 vdagent_x11_get_atom_name(x11, type),
673 vdagent_x11_get_atom_name(x11, type_ret));
674 goto exit;
677 if (format_ret != format) {
678 SELPRINTF("expected %d bit format, got %d bits", format, format_ret);
679 goto exit;
682 /* Convert len to bytes */
683 switch(format) {
684 case 8:
685 break;
686 case 16:
687 len *= sizeof(short);
688 break;
689 case 32:
690 len *= sizeof(long);
691 break;
694 if (incr) {
695 if (len) {
696 if (x11->clipboard_data_size + len > x11->clipboard_data_space) {
697 void *old_clipboard_data = x11->clipboard_data;
699 x11->clipboard_data_space = x11->clipboard_data_size + len;
700 x11->clipboard_data = realloc(x11->clipboard_data,
701 x11->clipboard_data_space);
702 if (!x11->clipboard_data) {
703 SELPRINTF("out of memory allocating clipboard buffer");
704 x11->clipboard_data_space = 0;
705 free(old_clipboard_data);
706 goto exit;
709 memcpy(x11->clipboard_data + x11->clipboard_data_size, data, len);
710 x11->clipboard_data_size += len;
711 VSELPRINTF("Appended %ld bytes to buffer", len);
712 XFree(data);
713 return 0; /* Wait for more data */
715 len = x11->clipboard_data_size;
716 *data_ret = x11->clipboard_data;
717 } else
718 *data_ret = data;
720 if (len > 0) {
721 ret_val = len;
722 } else {
723 SELPRINTF("property contains no data (zero length)");
724 *data_ret = NULL;
727 exit:
728 if ((incr || ret_val == -1) && data)
729 XFree(data);
731 if (incr) {
732 x11->clipboard_data_size = 0;
733 x11->expect_property_notify = 0;
736 return ret_val;
739 static void vdagent_x11_get_selection_free(struct vdagent_x11 *x11,
740 unsigned char *data, int incr)
742 if (incr) {
743 /* If the clipboard has grown large return the memory to the system */
744 if (x11->clipboard_data_space > 512 * 1024) {
745 free(x11->clipboard_data);
746 x11->clipboard_data = NULL;
747 x11->clipboard_data_space = 0;
749 } else if (data)
750 XFree(data);
753 static uint32_t vdagent_x11_target_to_type(struct vdagent_x11 *x11,
754 uint8_t selection, Atom target)
756 int i, j;
758 for (i = 0; i < clipboard_format_count; i++) {
759 for (j = 0; j < x11->clipboard_formats[i].atom_count; j++) {
760 if (x11->clipboard_formats[i].atoms[j] == target) {
761 return x11->clipboard_formats[i].type;
766 VSELPRINTF("unexpected selection type %s",
767 vdagent_x11_get_atom_name(x11, target));
768 return VD_AGENT_CLIPBOARD_NONE;
771 static Atom vdagent_x11_type_to_target(struct vdagent_x11 *x11,
772 uint8_t selection, uint32_t type)
774 int i;
776 for (i = 0; i < x11->clipboard_type_count[selection]; i++) {
777 if (x11->clipboard_agent_types[selection][i] == type) {
778 return x11->clipboard_x11_targets[selection][i];
781 SELPRINTF("client requested unavailable type %u", type);
782 return None;
785 static void vdagent_x11_handle_conversion_request(struct vdagent_x11 *x11)
787 Atom clip = None;
789 if (!x11->conversion_req) {
790 return;
793 vdagent_x11_get_clipboard_atom(x11, x11->conversion_req->selection, &clip);
794 XConvertSelection(x11->display, clip, x11->conversion_req->target,
795 clip, x11->selection_window, CurrentTime);
798 static void vdagent_x11_handle_selection_notify(struct vdagent_x11 *x11,
799 XEvent *event, int incr)
801 int len = 0;
802 unsigned char *data = NULL;
803 uint32_t type;
804 uint8_t selection = -1;
805 Atom clip = None;
807 if (!x11->conversion_req) {
808 syslog(LOG_ERR, "SelectionNotify received without a target");
809 return;
811 vdagent_x11_get_clipboard_atom(x11, x11->conversion_req->selection, &clip);
813 if (incr) {
814 if (event->xproperty.atom != clip ||
815 event->xproperty.window != x11->selection_window) {
816 return;
818 } else {
819 if (vdagent_x11_get_clipboard_selection(x11, event, &selection)) {
820 len = -1;
821 } else if (selection != x11->conversion_req->selection) {
822 SELPRINTF("Requested data for selection %d got %d",
823 (int)x11->conversion_req->selection, (int)selection);
824 len = -1;
826 if (event->xselection.target != x11->conversion_req->target &&
827 event->xselection.target != x11->incr_atom) {
828 SELPRINTF("Requested %s target got %s",
829 vdagent_x11_get_atom_name(x11, x11->conversion_req->target),
830 vdagent_x11_get_atom_name(x11, event->xselection.target));
831 len = -1;
835 selection = x11->conversion_req->selection;
836 type = vdagent_x11_target_to_type(x11, selection,
837 x11->conversion_req->target);
838 if (type == VD_AGENT_CLIPBOARD_NONE)
839 SELPRINTF("internal error conversion_req has bad target %s",
840 vdagent_x11_get_atom_name(x11, x11->conversion_req->target));
841 if (len == 0) { /* No errors so far */
842 len = vdagent_x11_get_selection(x11, event, selection,
843 x11->conversion_req->target,
844 clip, 8, &data, incr);
845 if (len == 0) { /* waiting for more data? */
846 return;
849 if (len == -1) {
850 type = VD_AGENT_CLIPBOARD_NONE;
851 len = 0;
854 udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_DATA, selection, type,
855 data, len);
856 vdagent_x11_get_selection_free(x11, data, incr);
858 vdagent_x11_next_conversion_request(x11);
859 vdagent_x11_handle_conversion_request(x11);
862 static Atom atom_lists_overlap(Atom *atoms1, Atom *atoms2, int l1, int l2)
864 int i, j;
866 for (i = 0; i < l1; i++)
867 for (j = 0; j < l2; j++)
868 if (atoms1[i] == atoms2[j])
869 return atoms1[i];
871 return 0;
874 static void vdagent_x11_print_targets(struct vdagent_x11 *x11,
875 uint8_t selection, const char *action, Atom *atoms, int c)
877 int i;
878 VSELPRINTF("%s %d targets:", action, c);
879 for (i = 0; i < c; i++)
880 VSELPRINTF("%s", vdagent_x11_get_atom_name(x11, atoms[i]));
883 static void vdagent_x11_handle_targets_notify(struct vdagent_x11 *x11,
884 XEvent *event)
886 int i, len;
887 Atom atom, *atoms = NULL;
888 uint8_t selection;
889 int *type_count;
891 if (vdagent_x11_get_clipboard_selection(x11, event, &selection)) {
892 return;
895 if (!x11->expected_targets_notifies[selection]) {
896 SELPRINTF("unexpected selection notify TARGETS");
897 return;
900 x11->expected_targets_notifies[selection]--;
902 /* If we have more targets_notifies pending, ignore this one, we
903 are only interested in the targets list of the current owner
904 (which is the last one we've requested a targets list from) */
905 if (x11->expected_targets_notifies[selection]) {
906 return;
909 len = vdagent_x11_get_selection(x11, event, selection,
910 XA_ATOM, x11->targets_atom, 32,
911 (unsigned char **)&atoms, 0);
912 if (len == 0 || len == -1) /* waiting for more data or error? */
913 return;
915 /* bytes -> atoms */
916 len /= sizeof(Atom);
917 vdagent_x11_print_targets(x11, selection, "received", atoms, len);
919 type_count = &x11->clipboard_type_count[selection];
920 *type_count = 0;
921 for (i = 0; i < clipboard_format_count; i++) {
922 atom = atom_lists_overlap(x11->clipboard_formats[i].atoms, atoms,
923 x11->clipboard_formats[i].atom_count, len);
924 if (atom) {
925 x11->clipboard_agent_types[selection][*type_count] =
926 x11->clipboard_formats[i].type;
927 x11->clipboard_x11_targets[selection][*type_count] = atom;
928 (*type_count)++;
929 if (*type_count ==
930 sizeof(x11->clipboard_agent_types[0])/sizeof(uint32_t)) {
931 SELPRINTF("handle_targets_notify: too many types");
932 break;
937 if (*type_count) {
938 udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_GRAB, selection, 0,
939 (uint8_t *)x11->clipboard_agent_types[selection],
940 *type_count * sizeof(uint32_t));
941 vdagent_x11_set_clipboard_owner(x11, selection, owner_guest);
944 vdagent_x11_get_selection_free(x11, (unsigned char *)atoms, 0);
947 static void vdagent_x11_send_selection_notify(struct vdagent_x11 *x11,
948 Atom prop, struct vdagent_x11_selection_request *request)
950 XEvent res, *event;
952 if (request) {
953 event = &request->event;
954 } else {
955 event = &x11->selection_req->event;
958 res.xselection.property = prop;
959 res.xselection.type = SelectionNotify;
960 res.xselection.display = event->xselectionrequest.display;
961 res.xselection.requestor = event->xselectionrequest.requestor;
962 res.xselection.selection = event->xselectionrequest.selection;
963 res.xselection.target = event->xselectionrequest.target;
964 res.xselection.time = event->xselectionrequest.time;
966 vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler);
967 XSendEvent(x11->display, event->xselectionrequest.requestor, 0, 0, &res);
968 vdagent_x11_restore_error_handler(x11);
970 if (!request) {
971 vdagent_x11_next_selection_request(x11);
972 vdagent_x11_handle_selection_request(x11);
976 static void vdagent_x11_send_targets(struct vdagent_x11 *x11,
977 uint8_t selection, XEvent *event)
979 Atom prop, targets[256] = { x11->targets_atom, };
980 int i, j, k, target_count = 1;
982 for (i = 0; i < x11->clipboard_type_count[selection]; i++) {
983 for (j = 0; j < clipboard_format_count; j++) {
984 if (x11->clipboard_formats[j].type !=
985 x11->clipboard_agent_types[selection][i])
986 continue;
988 for (k = 0; k < x11->clipboard_formats[j].atom_count; k++) {
989 targets[target_count] = x11->clipboard_formats[j].atoms[k];
990 target_count++;
991 if (target_count == sizeof(targets)/sizeof(Atom)) {
992 SELPRINTF("send_targets: too many targets");
993 goto exit_loop;
998 exit_loop:
1000 prop = event->xselectionrequest.property;
1001 if (prop == None)
1002 prop = event->xselectionrequest.target;
1004 vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler);
1005 XChangeProperty(x11->display, event->xselectionrequest.requestor, prop,
1006 XA_ATOM, 32, PropModeReplace, (unsigned char *)&targets,
1007 target_count);
1008 if (vdagent_x11_restore_error_handler(x11) == 0) {
1009 vdagent_x11_print_targets(x11, selection, "sent",
1010 targets, target_count);
1011 vdagent_x11_send_selection_notify(x11, prop, NULL);
1012 } else
1013 SELPRINTF("send_targets: Failed to sent, requestor window gone");
1016 static void vdagent_x11_handle_selection_request(struct vdagent_x11 *x11)
1018 XEvent *event;
1019 uint32_t type = VD_AGENT_CLIPBOARD_NONE;
1020 uint8_t selection;
1022 if (!x11->selection_req)
1023 return;
1025 event = &x11->selection_req->event;
1026 selection = x11->selection_req->selection;
1028 if (x11->clipboard_owner[selection] != owner_client) {
1029 SELPRINTF("received selection request event for target %s, "
1030 "while not owning client clipboard",
1031 vdagent_x11_get_atom_name(x11, event->xselectionrequest.target));
1032 vdagent_x11_send_selection_notify(x11, None, NULL);
1033 return;
1036 if (event->xselectionrequest.target == x11->multiple_atom) {
1037 SELPRINTF("multiple target not supported");
1038 vdagent_x11_send_selection_notify(x11, None, NULL);
1039 return;
1042 if (event->xselectionrequest.target == x11->timestamp_atom) {
1043 /* TODO: use more accurate selection time */
1044 guint32 timestamp = event->xselectionrequest.time;
1046 XChangeProperty(x11->display, event->xselectionrequest.requestor,
1047 event->xselectionrequest.property,
1048 event->xselectionrequest.target, 32, PropModeReplace,
1049 (guint8*)&timestamp, 1);
1050 vdagent_x11_send_selection_notify(x11,
1051 event->xselectionrequest.property, NULL);
1052 return;
1056 if (event->xselectionrequest.target == x11->targets_atom) {
1057 vdagent_x11_send_targets(x11, selection, event);
1058 return;
1061 type = vdagent_x11_target_to_type(x11, selection,
1062 event->xselectionrequest.target);
1063 if (type == VD_AGENT_CLIPBOARD_NONE) {
1064 VSELPRINTF("guest app requested a non-advertised target");
1065 vdagent_x11_send_selection_notify(x11, None, NULL);
1066 return;
1069 udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_REQUEST, selection, type,
1070 NULL, 0);
1073 static void vdagent_x11_handle_property_delete_notify(struct vdagent_x11 *x11,
1074 XEvent *del_event)
1076 XEvent *sel_event;
1077 int len;
1078 uint8_t selection;
1080 assert(x11->selection_req);
1081 sel_event = &x11->selection_req->event;
1082 selection = x11->selection_req->selection;
1083 if (del_event->xproperty.window != sel_event->xselectionrequest.requestor
1084 || del_event->xproperty.atom != x11->selection_req_atom) {
1085 return;
1088 len = x11->selection_req_data_size - x11->selection_req_data_pos;
1089 if (len > x11->max_prop_size) {
1090 len = x11->max_prop_size;
1093 if (len) {
1094 VSELPRINTF("Sending %d-%d/%d bytes of clipboard data",
1095 x11->selection_req_data_pos,
1096 x11->selection_req_data_pos + len - 1,
1097 x11->selection_req_data_size);
1098 } else {
1099 VSELPRINTF("Ending incr send of clipboard data");
1101 vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler);
1102 XChangeProperty(x11->display, sel_event->xselectionrequest.requestor,
1103 x11->selection_req_atom,
1104 sel_event->xselectionrequest.target, 8, PropModeReplace,
1105 x11->selection_req_data + x11->selection_req_data_pos,
1106 len);
1107 if (vdagent_x11_restore_error_handler(x11)) {
1108 SELPRINTF("incr sent failed, requestor window gone");
1109 len = 0;
1112 x11->selection_req_data_pos += len;
1114 /* Note we must explictly send a 0 sized XChangeProperty to signal the
1115 incr transfer is done. Hence we do not check if we've send all data
1116 but instead check we've send the final 0 sized XChangeProperty. */
1117 if (len == 0) {
1118 free(x11->selection_req_data);
1119 x11->selection_req_data = NULL;
1120 x11->selection_req_data_pos = 0;
1121 x11->selection_req_data_size = 0;
1122 x11->selection_req_atom = None;
1123 vdagent_x11_next_selection_request(x11);
1124 vdagent_x11_handle_selection_request(x11);
1128 void vdagent_x11_clipboard_request(struct vdagent_x11 *x11,
1129 uint8_t selection, uint32_t type)
1131 Atom target, clip;
1132 struct vdagent_x11_conversion_request *req, *new_req;
1134 /* We don't use clip here, but we call get_clipboard_atom to verify
1135 selection is valid */
1136 if (vdagent_x11_get_clipboard_atom(x11, selection, &clip)) {
1137 goto none;
1140 if (x11->clipboard_owner[selection] != owner_guest) {
1141 SELPRINTF("received clipboard req while not owning guest clipboard");
1142 goto none;
1145 target = vdagent_x11_type_to_target(x11, selection, type);
1146 if (target == None) {
1147 goto none;
1150 new_req = malloc(sizeof(*new_req));
1151 if (!new_req) {
1152 SELPRINTF("out of memory on client clipboard request, ignoring.");
1153 return;
1156 new_req->target = target;
1157 new_req->selection = selection;
1158 new_req->next = NULL;
1160 if (!x11->conversion_req) {
1161 x11->conversion_req = new_req;
1162 vdagent_x11_handle_conversion_request(x11);
1163 /* Flush output buffers and consume any pending events */
1164 vdagent_x11_do_read(x11);
1165 return;
1168 /* maybe we should limit the conversion_request stack depth ? */
1169 req = x11->conversion_req;
1170 while (req->next)
1171 req = req->next;
1173 req->next = new_req;
1174 return;
1176 none:
1177 udscs_write(x11->vdagentd, VDAGENTD_CLIPBOARD_DATA,
1178 selection, VD_AGENT_CLIPBOARD_NONE, NULL, 0);
1181 void vdagent_x11_clipboard_grab(struct vdagent_x11 *x11, uint8_t selection,
1182 uint32_t *types, uint32_t type_count)
1184 Atom clip = None;
1186 if (vdagent_x11_get_clipboard_atom(x11, selection, &clip)) {
1187 return;
1190 if (type_count > sizeof(x11->clipboard_agent_types[0])/sizeof(uint32_t)) {
1191 SELPRINTF("x11_clipboard_grab: too many types");
1192 type_count = sizeof(x11->clipboard_agent_types[0])/sizeof(uint32_t);
1195 memcpy(x11->clipboard_agent_types[selection], types,
1196 type_count * sizeof(uint32_t));
1197 x11->clipboard_type_count[selection] = type_count;
1199 XSetSelectionOwner(x11->display, clip,
1200 x11->selection_window, CurrentTime);
1201 vdagent_x11_set_clipboard_owner(x11, selection, owner_client);
1203 /* Flush output buffers and consume any pending events */
1204 vdagent_x11_do_read(x11);
1207 void vdagent_x11_clipboard_data(struct vdagent_x11 *x11, uint8_t selection,
1208 uint32_t type, uint8_t *data, uint32_t size)
1210 Atom prop;
1211 XEvent *event;
1212 uint32_t type_from_event;
1214 if (x11->selection_req_data) {
1215 if (type || size) {
1216 SELPRINTF("received clipboard data while still sending"
1217 " data from previous request, ignoring");
1219 free(data);
1220 return;
1223 if (!x11->selection_req) {
1224 if (type || size) {
1225 SELPRINTF("received clipboard data without an "
1226 "outstanding selection request, ignoring");
1228 free(data);
1229 return;
1232 event = &x11->selection_req->event;
1233 type_from_event = vdagent_x11_target_to_type(x11,
1234 x11->selection_req->selection,
1235 event->xselectionrequest.target);
1236 if (type_from_event != type ||
1237 selection != x11->selection_req->selection) {
1238 if (selection != x11->selection_req->selection) {
1239 SELPRINTF("expecting data for selection %d got %d",
1240 (int)x11->selection_req->selection, (int)selection);
1242 if (type_from_event != type) {
1243 SELPRINTF("expecting type %u clipboard data got %u",
1244 type_from_event, type);
1246 vdagent_x11_send_selection_notify(x11, None, NULL);
1247 free(data);
1249 /* Flush output buffers and consume any pending events */
1250 vdagent_x11_do_read(x11);
1251 return;
1254 prop = event->xselectionrequest.property;
1255 if (prop == None)
1256 prop = event->xselectionrequest.target;
1258 if (size > x11->max_prop_size) {
1259 unsigned long len = size;
1260 VSELPRINTF("Starting incr send of clipboard data");
1262 vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler);
1263 XSelectInput(x11->display, event->xselectionrequest.requestor,
1264 PropertyChangeMask);
1265 XChangeProperty(x11->display, event->xselectionrequest.requestor, prop,
1266 x11->incr_atom, 32, PropModeReplace,
1267 (unsigned char*)&len, 1);
1268 if (vdagent_x11_restore_error_handler(x11) == 0) {
1269 x11->selection_req_data = data;
1270 x11->selection_req_data_pos = 0;
1271 x11->selection_req_data_size = size;
1272 x11->selection_req_atom = prop;
1273 vdagent_x11_send_selection_notify(x11, prop, x11->selection_req);
1274 } else {
1275 SELPRINTF("clipboard data sent failed, requestor window gone");
1276 free(data);
1278 } else {
1279 vdagent_x11_set_error_handler(x11, vdagent_x11_ignore_bad_window_handler);
1280 XChangeProperty(x11->display, event->xselectionrequest.requestor, prop,
1281 event->xselectionrequest.target, 8, PropModeReplace,
1282 data, size);
1283 if (vdagent_x11_restore_error_handler(x11) == 0)
1284 vdagent_x11_send_selection_notify(x11, prop, NULL);
1285 else
1286 SELPRINTF("clipboard data sent failed, requestor window gone");
1288 free(data);
1291 /* Flush output buffers and consume any pending events */
1292 vdagent_x11_do_read(x11);
1295 void vdagent_x11_clipboard_release(struct vdagent_x11 *x11, uint8_t selection)
1297 XEvent event;
1298 Atom clip = None;
1300 if (vdagent_x11_get_clipboard_atom(x11, selection, &clip)) {
1301 return;
1304 if (x11->clipboard_owner[selection] != owner_client) {
1305 VSELPRINTF("received release while not owning client clipboard");
1306 return;
1309 XSetSelectionOwner(x11->display, clip, None, CurrentTime);
1310 /* Make sure we process the XFixesSetSelectionOwnerNotify event caused
1311 by this, so we don't end up changing the clipboard owner to none, after
1312 it has already been re-owned because this event is still pending. */
1313 XSync(x11->display, False);
1314 while (XCheckTypedEvent(x11->display, x11->xfixes_event_base,
1315 &event))
1316 vdagent_x11_handle_event(x11, event);
1318 /* Note no need to do a set_clipboard_owner(owner_none) here, as that is
1319 already done by processing the XFixesSetSelectionOwnerNotify event. */
1321 /* Flush output buffers and consume any pending events */
1322 vdagent_x11_do_read(x11);
1325 void vdagent_x11_client_disconnected(struct vdagent_x11 *x11)
1327 int sel;
1329 for (sel = 0; sel < VD_AGENT_CLIPBOARD_SELECTION_SECONDARY; sel++) {
1330 if (x11->clipboard_owner[sel] == owner_client)
1331 vdagent_x11_clipboard_release(x11, sel);
1335 /* Function used to determine the default location to save file-xfers,
1336 xdg desktop dir or xdg download dir. We error on the save side and use a
1337 whitelist approach, so any unknown desktops will end up with saving
1338 file-xfers to the xdg download dir, and opening the xdg download dir with
1339 xdg-open when the file-xfer completes. */
1340 int vdagent_x11_has_icons_on_desktop(struct vdagent_x11 *x11)
1342 const char * const wms_with_icons_on_desktop[] = {
1343 "Metacity", /* GNOME-2 or GNOME-3 fallback */
1344 "Xfwm4", /* XFCE */
1345 "Marco", /* Mate */
1346 NULL
1348 int i;
1350 if (x11->net_wm_name)
1351 for (i = 0; wms_with_icons_on_desktop[i]; i++)
1352 if (!strcmp(x11->net_wm_name, wms_with_icons_on_desktop[i]))
1353 return 1;
1355 return 0;