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