1 /* vdagent-x11.c vdagent x11 code
3 Copyright 2010 Red Hat, Inc.
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 *all* X11 calls in this file which do not wait for a result must be
23 followed by an XFlush, given that the X11 code pumping the event loop
24 (and thus flushing queued writes) is only called when there is data to be
25 read from the X11 socket. */
30 #include <X11/Xatom.h>
32 #include <X11/extensions/Xrandr.h>
33 #include <X11/extensions/Xfixes.h>
34 #include "vdagentd-proto.h"
35 #include "vdagent-x11.h"
37 enum { owner_none
, owner_guest
, owner_client
};
39 const char *utf8_atom_names
[] = {
41 "text/plain;charset=UTF-8",
42 "text/plain;charset=utf-8",
45 #define utf8_atom_count (sizeof(utf8_atom_names)/sizeof(utf8_atom_names[0]))
47 struct vdagent_x11_selection_request
{
49 struct vdagent_x11_selection_request
*next
;
58 Atom utf8_atoms
[utf8_atom_count
];
60 Window selection_window
;
61 struct udscs_connection
*vdagentd
;
69 int xfixes_event_base
;
70 int expected_targets_notifies
;
72 int clipboard_type_count
;
73 /* Warning the size of these needs to be increased each time we add
74 support for a new type!! */
75 uint32_t clipboard_agent_types
[1];
76 Atom clipboard_x11_targets
[1];
77 struct vdagent_x11_selection_request
*selection_request
;
80 static void vdagent_x11_send_daemon_guest_xorg_res(struct vdagent_x11
*x11
);
81 static void vdagent_x11_handle_selection_notify(struct vdagent_x11
*x11
,
83 static void vdagent_x11_handle_selection_request(struct vdagent_x11
*x11
);
84 static void vdagent_x11_handle_targets_notify(struct vdagent_x11
*x11
,
86 static void vdagent_x11_send_selection_notify(struct vdagent_x11
*x11
,
88 int process_next_req
);
90 struct vdagent_x11
*vdagent_x11_create(struct udscs_connection
*vdagentd
,
93 struct vdagent_x11
*x11
;
94 XWindowAttributes attrib
;
97 x11
= calloc(1, sizeof(*x11
));
99 fprintf(stderr
, "out of memory allocating vdagent_x11 struct\n");
103 x11
->vdagentd
= vdagentd
;
104 x11
->verbose
= verbose
;
106 x11
->display
= XOpenDisplay(NULL
);
108 fprintf(stderr
, "could not connect to X-server\n");
113 x11
->screen
= DefaultScreen(x11
->display
);
114 x11
->root_window
= RootWindow(x11
->display
, x11
->screen
);
115 x11
->fd
= ConnectionNumber(x11
->display
);
116 x11
->clipboard_atom
= XInternAtom(x11
->display
, "CLIPBOARD", False
);
117 x11
->targets_atom
= XInternAtom(x11
->display
, "TARGETS", False
);
118 x11
->incr_atom
= XInternAtom(x11
->display
, "INCR", False
);
119 x11
->multiple_atom
= XInternAtom(x11
->display
, "MULTIPLE", False
);
120 for(i
= 0; i
< utf8_atom_count
; i
++)
121 x11
->utf8_atoms
[i
] = XInternAtom(x11
->display
, utf8_atom_names
[i
],
124 /* We should not store properties (for selections) on the root window */
125 x11
->selection_window
= XCreateSimpleWindow(x11
->display
, x11
->root_window
,
126 0, 0, 1, 1, 0, 0, 0);
128 fprintf(stderr
, "Selection window: %u\n",
129 (unsigned int)x11
->selection_window
);
131 if (XRRQueryExtension(x11
->display
, &i
, &i
))
134 fprintf(stderr
, "no xrandr\n");
136 if (XFixesQueryExtension(x11
->display
, &x11
->xfixes_event_base
, &i
) &&
137 XFixesQueryVersion(x11
->display
, &major
, &minor
) && major
>= 1) {
139 XFixesSelectSelectionInput(x11
->display
, x11
->root_window
,
141 XFixesSetSelectionOwnerNotifyMask
);
143 fprintf(stderr
, "no xfixes, no guest -> client copy paste support\n");
145 /* Catch resolution changes */
146 XSelectInput(x11
->display
, x11
->root_window
, StructureNotifyMask
);
148 /* Get the current resolution */
149 XGetWindowAttributes(x11
->display
, x11
->root_window
, &attrib
);
150 x11
->width
= attrib
.width
;
151 x11
->height
= attrib
.height
;
152 vdagent_x11_send_daemon_guest_xorg_res(x11
);
154 /* No need for XFlush as XGetWindowAttributes does an implicit Xflush */
159 void vdagent_x11_destroy(struct vdagent_x11
*x11
)
164 XCloseDisplay(x11
->display
);
168 int vdagent_x11_get_fd(struct vdagent_x11
*x11
)
173 static void vdagent_x11_set_clipboard_owner(struct vdagent_x11
*x11
,
176 if (x11
->selection_request
) {
178 "selection requests pending on clipboard ownership change, "
180 while (x11
->selection_request
)
181 vdagent_x11_send_selection_notify(x11
, None
, 0);
184 if (new_owner
== owner_none
) {
185 /* When going from owner_guest to owner_none we need to send a
186 clipboard release message to the client */
187 if (x11
->clipboard_owner
== owner_guest
)
188 udscs_write(x11
->vdagentd
, VDAGENTD_CLIPBOARD_RELEASE
, 0, NULL
, 0);
190 x11
->clipboard_type_count
= 0;
192 x11
->clipboard_owner
= new_owner
;
195 static void vdagent_x11_handle_event(struct vdagent_x11
*x11
, XEvent event
)
200 if (event
.type
== x11
->xfixes_event_base
) {
203 XFixesSelectionNotifyEvent xfev
;
207 if (ev
.xfev
.subtype
!= XFixesSetSelectionOwnerNotify
) {
209 fprintf(stderr
, "unexpected xfix event subtype %d window %d\n",
210 (int)ev
.xfev
.subtype
, (int)event
.xany
.window
);
215 fprintf(stderr
, "New selection owner: %u\n",
216 (unsigned int)ev
.xfev
.owner
);
218 /* Ignore becoming the owner ourselves */
219 if (ev
.xfev
.owner
== x11
->selection_window
)
222 /* If the clipboard owner is changed we no longer own it */
223 vdagent_x11_set_clipboard_owner(x11
, owner_none
);
225 if (ev
.xfev
.owner
== None
)
228 /* Request the supported targets from the new owner */
229 XConvertSelection(x11
->display
, x11
->clipboard_atom
, x11
->targets_atom
,
230 x11
->targets_atom
, x11
->selection_window
,
232 XFlush(x11
->display
);
233 x11
->expected_targets_notifies
++;
237 switch (event
.type
) {
238 case ConfigureNotify
:
239 if (event
.xconfigure
.window
!= x11
->root_window
)
244 if (event
.xconfigure
.width
== x11
->width
&&
245 event
.xconfigure
.height
== x11
->height
)
248 x11
->width
= event
.xconfigure
.width
;
249 x11
->height
= event
.xconfigure
.height
;
251 vdagent_x11_send_daemon_guest_xorg_res(x11
);
253 case SelectionNotify
:
254 if (event
.xselection
.target
== x11
->targets_atom
)
255 vdagent_x11_handle_targets_notify(x11
, &event
);
257 vdagent_x11_handle_selection_notify(x11
, &event
);
262 /* Do nothing the clipboard ownership will get updated through
263 the XFixesSetSelectionOwnerNotify event */
266 case SelectionRequest
: {
267 struct vdagent_x11_selection_request
*req
, *new_req
;
269 new_req
= malloc(sizeof(*new_req
));
271 fprintf(stderr
, "out of memory on SelectionRequest, ignoring.\n");
277 new_req
->event
= event
;
278 new_req
->next
= NULL
;
280 if (!x11
->selection_request
) {
281 x11
->selection_request
= new_req
;
282 vdagent_x11_handle_selection_request(x11
);
286 /* maybe we should limit the selection_request stack depth ? */
287 req
= x11
->selection_request
;
295 if (!handled
&& x11
->verbose
)
296 fprintf(stderr
, "unhandled x11 event, type %d, window %d\n",
297 (int)event
.type
, (int)event
.xany
.window
);
300 void vdagent_x11_do_read(struct vdagent_x11
*x11
)
304 while (XPending(x11
->display
)) {
305 XNextEvent(x11
->display
, &event
);
306 vdagent_x11_handle_event(x11
, event
);
310 static void vdagent_x11_send_daemon_guest_xorg_res(struct vdagent_x11
*x11
)
312 struct vdagentd_guest_xorg_resolution res
;
314 res
.width
= x11
->width
;
315 res
.height
= x11
->height
;
317 udscs_write(x11
->vdagentd
, VDAGENTD_GUEST_XORG_RESOLUTION
, 0,
318 (uint8_t *)&res
, sizeof(res
));
321 static const char *vdagent_x11_get_atom_name(struct vdagent_x11
*x11
, Atom a
)
326 return XGetAtomName(x11
->display
, a
);
329 static int vdagent_x11_get_selection(struct vdagent_x11
*x11
, XEvent
*event
,
330 Atom type
, Atom prop
, int format
,
331 unsigned char **data
)
335 unsigned long len
, remain
;
337 if (event
->xselection
.property
== None
) {
339 fprintf(stderr
, "XConvertSelection refused by clipboard owner\n");
343 if (event
->xselection
.requestor
!= x11
->selection_window
||
344 event
->xselection
.selection
!= x11
->clipboard_atom
||
345 event
->xselection
.property
!= prop
) {
346 fprintf(stderr
, "SelectionNotify parameters mismatch\n");
350 /* FIXME when we've incr support we should not immediately
351 delete the property (as we need to first register for
352 property change events) */
353 if (XGetWindowProperty(x11
->display
, x11
->selection_window
, prop
, 0,
354 LONG_MAX
, True
, type
, &type_ret
, &format_ret
, &len
,
355 &remain
, data
) != Success
) {
356 fprintf(stderr
, "XGetWindowProperty failed\n");
360 if (type_ret
== x11
->incr_atom
) {
362 fprintf(stderr
, "incr properties are currently unsupported\n");
366 if (type_ret
!= type
) {
367 fprintf(stderr
, "expected property type: %s, got: %s\n",
368 vdagent_x11_get_atom_name(x11
, type
),
369 vdagent_x11_get_atom_name(x11
, type_ret
));
373 if (format_ret
!= format
) {
374 fprintf(stderr
, "expected %d bit format, got %d bits\n", format
,
380 fprintf(stderr
, "property contains no data (zero length)\n");
387 static uint32_t vdagent_x11_target_to_type(struct vdagent_x11
*x11
,
392 for (i
= 0; i
< utf8_atom_count
; i
++)
393 if (x11
->utf8_atoms
[i
] == target
)
394 return VD_AGENT_CLIPBOARD_UTF8_TEXT
;
396 /* TODO Add support for more types here */
398 fprintf(stderr
, "unexpected selection type %s\n",
399 vdagent_x11_get_atom_name(x11
, target
));
400 return VD_AGENT_CLIPBOARD_NONE
;
403 static Atom
vdagent_x11_type_to_target(struct vdagent_x11
*x11
, uint32_t type
)
407 for (i
= 0; i
< x11
->clipboard_type_count
; i
++)
408 if (x11
->clipboard_agent_types
[i
] == type
)
409 return x11
->clipboard_x11_targets
[i
];
411 fprintf(stderr
, "client requested unavailable type %u\n", type
);
415 static void vdagent_x11_handle_selection_notify(struct vdagent_x11
*x11
,
419 unsigned char *data
= NULL
;
422 type
= vdagent_x11_target_to_type(x11
, event
->xselection
.target
);
423 if (type
== VD_AGENT_CLIPBOARD_NONE
) {
424 udscs_write(x11
->vdagentd
, VDAGENTD_CLIPBOARD_DATA
,
425 VD_AGENT_CLIPBOARD_NONE
, NULL
, 0);
429 len
= vdagent_x11_get_selection(x11
, event
, event
->xselection
.target
,
430 x11
->clipboard_atom
, 8, &data
);
434 udscs_write(x11
->vdagentd
, VDAGENTD_CLIPBOARD_DATA
,
435 VD_AGENT_CLIPBOARD_NONE
, NULL
, 0);
439 udscs_write(x11
->vdagentd
, VDAGENTD_CLIPBOARD_DATA
, type
, data
, len
);
443 static Atom
atom_lists_overlap(Atom
*atoms1
, Atom
*atoms2
, int l1
, int l2
)
447 for (i
= 0; i
< l1
; i
++)
448 for (j
= 0; j
< l2
; j
++)
449 if (atoms1
[i
] == atoms2
[j
])
455 static void vdagent_x11_print_targets(struct vdagent_x11
*x11
,
456 const char *action
, Atom
*atoms
, int c
)
463 fprintf(stderr
, "%s %d targets:\n", action
, c
);
464 for (i
= 0; i
< c
; i
++)
465 fprintf(stderr
, "%s\n", vdagent_x11_get_atom_name(x11
, atoms
[i
]));
468 static void vdagent_x11_handle_targets_notify(struct vdagent_x11
*x11
,
472 Atom atom
, *atoms
= NULL
;
474 if (!x11
->expected_targets_notifies
) {
475 fprintf(stderr
, "unexpected selection notify TARGETS\n");
479 x11
->expected_targets_notifies
--;
481 /* If we have more targets_notifies pending, ignore this one, we
482 are only interested in the targets list of the current owner
483 (which is the last one we've requested a targets list from) */
484 if (x11
->expected_targets_notifies
)
487 len
= vdagent_x11_get_selection(x11
, event
, XA_ATOM
, x11
->targets_atom
, 32,
488 (unsigned char **)&atoms
);
495 vdagent_x11_print_targets(x11
, "received", atoms
, len
);
497 x11
->clipboard_type_count
= 0;
498 atom
= atom_lists_overlap(x11
->utf8_atoms
, atoms
, utf8_atom_count
, len
);
500 x11
->clipboard_agent_types
[x11
->clipboard_type_count
] =
501 VD_AGENT_CLIPBOARD_UTF8_TEXT
;
502 x11
->clipboard_x11_targets
[x11
->clipboard_type_count
] = atom
;
503 x11
->clipboard_type_count
++;
506 /* TODO Add support for more types here */
508 if (x11
->clipboard_type_count
) {
509 udscs_write(x11
->vdagentd
, VDAGENTD_CLIPBOARD_GRAB
, 0,
510 (uint8_t *)x11
->clipboard_agent_types
,
511 x11
->clipboard_type_count
* sizeof(uint32_t));
512 vdagent_x11_set_clipboard_owner(x11
, owner_guest
);
517 static void vdagent_x11_send_selection_notify(struct vdagent_x11
*x11
,
518 Atom prop
, int process_next_req
)
520 XEvent res
, *event
= &x11
->selection_request
->event
;
521 struct vdagent_x11_selection_request
*selection_request
;
523 res
.xselection
.property
= prop
;
524 res
.xselection
.type
= SelectionNotify
;
525 res
.xselection
.display
= event
->xselectionrequest
.display
;
526 res
.xselection
.requestor
= event
->xselectionrequest
.requestor
;
527 res
.xselection
.selection
= event
->xselectionrequest
.selection
;
528 res
.xselection
.target
= event
->xselectionrequest
.target
;
529 res
.xselection
.time
= event
->xselectionrequest
.time
;
530 XSendEvent(x11
->display
, event
->xselectionrequest
.requestor
, 0, 0, &res
);
531 XFlush(x11
->display
);
533 selection_request
= x11
->selection_request
;
534 x11
->selection_request
= selection_request
->next
;
535 free(selection_request
);
536 if (process_next_req
)
537 vdagent_x11_handle_selection_request(x11
);
540 static void vdagent_x11_send_targets(struct vdagent_x11
*x11
, XEvent
*event
)
542 /* Warning the size of this needs to be increased each time we add support
543 for a new type, or the atom count of an existing type changes */
544 Atom prop
, targets
[4] = { x11
->targets_atom
, };
545 int i
, j
, target_count
= 1;
547 for (i
= 0; i
< x11
->clipboard_type_count
; i
++) {
548 switch (x11
->clipboard_agent_types
[i
]) {
549 case VD_AGENT_CLIPBOARD_UTF8_TEXT
:
550 for (j
= 0; j
< utf8_atom_count
; j
++) {
551 targets
[target_count
] = x11
->utf8_atoms
[j
];
555 /* TODO Add support for more types here */
559 prop
= event
->xselectionrequest
.property
;
561 prop
= event
->xselectionrequest
.target
;
563 XChangeProperty(x11
->display
, event
->xselectionrequest
.requestor
, prop
,
564 XA_ATOM
, 32, PropModeReplace
, (unsigned char *)&targets
,
566 vdagent_x11_print_targets(x11
, "sent", targets
, target_count
);
567 vdagent_x11_send_selection_notify(x11
, prop
, 1);
570 static void vdagent_x11_handle_selection_request(struct vdagent_x11
*x11
)
573 uint32_t type
= VD_AGENT_CLIPBOARD_NONE
;
575 if (!x11
->selection_request
)
578 event
= &x11
->selection_request
->event
;
580 if (x11
->clipboard_owner
!= owner_client
) {
582 "received selection request event for target %s, "
583 "while not owning client clipboard\n",
584 vdagent_x11_get_atom_name(x11
, event
->xselectionrequest
.target
));
585 vdagent_x11_send_selection_notify(x11
, None
, 1);
589 if (event
->xselectionrequest
.target
== x11
->multiple_atom
) {
590 fprintf(stderr
, "multiple target not supported\n");
591 vdagent_x11_send_selection_notify(x11
, None
, 1);
595 if (event
->xselectionrequest
.target
== x11
->targets_atom
) {
596 vdagent_x11_send_targets(x11
, event
);
600 type
= vdagent_x11_target_to_type(x11
, event
->xselectionrequest
.target
);
601 if (type
== VD_AGENT_CLIPBOARD_NONE
) {
602 vdagent_x11_send_selection_notify(x11
, None
, 1);
606 udscs_write(x11
->vdagentd
, VDAGENTD_CLIPBOARD_REQUEST
, type
, NULL
, 0);
609 void vdagent_x11_set_monitor_config(struct vdagent_x11
*x11
,
610 VDAgentMonitorsConfig
*mon_config
)
612 int i
, num_sizes
= 0;
614 unsigned int closest_diff
= -1;
615 XRRScreenSize
* sizes
;
616 XRRScreenConfiguration
* config
;
619 if (!x11
->has_xrandr
)
622 if (mon_config
->num_of_monitors
!= 1) {
623 fprintf(stderr
, "Only 1 monitor supported, ignoring monitor config\n");
627 sizes
= XRRSizes(x11
->display
, x11
->screen
, &num_sizes
);
628 if (!sizes
|| !num_sizes
) {
629 fprintf(stderr
, "XRRSizes failed\n");
633 /* Find the closest size which will fit within the monitor */
634 for (i
= 0; i
< num_sizes
; i
++) {
635 if (sizes
[i
].width
> mon_config
->monitors
[0].width
||
636 sizes
[i
].height
> mon_config
->monitors
[0].height
)
637 continue; /* Too large for the monitor */
639 unsigned int wdiff
= mon_config
->monitors
[0].width
- sizes
[i
].width
;
640 unsigned int hdiff
= mon_config
->monitors
[0].height
- sizes
[i
].height
;
641 unsigned int diff
= wdiff
* wdiff
+ hdiff
* hdiff
;
642 if (diff
< closest_diff
) {
649 fprintf(stderr
, "no suitable resolution found for monitor\n");
653 config
= XRRGetScreenInfo(x11
->display
, x11
->root_window
);
655 fprintf(stderr
, "get screen info failed\n");
658 XRRConfigCurrentConfiguration(config
, &rotation
);
659 XRRSetScreenConfig(x11
->display
, config
, x11
->root_window
, best
,
660 rotation
, CurrentTime
);
661 XRRFreeScreenConfigInfo(config
);
662 XFlush(x11
->display
);
665 void vdagent_x11_clipboard_request(struct vdagent_x11
*x11
, uint32_t type
)
669 if (x11
->clipboard_owner
!= owner_guest
) {
671 "received clipboard req while not owning guest clipboard\n");
672 udscs_write(x11
->vdagentd
, VDAGENTD_CLIPBOARD_DATA
,
673 VD_AGENT_CLIPBOARD_NONE
, NULL
, 0);
677 target
= vdagent_x11_type_to_target(x11
, type
);
678 if (target
== None
) {
679 udscs_write(x11
->vdagentd
, VDAGENTD_CLIPBOARD_DATA
,
680 VD_AGENT_CLIPBOARD_NONE
, NULL
, 0);
684 XConvertSelection(x11
->display
, x11
->clipboard_atom
, target
,
685 x11
->clipboard_atom
, x11
->selection_window
, CurrentTime
);
686 XFlush(x11
->display
);
689 void vdagent_x11_clipboard_grab(struct vdagent_x11
*x11
, uint32_t *types
,
694 x11
->clipboard_type_count
= 0;
695 for (i
= 0; i
< type_count
; i
++) {
696 /* TODO Add support for more types here */
697 /* Check if we support the type */
698 if (types
[i
] != VD_AGENT_CLIPBOARD_UTF8_TEXT
)
701 x11
->clipboard_agent_types
[x11
->clipboard_type_count
] = types
[i
];
702 x11
->clipboard_type_count
++;
705 if (!x11
->clipboard_type_count
)
708 XSetSelectionOwner(x11
->display
, x11
->clipboard_atom
,
709 x11
->selection_window
, CurrentTime
);
710 XFlush(x11
->display
);
711 vdagent_x11_set_clipboard_owner(x11
, owner_client
);
714 void vdagent_x11_clipboard_data(struct vdagent_x11
*x11
, uint32_t type
,
715 const uint8_t *data
, uint32_t size
)
719 uint32_t type_from_event
;
721 if (!x11
->selection_request
) {
722 fprintf(stderr
, "received clipboard data without an outstanding"
723 "selection request, ignoring\n");
727 event
= &x11
->selection_request
->event
;
728 type_from_event
= vdagent_x11_target_to_type(x11
,
729 event
->xselectionrequest
.target
);
730 if (type_from_event
!= type
) {
731 fprintf(stderr
, "expecting type %u clipboard data got %u\n",
732 type_from_event
, type
);
733 vdagent_x11_send_selection_notify(x11
, None
, 1);
737 prop
= event
->xselectionrequest
.property
;
739 prop
= event
->xselectionrequest
.target
;
741 /* FIXME: use INCR for large data transfers */
742 XChangeProperty(x11
->display
, event
->xselectionrequest
.requestor
, prop
,
743 event
->xselectionrequest
.target
, 8, PropModeReplace
,
745 vdagent_x11_send_selection_notify(x11
, prop
, 1);
748 void vdagent_x11_clipboard_release(struct vdagent_x11
*x11
)
752 if (x11
->clipboard_owner
!= owner_client
) {
754 "received clipboard release while not owning client clipboard\n");
758 XSetSelectionOwner(x11
->display
, x11
->clipboard_atom
, None
, CurrentTime
);
759 /* Make sure we process the XFixesSetSelectionOwnerNotify event caused
760 by this, so we don't end up changing the clipboard owner to none, after
761 it has already been re-owned because this event is still pending. */
762 XSync(x11
->display
, False
);
763 while (XCheckTypedEvent(x11
->display
, x11
->xfixes_event_base
,
765 vdagent_x11_handle_event(x11
, event
);
767 /* Note no need to do a set_clipboard_owner(owner_none) here, as that is
768 already done by processing the XFixesSetSelectionOwnerNotify event. */