1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
3
* Copyright (C) 2007 Matthias Clasen
4
* Copyright (C) 2007 Anders Carlsson
5
* Copyright (C) 2007 Rodrigo Moya
6
* Copyright (C) 2007 William Jon McCann <mccann@jhu.edu>
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 2 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, write to the Free Software
20
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
#include <sys/types.h>
37
#include <glib/gi18n.h>
42
#include <X11/Xatom.h>
47
#include "gnome-settings-profile.h"
48
#include "gsd-clipboard-manager.h"
50
#define GSD_CLIPBOARD_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_CLIPBOARD_MANAGER, GsdClipboardManagerPrivate))
52
struct GsdClipboardManagerPrivate
86
static void gsd_clipboard_manager_class_init (GsdClipboardManagerClass *klass);
87
static void gsd_clipboard_manager_init (GsdClipboardManager *clipboard_manager);
88
static void gsd_clipboard_manager_finalize (GObject *object);
90
static void clipboard_manager_watch_cb (GsdClipboardManager *manager,
96
G_DEFINE_TYPE (GsdClipboardManager, gsd_clipboard_manager, G_TYPE_OBJECT)
98
static gpointer manager_object = NULL;
100
/* We need to use reference counting for the target data, since we may
101
* need to keep the data around after loosing the CLIPBOARD ownership
102
* to complete incremental transfers.
105
target_data_ref (TargetData *data)
112
target_data_unref (TargetData *data)
115
if (data->refcount == 0) {
122
conversion_free (IncrConversion *rdata)
125
target_data_unref (rdata->data);
131
send_selection_notify (GsdClipboardManager *manager,
134
XSelectionEvent notify;
136
notify.type = SelectionNotify;
138
notify.send_event = True;
139
notify.display = manager->priv->display;
140
notify.requestor = manager->priv->requestor;
141
notify.selection = XA_CLIPBOARD_MANAGER;
142
notify.target = XA_SAVE_TARGETS;
143
notify.property = success ? manager->priv->property : None;
144
notify.time = manager->priv->time;
146
gdk_error_trap_push ();
148
XSendEvent (manager->priv->display,
149
manager->priv->requestor,
153
XSync (manager->priv->display, False);
155
gdk_error_trap_pop_ignored ();
159
finish_selection_request (GsdClipboardManager *manager,
163
XSelectionEvent notify;
165
notify.type = SelectionNotify;
167
notify.send_event = True;
168
notify.display = xev->xselectionrequest.display;
169
notify.requestor = xev->xselectionrequest.requestor;
170
notify.selection = xev->xselectionrequest.selection;
171
notify.target = xev->xselectionrequest.target;
172
notify.property = success ? xev->xselectionrequest.property : None;
173
notify.time = xev->xselectionrequest.time;
175
gdk_error_trap_push ();
177
XSendEvent (xev->xselectionrequest.display,
178
xev->xselectionrequest.requestor,
179
False, NoEventMask, (XEvent *) ¬ify);
180
XSync (manager->priv->display, False);
182
gdk_error_trap_pop_ignored ();
186
clipboard_bytes_per_item (int format)
189
case 8: return sizeof (char);
190
case 16: return sizeof (short);
191
case 32: return sizeof (long);
199
save_targets (GsdClipboardManager *manager,
207
multiple = (Atom *) malloc (2 * nitems * sizeof (Atom));
210
for (i = 0; i < nitems; i++) {
211
if (save_targets[i] != XA_TARGETS &&
212
save_targets[i] != XA_MULTIPLE &&
213
save_targets[i] != XA_DELETE &&
214
save_targets[i] != XA_INSERT_PROPERTY &&
215
save_targets[i] != XA_INSERT_SELECTION &&
216
save_targets[i] != XA_PIXMAP) {
217
tdata = (TargetData *) malloc (sizeof (TargetData));
220
tdata->target = save_targets[i];
224
manager->priv->contents = list_prepend (manager->priv->contents, tdata);
226
multiple[nout++] = save_targets[i];
227
multiple[nout++] = save_targets[i];
231
XFree (save_targets);
233
XChangeProperty (manager->priv->display, manager->priv->window,
234
XA_MULTIPLE, XA_ATOM_PAIR,
235
32, PropModeReplace, (const unsigned char *) multiple, nout);
238
XConvertSelection (manager->priv->display, XA_CLIPBOARD,
239
XA_MULTIPLE, XA_MULTIPLE,
240
manager->priv->window, manager->priv->time);
244
find_content_target (TargetData *tdata,
247
return tdata->target == target;
251
find_content_type (TargetData *tdata,
254
return tdata->type == type;
258
find_conversion_requestor (IncrConversion *rdata,
261
return (rdata->requestor == xev->xproperty.window &&
262
rdata->property == xev->xproperty.atom);
266
get_property (TargetData *tdata,
267
GsdClipboardManager *manager)
271
unsigned long length;
272
unsigned long remaining;
275
XGetWindowProperty (manager->priv->display,
276
manager->priv->window,
289
manager->priv->contents = list_remove (manager->priv->contents, tdata);
291
} else if (type == XA_INCR) {
298
tdata->length = length * clipboard_bytes_per_item (format);
299
tdata->format = format;
304
receive_incrementally (GsdClipboardManager *manager,
311
unsigned long length, nitems, remaining;
314
if (xev->xproperty.window != manager->priv->window)
317
list = list_find (manager->priv->contents,
318
(ListFindFunc) find_content_target, (void *) xev->xproperty.atom);
323
tdata = (TargetData *) list->data;
325
if (tdata->type != XA_INCR)
328
XGetWindowProperty (xev->xproperty.display,
329
xev->xproperty.window,
331
0, 0x1FFFFFFF, True, AnyPropertyType,
332
&type, &format, &nitems, &remaining, &data);
334
length = nitems * clipboard_bytes_per_item (format);
337
tdata->format = format;
339
if (!list_find (manager->priv->contents,
340
(ListFindFunc) find_content_type, (void *)XA_INCR)) {
341
/* all incremental transfers done */
342
send_selection_notify (manager, True);
343
manager->priv->requestor = None;
350
tdata->length = length;
352
tdata->data = realloc (tdata->data, tdata->length + length + 1);
353
memcpy (tdata->data + tdata->length, data, length + 1);
354
tdata->length += length;
363
send_incrementally (GsdClipboardManager *manager,
367
IncrConversion *rdata;
368
unsigned long length;
372
list = list_find (manager->priv->conversions,
373
(ListFindFunc) find_conversion_requestor, xev);
377
rdata = (IncrConversion *) list->data;
379
data = rdata->data->data + rdata->offset;
380
length = rdata->data->length - rdata->offset;
381
if (length > SELECTION_MAX_SIZE)
382
length = SELECTION_MAX_SIZE;
384
rdata->offset += length;
386
items = length / clipboard_bytes_per_item (rdata->data->format);
387
XChangeProperty (manager->priv->display, rdata->requestor,
388
rdata->property, rdata->data->type,
389
rdata->data->format, PropModeAppend,
393
clipboard_manager_watch_cb (manager,
399
manager->priv->conversions = list_remove (manager->priv->conversions, rdata);
400
conversion_free (rdata);
407
convert_clipboard_manager (GsdClipboardManager *manager,
412
unsigned long nitems;
413
unsigned long remaining;
414
Atom *targets = NULL;
416
if (xev->xselectionrequest.target == XA_SAVE_TARGETS) {
417
if (manager->priv->requestor != None || manager->priv->contents != NULL) {
418
/* We're in the middle of a conversion request, or own
419
* the CLIPBOARD already
421
finish_selection_request (manager, xev, False);
423
gdk_error_trap_push ();
425
clipboard_manager_watch_cb (manager,
426
xev->xselectionrequest.requestor,
430
XSelectInput (manager->priv->display,
431
xev->xselectionrequest.requestor,
432
StructureNotifyMask);
433
XSync (manager->priv->display, False);
435
if (gdk_error_trap_pop () != Success)
438
gdk_error_trap_push ();
440
if (xev->xselectionrequest.property != None) {
441
XGetWindowProperty (manager->priv->display,
442
xev->xselectionrequest.requestor,
443
xev->xselectionrequest.property,
444
0, 0x1FFFFFFF, False, XA_ATOM,
445
&type, &format, &nitems, &remaining,
446
(unsigned char **) &targets);
448
if (gdk_error_trap_pop () != Success) {
456
manager->priv->requestor = xev->xselectionrequest.requestor;
457
manager->priv->property = xev->xselectionrequest.property;
458
manager->priv->time = xev->xselectionrequest.time;
461
XConvertSelection (manager->priv->display, XA_CLIPBOARD,
462
XA_TARGETS, XA_TARGETS,
463
manager->priv->window, manager->priv->time);
465
save_targets (manager, targets, nitems);
467
} else if (xev->xselectionrequest.target == XA_TIMESTAMP) {
468
XChangeProperty (manager->priv->display,
469
xev->xselectionrequest.requestor,
470
xev->xselectionrequest.property,
471
XA_INTEGER, 32, PropModeReplace,
472
(unsigned char *) &manager->priv->timestamp, 1);
474
finish_selection_request (manager, xev, True);
475
} else if (xev->xselectionrequest.target == XA_TARGETS) {
479
targets[n_targets++] = XA_TARGETS;
480
targets[n_targets++] = XA_TIMESTAMP;
481
targets[n_targets++] = XA_SAVE_TARGETS;
483
XChangeProperty (manager->priv->display,
484
xev->xselectionrequest.requestor,
485
xev->xselectionrequest.property,
486
XA_ATOM, 32, PropModeReplace,
487
(unsigned char *) targets, n_targets);
489
finish_selection_request (manager, xev, True);
491
finish_selection_request (manager, xev, False);
495
convert_clipboard_target (IncrConversion *rdata,
496
GsdClipboardManager *manager)
503
XWindowAttributes atts;
505
if (rdata->target == XA_TARGETS) {
506
n_targets = list_length (manager->priv->contents) + 2;
507
targets = (Atom *) malloc (n_targets * sizeof (Atom));
511
targets[n_targets++] = XA_TARGETS;
512
targets[n_targets++] = XA_MULTIPLE;
514
for (list = manager->priv->contents; list; list = list->next) {
515
tdata = (TargetData *) list->data;
516
targets[n_targets++] = tdata->target;
519
XChangeProperty (manager->priv->display, rdata->requestor,
521
XA_ATOM, 32, PropModeReplace,
522
(unsigned char *) targets, n_targets);
525
/* Convert from stored CLIPBOARD data */
526
list = list_find (manager->priv->contents,
527
(ListFindFunc) find_content_target, (void *) rdata->target);
529
/* We got a target that we don't support */
533
tdata = (TargetData *)list->data;
534
if (tdata->type == XA_INCR) {
535
/* we haven't completely received this target yet */
536
rdata->property = None;
540
rdata->data = target_data_ref (tdata);
541
items = tdata->length / clipboard_bytes_per_item (tdata->format);
542
if (tdata->length <= SELECTION_MAX_SIZE)
543
XChangeProperty (manager->priv->display, rdata->requestor,
545
tdata->type, tdata->format, PropModeReplace,
548
/* start incremental transfer */
551
gdk_error_trap_push ();
553
XGetWindowAttributes (manager->priv->display, rdata->requestor, &atts);
555
clipboard_manager_watch_cb (manager,
561
XSelectInput (manager->priv->display, rdata->requestor,
562
atts.your_event_mask | PropertyChangeMask);
564
XChangeProperty (manager->priv->display, rdata->requestor,
566
XA_INCR, 32, PropModeReplace,
567
(unsigned char *) &items, 1);
569
XSync (manager->priv->display, False);
571
gdk_error_trap_pop_ignored ();
577
collect_incremental (IncrConversion *rdata,
578
GsdClipboardManager *manager)
580
if (rdata->offset >= 0)
581
manager->priv->conversions = list_prepend (manager->priv->conversions, rdata);
584
target_data_unref (rdata->data);
592
convert_clipboard (GsdClipboardManager *manager,
597
IncrConversion *rdata;
601
unsigned long nitems;
602
unsigned long remaining;
608
if (xev->xselectionrequest.target == XA_MULTIPLE) {
609
XGetWindowProperty (xev->xselectionrequest.display,
610
xev->xselectionrequest.requestor,
611
xev->xselectionrequest.property,
612
0, 0x1FFFFFFF, False, XA_ATOM_PAIR,
613
&type, &format, &nitems, &remaining,
614
(unsigned char **) &multiple);
616
if (type != XA_ATOM_PAIR || nitems == 0) {
622
for (i = 0; i < nitems; i += 2) {
623
rdata = (IncrConversion *) malloc (sizeof (IncrConversion));
624
rdata->requestor = xev->xselectionrequest.requestor;
625
rdata->target = multiple[i];
626
rdata->property = multiple[i+1];
629
conversions = list_prepend (conversions, rdata);
634
rdata = (IncrConversion *) malloc (sizeof (IncrConversion));
635
rdata->requestor = xev->xselectionrequest.requestor;
636
rdata->target = xev->xselectionrequest.target;
637
rdata->property = xev->xselectionrequest.property;
640
conversions = list_prepend (conversions, rdata);
643
list_foreach (conversions, (Callback) convert_clipboard_target, manager);
645
if (conversions->next == NULL &&
646
((IncrConversion *) conversions->data)->property == None) {
647
finish_selection_request (manager, xev, False);
651
for (list = conversions; list; list = list->next) {
652
rdata = (IncrConversion *)list->data;
653
multiple[i++] = rdata->target;
654
multiple[i++] = rdata->property;
656
XChangeProperty (xev->xselectionrequest.display,
657
xev->xselectionrequest.requestor,
658
xev->xselectionrequest.property,
659
XA_ATOM_PAIR, 32, PropModeReplace,
660
(unsigned char *) multiple, nitems);
662
finish_selection_request (manager, xev, True);
665
list_foreach (conversions, (Callback) collect_incremental, manager);
666
list_free (conversions);
673
clipboard_manager_process_event (GsdClipboardManager *manager,
678
unsigned long nitems;
679
unsigned long remaining;
684
switch (xev->xany.type) {
686
if (xev->xdestroywindow.window == manager->priv->requestor) {
687
list_foreach (manager->priv->contents, (Callback)target_data_unref, NULL);
688
list_free (manager->priv->contents);
689
manager->priv->contents = NULL;
691
clipboard_manager_watch_cb (manager,
692
manager->priv->requestor,
696
manager->priv->requestor = None;
700
if (xev->xproperty.state == PropertyNewValue) {
701
return receive_incrementally (manager, xev);
703
return send_incrementally (manager, xev);
707
if (xev->xany.window != manager->priv->window)
710
if (xev->xselectionclear.selection == XA_CLIPBOARD_MANAGER) {
711
/* We lost the manager selection */
712
if (manager->priv->contents) {
713
list_foreach (manager->priv->contents, (Callback)target_data_unref, NULL);
714
list_free (manager->priv->contents);
715
manager->priv->contents = NULL;
717
XSetSelectionOwner (manager->priv->display,
719
None, manager->priv->time);
724
if (xev->xselectionclear.selection == XA_CLIPBOARD) {
725
/* We lost the clipboard selection */
726
list_foreach (manager->priv->contents, (Callback)target_data_unref, NULL);
727
list_free (manager->priv->contents);
728
manager->priv->contents = NULL;
729
clipboard_manager_watch_cb (manager,
730
manager->priv->requestor,
734
manager->priv->requestor = None;
740
case SelectionNotify:
741
if (xev->xany.window != manager->priv->window)
744
if (xev->xselection.selection == XA_CLIPBOARD) {
745
/* a CLIPBOARD conversion is done */
746
if (xev->xselection.property == XA_TARGETS) {
747
XGetWindowProperty (xev->xselection.display,
748
xev->xselection.requestor,
749
xev->xselection.property,
750
0, 0x1FFFFFFF, True, XA_ATOM,
751
&type, &format, &nitems, &remaining,
752
(unsigned char **) &targets);
754
save_targets (manager, targets, nitems);
755
} else if (xev->xselection.property == XA_MULTIPLE) {
758
tmp = list_copy (manager->priv->contents);
759
list_foreach (tmp, (Callback) get_property, manager);
762
manager->priv->time = xev->xselection.time;
763
XSetSelectionOwner (manager->priv->display, XA_CLIPBOARD,
764
manager->priv->window, manager->priv->time);
766
if (manager->priv->property != None)
767
XChangeProperty (manager->priv->display,
768
manager->priv->requestor,
769
manager->priv->property,
770
XA_ATOM, 32, PropModeReplace,
771
(unsigned char *)&XA_NULL, 1);
773
if (!list_find (manager->priv->contents,
774
(ListFindFunc)find_content_type, (void *)XA_INCR)) {
775
/* all transfers done */
776
send_selection_notify (manager, True);
777
clipboard_manager_watch_cb (manager,
778
manager->priv->requestor,
782
manager->priv->requestor = None;
785
else if (xev->xselection.property == None) {
786
send_selection_notify (manager, False);
787
clipboard_manager_watch_cb (manager,
788
manager->priv->requestor,
792
manager->priv->requestor = None;
799
case SelectionRequest:
800
if (xev->xany.window != manager->priv->window) {
804
if (xev->xselectionrequest.selection == XA_CLIPBOARD_MANAGER) {
805
convert_clipboard_manager (manager, xev);
807
} else if (xev->xselectionrequest.selection == XA_CLIPBOARD) {
808
convert_clipboard (manager, xev);
819
static GdkFilterReturn
820
clipboard_manager_event_filter (GdkXEvent *xevent,
822
GsdClipboardManager *manager)
824
if (clipboard_manager_process_event (manager, (XEvent *)xevent)) {
825
return GDK_FILTER_REMOVE;
827
return GDK_FILTER_CONTINUE;
832
clipboard_manager_watch_cb (GsdClipboardManager *manager,
841
display = gdk_display_get_default ();
842
gdkwin = gdk_x11_window_lookup_for_display (display, window);
845
if (gdkwin == NULL) {
846
gdkwin = gdk_x11_window_foreign_new_for_display (display, window);
848
g_object_ref (gdkwin);
851
gdk_window_add_filter (gdkwin,
852
(GdkFilterFunc)clipboard_manager_event_filter,
855
if (gdkwin == NULL) {
858
gdk_window_remove_filter (gdkwin,
859
(GdkFilterFunc)clipboard_manager_event_filter,
861
g_object_unref (gdkwin);
866
start_clipboard_idle_cb (GsdClipboardManager *manager)
868
XClientMessageEvent xev;
871
gnome_settings_profile_start (NULL);
873
init_atoms (manager->priv->display);
875
/* check if there is a clipboard manager running */
876
if (XGetSelectionOwner (manager->priv->display, XA_CLIPBOARD_MANAGER)) {
877
g_warning ("Clipboard manager is already running.");
881
manager->priv->contents = NULL;
882
manager->priv->conversions = NULL;
883
manager->priv->requestor = None;
885
manager->priv->window = XCreateSimpleWindow (manager->priv->display,
886
DefaultRootWindow (manager->priv->display),
888
WhitePixel (manager->priv->display,
889
DefaultScreen (manager->priv->display)),
890
WhitePixel (manager->priv->display,
891
DefaultScreen (manager->priv->display)));
892
clipboard_manager_watch_cb (manager,
893
manager->priv->window,
897
XSelectInput (manager->priv->display,
898
manager->priv->window,
900
manager->priv->timestamp = get_server_time (manager->priv->display, manager->priv->window);
902
XSetSelectionOwner (manager->priv->display,
903
XA_CLIPBOARD_MANAGER,
904
manager->priv->window,
905
manager->priv->timestamp);
907
/* Check to see if we managed to claim the selection. If not,
908
* we treat it as if we got it then immediately lost it
910
if (XGetSelectionOwner (manager->priv->display, XA_CLIPBOARD_MANAGER) == manager->priv->window) {
911
xev.type = ClientMessage;
912
xev.window = DefaultRootWindow (manager->priv->display);
913
xev.message_type = XA_MANAGER;
915
xev.data.l[0] = manager->priv->timestamp;
916
xev.data.l[1] = XA_CLIPBOARD_MANAGER;
917
xev.data.l[2] = manager->priv->window;
918
xev.data.l[3] = 0; /* manager specific data */
919
xev.data.l[4] = 0; /* manager specific data */
921
XSendEvent (manager->priv->display,
922
DefaultRootWindow (manager->priv->display),
927
clipboard_manager_watch_cb (manager,
928
manager->priv->window,
932
/* FIXME: manager->priv->terminate (manager->priv->cb_data); */
935
gnome_settings_profile_end (NULL);
937
manager->priv->start_idle_id = 0;
943
gsd_clipboard_manager_start (GsdClipboardManager *manager,
946
gnome_settings_profile_start (NULL);
948
manager->priv->start_idle_id = g_idle_add ((GSourceFunc) start_clipboard_idle_cb, manager);
950
gnome_settings_profile_end (NULL);
956
gsd_clipboard_manager_stop (GsdClipboardManager *manager)
958
g_debug ("Stopping clipboard manager");
960
if (manager->priv->window != None) {
961
clipboard_manager_watch_cb (manager,
962
manager->priv->window,
966
XDestroyWindow (manager->priv->display, manager->priv->window);
967
manager->priv->window = None;
970
if (manager->priv->conversions != NULL) {
971
list_foreach (manager->priv->conversions, (Callback) conversion_free, NULL);
972
list_free (manager->priv->conversions);
973
manager->priv->conversions = NULL;
976
if (manager->priv->contents != NULL) {
977
list_foreach (manager->priv->contents, (Callback) target_data_unref, NULL);
978
list_free (manager->priv->contents);
979
manager->priv->contents = NULL;
984
gsd_clipboard_manager_constructor (GType type,
985
guint n_construct_properties,
986
GObjectConstructParam *construct_properties)
988
GsdClipboardManager *clipboard_manager;
990
clipboard_manager = GSD_CLIPBOARD_MANAGER (G_OBJECT_CLASS (gsd_clipboard_manager_parent_class)->constructor (type,
991
n_construct_properties,
992
construct_properties));
994
return G_OBJECT (clipboard_manager);
998
gsd_clipboard_manager_class_init (GsdClipboardManagerClass *klass)
1000
GObjectClass *object_class = G_OBJECT_CLASS (klass);
1002
object_class->constructor = gsd_clipboard_manager_constructor;
1003
object_class->finalize = gsd_clipboard_manager_finalize;
1005
g_type_class_add_private (klass, sizeof (GsdClipboardManagerPrivate));
1009
gsd_clipboard_manager_init (GsdClipboardManager *manager)
1011
manager->priv = GSD_CLIPBOARD_MANAGER_GET_PRIVATE (manager);
1013
manager->priv->display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
1018
gsd_clipboard_manager_finalize (GObject *object)
1020
GsdClipboardManager *clipboard_manager;
1022
g_return_if_fail (object != NULL);
1023
g_return_if_fail (GSD_IS_CLIPBOARD_MANAGER (object));
1025
clipboard_manager = GSD_CLIPBOARD_MANAGER (object);
1027
g_return_if_fail (clipboard_manager->priv != NULL);
1029
if (clipboard_manager->priv->start_idle_id !=0)
1030
g_source_remove (clipboard_manager->priv->start_idle_id);
1032
G_OBJECT_CLASS (gsd_clipboard_manager_parent_class)->finalize (object);
1035
GsdClipboardManager *
1036
gsd_clipboard_manager_new (void)
1038
if (manager_object != NULL) {
1039
g_object_ref (manager_object);
1041
manager_object = g_object_new (GSD_TYPE_CLIPBOARD_MANAGER, NULL);
1042
g_object_add_weak_pointer (manager_object,
1043
(gpointer *) &manager_object);
1046
return GSD_CLIPBOARD_MANAGER (manager_object);