No issues found
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-clipboard.c: clipboard object
4 *
5 * Copyright 2009 Intel Corporation.
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms and conditions of the GNU Lesser General Public License,
9 * version 2.1, as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
14 * more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /**
21 * SECTION:st-clipboard
22 * @short_description: a simple representation of the X clipboard
23 *
24 * #StCliboard is a very simple object representation of the clipboard
25 * available to applications. Text is always assumed to be UTF-8 and non-text
26 * items are not handled.
27 */
28
29
30 #include "st-clipboard.h"
31 #include <X11/Xlib.h>
32 #include <X11/Xatom.h>
33 #include <clutter/x11/clutter-x11.h>
34 #include <string.h>
35
36 G_DEFINE_TYPE (StClipboard, st_clipboard, G_TYPE_OBJECT)
37
38 #define CLIPBOARD_PRIVATE(o) \
39 (G_TYPE_INSTANCE_GET_PRIVATE ((o), ST_TYPE_CLIPBOARD, StClipboardPrivate))
40
41 struct _StClipboardPrivate
42 {
43 Window clipboard_window;
44 gchar *clipboard_text;
45
46 Atom *supported_targets;
47 gint n_targets;
48 };
49
50 typedef struct _EventFilterData EventFilterData;
51 struct _EventFilterData
52 {
53 StClipboard *clipboard;
54 StClipboardCallbackFunc callback;
55 gpointer user_data;
56 };
57
58 static Atom __atom_clip = None;
59 static Atom __utf8_string = None;
60 static Atom __atom_targets = None;
61
62 static void
63 st_clipboard_get_property (GObject *object,
64 guint property_id,
65 GValue *value,
66 GParamSpec *pspec)
67 {
68 switch (property_id)
69 {
70 default:
71 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
72 }
73 }
74
75 static void
76 st_clipboard_set_property (GObject *object,
77 guint property_id,
78 const GValue *value,
79 GParamSpec *pspec)
80 {
81 switch (property_id)
82 {
83 default:
84 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
85 }
86 }
87
88 static void
89 st_clipboard_dispose (GObject *object)
90 {
91 G_OBJECT_CLASS (st_clipboard_parent_class)->dispose (object);
92 }
93
94 static void
95 st_clipboard_finalize (GObject *object)
96 {
97 StClipboardPrivate *priv = ((StClipboard *) object)->priv;
98
99 g_free (priv->clipboard_text);
100 priv->clipboard_text = NULL;
101
102 g_free (priv->supported_targets);
103 priv->supported_targets = NULL;
104 priv->n_targets = 0;
105
106 G_OBJECT_CLASS (st_clipboard_parent_class)->finalize (object);
107 }
108
109 static ClutterX11FilterReturn
110 st_clipboard_provider (XEvent *xev,
111 ClutterEvent *cev,
112 StClipboard *clipboard)
113 {
114 XSelectionEvent notify_event;
115 XSelectionRequestEvent *req_event;
116
117 if (xev->type != SelectionRequest)
118 return CLUTTER_X11_FILTER_CONTINUE;
119
120 req_event = &xev->xselectionrequest;
121
122 clutter_x11_trap_x_errors ();
123
124 if (req_event->target == __atom_targets)
125 {
126 XChangeProperty (req_event->display,
127 req_event->requestor,
128 req_event->property,
129 XA_ATOM,
130 32,
131 PropModeReplace,
132 (guchar*) clipboard->priv->supported_targets,
133 clipboard->priv->n_targets);
134 }
135 else
136 {
137 XChangeProperty (req_event->display,
138 req_event->requestor,
139 req_event->property,
140 req_event->target,
141 8,
142 PropModeReplace,
143 (guchar*) clipboard->priv->clipboard_text,
144 strlen (clipboard->priv->clipboard_text));
145 }
146
147 notify_event.type = SelectionNotify;
148 notify_event.display = req_event->display;
149 notify_event.requestor = req_event->requestor;
150 notify_event.selection = req_event->selection;
151 notify_event.target = req_event->target;
152 notify_event.time = req_event->time;
153
154 if (req_event->property == None)
155 notify_event.property = req_event->target;
156 else
157 notify_event.property = req_event->property;
158
159 /* notify the requestor that they have a copy of the selection */
160 XSendEvent (req_event->display, req_event->requestor, False, 0,
161 (XEvent *) ¬ify_event);
162 /* Make it happen non async */
163 XSync (clutter_x11_get_default_display(), FALSE);
164
165 clutter_x11_untrap_x_errors (); /* FIXME: Warn here on fail ? */
166
167 return CLUTTER_X11_FILTER_REMOVE;
168 }
169
170
171 static void
172 st_clipboard_class_init (StClipboardClass *klass)
173 {
174 GObjectClass *object_class = G_OBJECT_CLASS (klass);
175
176 g_type_class_add_private (klass, sizeof (StClipboardPrivate));
177
178 object_class->get_property = st_clipboard_get_property;
179 object_class->set_property = st_clipboard_set_property;
180 object_class->dispose = st_clipboard_dispose;
181 object_class->finalize = st_clipboard_finalize;
182 }
183
184 static void
185 st_clipboard_init (StClipboard *self)
186 {
187 Display *dpy;
188 StClipboardPrivate *priv;
189
190 priv = self->priv = CLIPBOARD_PRIVATE (self);
191
192 priv->clipboard_window =
193 XCreateSimpleWindow (clutter_x11_get_default_display (),
194 clutter_x11_get_root_window (),
195 -1, -1, 1, 1, 0, 0, 0);
196
197 dpy = clutter_x11_get_default_display ();
198
199 /* Only create once */
200 if (__atom_clip == None)
201 __atom_clip = XInternAtom (dpy, "CLIPBOARD", 0);
202
203 if (__utf8_string == None)
204 __utf8_string = XInternAtom (dpy, "UTF8_STRING", 0);
205
206 if (__atom_targets == None)
207 __atom_targets = XInternAtom (dpy, "TARGETS", 0);
208
209 priv->n_targets = 2;
210 priv->supported_targets = g_new (Atom, priv->n_targets);
211
212 priv->supported_targets[0] = __utf8_string;
213 priv->supported_targets[1] = __atom_targets;
214
215 clutter_x11_add_filter ((ClutterX11FilterFunc) st_clipboard_provider,
216 self);
217 }
218
219 static ClutterX11FilterReturn
220 st_clipboard_x11_event_filter (XEvent *xev,
221 ClutterEvent *cev,
222 EventFilterData *filter_data)
223 {
224 Atom actual_type;
225 int actual_format, result;
226 unsigned long nitems, bytes_after;
227 unsigned char *data = NULL;
228
229 if(xev->type != SelectionNotify)
230 return CLUTTER_X11_FILTER_CONTINUE;
231
232 if (xev->xselection.property == None)
233 {
234 /* clipboard empty */
235 filter_data->callback (filter_data->clipboard,
236 NULL,
237 filter_data->user_data);
238
239 clutter_x11_remove_filter ((ClutterX11FilterFunc) st_clipboard_x11_event_filter,
240 filter_data);
241 g_free (filter_data);
242 return CLUTTER_X11_FILTER_REMOVE;
243 }
244
245 clutter_x11_trap_x_errors ();
246
247 result = XGetWindowProperty (xev->xselection.display,
248 xev->xselection.requestor,
249 xev->xselection.property,
250 0L, G_MAXINT,
251 True,
252 AnyPropertyType,
253 &actual_type,
254 &actual_format,
255 &nitems,
256 &bytes_after,
257 &data);
258
259 if (clutter_x11_untrap_x_errors () || result != Success)
260 {
261 /* FIXME: handle failure better */
262 g_warning ("Clipboard: prop retrival failed");
263 }
264
265 filter_data->callback (filter_data->clipboard, (char*) data,
266 filter_data->user_data);
267
268 clutter_x11_remove_filter
269 ((ClutterX11FilterFunc) st_clipboard_x11_event_filter,
270 filter_data);
271
272 g_free (filter_data);
273
274 if (data)
275 XFree (data);
276
277 return CLUTTER_X11_FILTER_REMOVE;
278 }
279
280 /**
281 * st_clipboard_get_default:
282 *
283 * Get the global #StClipboard object that represents the clipboard.
284 *
285 * Returns: (transfer none): a #StClipboard owned by St and must not be
286 * unrefferenced or freed.
287 */
288 StClipboard*
289 st_clipboard_get_default (void)
290 {
291 static StClipboard *default_clipboard = NULL;
292
293 if (!default_clipboard)
294 {
295 default_clipboard = g_object_new (ST_TYPE_CLIPBOARD, NULL);
296 }
297
298 return default_clipboard;
299 }
300
301 /**
302 * st_clipboard_get_text:
303 * @clipboard: A #StCliboard
304 * @callback: (scope async): function to be called when the text is retreived
305 * @user_data: data to be passed to the callback
306 *
307 * Request the data from the clipboard in text form. @callback is executed
308 * when the data is retreived.
309 *
310 */
311 void
312 st_clipboard_get_text (StClipboard *clipboard,
313 StClipboardCallbackFunc callback,
314 gpointer user_data)
315 {
316 EventFilterData *data;
317
318 Display *dpy;
319
320 g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
321 g_return_if_fail (callback != NULL);
322
323 data = g_new0 (EventFilterData, 1);
324 data->clipboard = clipboard;
325 data->callback = callback;
326 data->user_data = user_data;
327
328 clutter_x11_add_filter ((ClutterX11FilterFunc) st_clipboard_x11_event_filter,
329 data);
330
331 dpy = clutter_x11_get_default_display ();
332
333 clutter_x11_trap_x_errors (); /* safety on */
334
335 XConvertSelection (dpy,
336 __atom_clip,
337 __utf8_string, __utf8_string,
338 clipboard->priv->clipboard_window,
339 CurrentTime);
340
341 clutter_x11_untrap_x_errors ();
342 }
343
344 /**
345 * st_clipboard_set_text:
346 * @clipboard: A #StClipboard
347 * @text: text to copy to the clipboard
348 *
349 * Sets text as the current contents of the clipboard.
350 *
351 */
352 void
353 st_clipboard_set_text (StClipboard *clipboard,
354 const gchar *text)
355 {
356 StClipboardPrivate *priv;
357 Display *dpy;
358
359 g_return_if_fail (ST_IS_CLIPBOARD (clipboard));
360 g_return_if_fail (text != NULL);
361
362 priv = clipboard->priv;
363
364 /* make a copy of the text */
365 g_free (priv->clipboard_text);
366 priv->clipboard_text = g_strdup (text);
367
368 /* tell X we own the clipboard selection */
369 dpy = clutter_x11_get_default_display ();
370
371 clutter_x11_trap_x_errors ();
372
373 XSetSelectionOwner (dpy, __atom_clip, priv->clipboard_window, CurrentTime);
374 XSync (dpy, FALSE);
375
376 clutter_x11_untrap_x_errors ();
377 }