1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
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>
7  * Copyright (C) 2009 Mike Massonnet <mmassonnet@xfce.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22  */
23 
24 #include "config.h"
25 
26 #include <glib.h>
27 #include <gdk/gdkx.h>
28 #include <gtk/gtk.h>
29 #include <X11/Xlib.h>
30 #include <X11/Xatom.h>
31 
32 #include "gsd-clipboard-manager.h"
33 
34 
35 
36 struct GsdClipboardManagerPrivate
37 {
38         GtkClipboard *default_clipboard;
39         GtkClipboard *primary_clipboard;
40 
41         GSList       *default_cache;
42         gboolean      default_internal_change;
43 
44         gchar        *primary_cache;
45         gboolean      primary_timeout;
46         gboolean      primary_internal_change;
47 
48         GtkWidget    *window;
49 };
50 
51 G_DEFINE_TYPE_WITH_PRIVATE (GsdClipboardManager, gsd_clipboard_manager, G_TYPE_OBJECT)
52 
53 static void     gsd_clipboard_manager_finalize    (GObject                  *object);
54 
55 
56 Atom XA_CLIPBOARD_MANAGER;
57 Atom XA_MANAGER;
58 
59 static void
init_atoms(Display * display)60 init_atoms (Display *display)
61 {
62         static int _init_atoms = 0;
63 
64         if (_init_atoms > 0) {
65                 return;
66         }
67 
68         XA_CLIPBOARD_MANAGER = XInternAtom (display, "CLIPBOARD_MANAGER", False);
69         XA_MANAGER = XInternAtom (display, "MANAGER", False);
70 
71         _init_atoms = 1;
72 }
73 
74 
75 static void
cb_selection_data_free(gpointer data)76 cb_selection_data_free (gpointer data)
77 {
78         gtk_selection_data_free ((GtkSelectionData *) data);
79 }
80 
81 
82 static void
default_clipboard_store(GsdClipboardManager * manager)83 default_clipboard_store (GsdClipboardManager *manager)
84 {
85         GtkSelectionData *selection_data;
86         GdkAtom          *atoms;
87         gint              n_atoms;
88         gint              i;
89 
90         if (!gtk_clipboard_wait_for_targets (manager->priv->default_clipboard, &atoms, &n_atoms)) {
91                 return;
92         }
93 
94         if (manager->priv->default_cache != NULL) {
95                 g_slist_free_full (manager->priv->default_cache, cb_selection_data_free);
96                 manager->priv->default_cache = NULL;
97         }
98 
99         for (i = 0; i < n_atoms; i++) {
100                 if (atoms[i] == gdk_atom_intern_static_string ("TARGETS")
101                     || atoms[i] == gdk_atom_intern_static_string ("MULTIPLE")
102                     || atoms[i] == gdk_atom_intern_static_string ("DELETE")
103                     || atoms[i] == gdk_atom_intern_static_string ("INSERT_PROPERTY")
104                     || atoms[i] == gdk_atom_intern_static_string ("INSERT_SELECTION")
105                     || atoms[i] == gdk_atom_intern_static_string ("PIXMAP")) {
106                         continue;
107                 }
108 
109                 selection_data = gtk_clipboard_wait_for_contents (manager->priv->default_clipboard, atoms[i]);
110                 if (selection_data == NULL) {
111                         continue;
112                 }
113 
114                 manager->priv->default_cache = g_slist_prepend (manager->priv->default_cache, selection_data);
115         }
116 }
117 
118 static void
default_clipboard_get_func(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,GsdClipboardManager * manager)119 default_clipboard_get_func (GtkClipboard *clipboard,
120                             GtkSelectionData *selection_data,
121                             guint info,
122                             GsdClipboardManager *manager)
123 {
124         GSList           *list;
125         GtkSelectionData *selection_data_cache = NULL;
126 
127         list = manager->priv->default_cache;
128         for (; list != NULL && list->next != NULL; list = list->next) {
129                 selection_data_cache = list->data;
130                 if (gtk_selection_data_get_target (selection_data) ==
131                     gtk_selection_data_get_target (selection_data_cache)) {
132                         break;
133                 }
134                 selection_data_cache = NULL;
135         }
136         if (selection_data_cache == NULL) {
137                 return;
138         }
139 
140         gtk_selection_data_set (selection_data,
141                                 gtk_selection_data_get_target (selection_data_cache),
142                                 gtk_selection_data_get_format (selection_data_cache),
143                                 gtk_selection_data_get_data (selection_data_cache),
144                                 gtk_selection_data_get_length (selection_data_cache));
145 }
146 
147 static void
default_clipboard_clear_func(GtkClipboard * clipboard,GsdClipboardManager * manager)148 default_clipboard_clear_func (GtkClipboard *clipboard,
149                               GsdClipboardManager *manager)
150 {
151         return;
152 }
153 
154 static void
default_clipboard_restore(GsdClipboardManager * manager)155 default_clipboard_restore (GsdClipboardManager *manager)
156 {
157         GtkTargetList    *target_list;
158         GtkTargetEntry   *targets;
159         gint              n_targets;
160         GtkSelectionData *sdata;
161         GSList           *list;
162 
163         list = manager->priv->default_cache;
164         if (list == NULL) {
165                 return;
166         }
167         target_list = gtk_target_list_new (NULL, 0);
168         for (; list->next != NULL; list = list->next) {
169                 sdata = list->data;
170                 gtk_target_list_add (target_list,
171                                      gtk_selection_data_get_target (sdata),
172                                      0, 0);
173         }
174         targets = gtk_target_table_new_from_list (target_list, &n_targets);
175         gtk_target_list_unref (target_list);
176 
177         gtk_clipboard_set_with_data (manager->priv->default_clipboard,
178                                      targets, n_targets,
179                                      (GtkClipboardGetFunc)default_clipboard_get_func,
180                                      (GtkClipboardClearFunc)default_clipboard_clear_func,
181                                      manager);
182 }
183 
184 static void
default_clipboard_owner_change(GsdClipboardManager * manager,GdkEventOwnerChange * event)185 default_clipboard_owner_change (GsdClipboardManager *manager,
186                                 GdkEventOwnerChange *event)
187 {
188         if (event->send_event == TRUE) {
189                 return;
190         }
191 
192         if (event->owner != 0) {
193                 if (manager->priv->default_internal_change) {
194                         manager->priv->default_internal_change = FALSE;
195                         return;
196                 }
197                 default_clipboard_store (manager);
198         }
199         else {
200                 /* This 'bug' happens with Mozilla applications, it means that
201                  * we restored the clipboard (we own it now) but somehow we are
202                  * being noticed twice about that fact where first the owner is
203                  * 0 (which is when we must restore) but then again where the
204                  * owner is ourself (which is what normally only happens and
205                  * also that means that we have to store the clipboard content
206                  * e.g. owner is not 0). By the second time we would store
207                  * ourself back with an empty clipboard... solution is to jump
208                  * over the first time and don't try to restore empty data. */
209                 if (manager->priv->default_internal_change) {
210                         return;
211                 }
212 
213                 manager->priv->default_internal_change = TRUE;
214                 default_clipboard_restore (manager);
215         }
216 }
217 
218 static gboolean
primary_clipboard_store(gpointer user_data)219 primary_clipboard_store (gpointer user_data)
220 {
221         GsdClipboardManager *manager = user_data;
222         GdkModifierType state = 0;
223         gchar *text;
224         GdkDisplay* display = gdk_display_get_default ();
225         GdkSeat *seat = gdk_display_get_default_seat (display);
226         GdkDevice *device = gdk_seat_get_pointer (seat);
227         GdkScreen* screen = gdk_screen_get_default ();
228         GdkWindow * root_win = gdk_screen_get_root_window (screen);
229 
230         gdk_window_get_device_position (root_win, device, NULL, NULL, &state);
231         if (state & (GDK_BUTTON1_MASK|GDK_SHIFT_MASK)) {
232                 return TRUE;
233         }
234 
235         text = gtk_clipboard_wait_for_text (manager->priv->primary_clipboard);
236         if (text != NULL) {
237                 g_free (manager->priv->primary_cache);
238                 manager->priv->primary_cache = text;
239         }
240 
241         manager->priv->primary_timeout = 0;
242 
243         return FALSE;
244 }
245 
246 static gboolean
primary_clipboard_restore(gpointer user_data)247 primary_clipboard_restore (gpointer user_data)
248 {
249         GsdClipboardManager *manager = user_data;
250         if (manager->priv->primary_cache != NULL) {
251                 gtk_clipboard_set_text (manager->priv->primary_clipboard,
252                                         manager->priv->primary_cache,
253                                         -1);
254                 manager->priv->primary_internal_change = TRUE;
255         }
256 
257         return FALSE;
258 }
259 
260 static void
primary_clipboard_owner_change(GsdClipboardManager * manager,GdkEventOwnerChange * event)261 primary_clipboard_owner_change (GsdClipboardManager *manager,
262                                 GdkEventOwnerChange *event)
263 {
264         if (event->send_event == TRUE) {
265                 return;
266         }
267         if (manager->priv->primary_timeout != 0) {
268                 g_source_remove (manager->priv->primary_timeout);
269                 manager->priv->primary_timeout = 0;
270         }
271 
272         if (event->owner != 0) {
273                 if (manager->priv->primary_internal_change == TRUE) {
274                         manager->priv->primary_internal_change = FALSE;
275                         return;
276                 }
277                 manager->priv->primary_timeout = g_timeout_add (250, primary_clipboard_store, manager);
278         }
279         else if (gtk_clipboard_wait_is_text_available (manager->priv->primary_clipboard) == FALSE) {
280                 manager->priv->primary_timeout = g_timeout_add (250, primary_clipboard_restore, manager);
281         }
282 }
283 
284 static gboolean
start_clipboard_idle_cb(gpointer user_data)285 start_clipboard_idle_cb (gpointer user_data)
286 {
287         GsdClipboardManager *manager = user_data;
288         XClientMessageEvent     xev;
289         Display                *display;
290         Window                  window;
291         Time                    timestamp;
292 
293         display = gdk_x11_get_default_xdisplay ();
294         init_atoms (display);
295 
296         /* Check if there is a clipboard manager running */
297         if (gdk_display_supports_clipboard_persistence (gdk_display_get_default ())) {
298                 g_warning ("Clipboard manager is already running.");
299                 return FALSE;
300         }
301 
302         manager->priv->window = gtk_invisible_new ();
303         gtk_widget_realize (manager->priv->window);
304 
305         window = GDK_WINDOW_XID (gtk_widget_get_window (manager->priv->window));
306         timestamp = GDK_CURRENT_TIME;
307 
308         XSelectInput (display, window, PropertyChangeMask);
309         XSetSelectionOwner (display, XA_CLIPBOARD_MANAGER, window, timestamp);
310 
311         g_signal_connect_swapped (manager->priv->default_clipboard, "owner-change",
312                                   G_CALLBACK (default_clipboard_owner_change), manager);
313         g_signal_connect_swapped (manager->priv->primary_clipboard, "owner-change",
314                                   G_CALLBACK (primary_clipboard_owner_change), manager);
315 
316         /* Check to see if we managed to claim the selection. If not,
317          * we treat it as if we got it then immediately lost it
318          */
319         if (XGetSelectionOwner (display, XA_CLIPBOARD_MANAGER) == window) {
320                 xev.type = ClientMessage;
321                 xev.window = DefaultRootWindow (display);
322                 xev.message_type = XA_MANAGER;
323                 xev.format = 32;
324                 xev.data.l[0] = timestamp;
325                 xev.data.l[1] = XA_CLIPBOARD_MANAGER;
326                 xev.data.l[2] = window;
327                 xev.data.l[3] = 0;      /* manager specific data */
328                 xev.data.l[4] = 0;      /* manager specific data */
329 
330                 XSendEvent (display, DefaultRootWindow (display), False,
331                             StructureNotifyMask, (XEvent *)&xev);
332         } else {
333                 gsd_clipboard_manager_stop (manager);
334         }
335 
336         return FALSE;
337 }
338 
339 gboolean
gsd_clipboard_manager_start(GsdClipboardManager * manager,GError ** error)340 gsd_clipboard_manager_start (GsdClipboardManager *manager,
341                              GError             **error)
342 {
343         g_idle_add (start_clipboard_idle_cb, manager);
344         return TRUE;
345 }
346 
347 void
gsd_clipboard_manager_stop(GsdClipboardManager * manager)348 gsd_clipboard_manager_stop (GsdClipboardManager *manager)
349 {
350         g_debug ("Stopping clipboard manager");
351 
352         g_signal_handlers_disconnect_by_func (manager->priv->default_clipboard,
353                                               default_clipboard_owner_change, manager);
354         g_signal_handlers_disconnect_by_func (manager->priv->primary_clipboard,
355                                               primary_clipboard_owner_change, manager);
356         gtk_widget_destroy (manager->priv->window);
357 
358         if (manager->priv->default_cache != NULL) {
359                 g_slist_free_full (manager->priv->default_cache, cb_selection_data_free);
360                 manager->priv->default_cache = NULL;
361         }
362         if (manager->priv->primary_cache != NULL) {
363                 g_free (manager->priv->primary_cache);
364         }
365 }
366 
367 static GObject *
gsd_clipboard_manager_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)368 gsd_clipboard_manager_constructor (GType                  type,
369                                    guint                  n_construct_properties,
370                                    GObjectConstructParam *construct_properties)
371 {
372         GsdClipboardManager      *clipboard_manager;
373 
374         clipboard_manager = GSD_CLIPBOARD_MANAGER (G_OBJECT_CLASS (gsd_clipboard_manager_parent_class)->constructor (type,
375                                                                                                       n_construct_properties,
376                                                                                                       construct_properties));
377 
378         return G_OBJECT (clipboard_manager);
379 }
380 
381 static void
gsd_clipboard_manager_class_init(GsdClipboardManagerClass * klass)382 gsd_clipboard_manager_class_init (GsdClipboardManagerClass *klass)
383 {
384         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
385 
386         object_class->constructor = gsd_clipboard_manager_constructor;
387         object_class->finalize = gsd_clipboard_manager_finalize;
388 }
389 
390 static void
gsd_clipboard_manager_init(GsdClipboardManager * manager)391 gsd_clipboard_manager_init (GsdClipboardManager *manager)
392 {
393         manager->priv = gsd_clipboard_manager_get_instance_private (manager);
394 
395         manager->priv->default_clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
396         manager->priv->primary_clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
397 
398         manager->priv->default_cache = NULL;
399         manager->priv->primary_cache = NULL;
400 }
401 
402 static void
gsd_clipboard_manager_finalize(GObject * object)403 gsd_clipboard_manager_finalize (GObject *object)
404 {
405         GsdClipboardManager *clipboard_manager;
406 
407         g_return_if_fail (object != NULL);
408         g_return_if_fail (GSD_IS_CLIPBOARD_MANAGER (object));
409 
410         clipboard_manager = GSD_CLIPBOARD_MANAGER (object);
411 
412         g_return_if_fail (clipboard_manager->priv != NULL);
413 
414         G_OBJECT_CLASS (gsd_clipboard_manager_parent_class)->finalize (object);
415 }
416 
417 GsdClipboardManager *
gsd_clipboard_manager_new(void)418 gsd_clipboard_manager_new (void)
419 {
420         static gpointer singleton = NULL;
421 
422         if (singleton != NULL) {
423                 g_object_ref (singleton);
424         } else {
425                 singleton = g_object_new (GSD_TYPE_CLIPBOARD_MANAGER, NULL);
426                 g_object_add_weak_pointer (singleton, (gpointer *) &singleton);
427         }
428 
429         return GSD_CLIPBOARD_MANAGER (singleton);
430 }
431