1 /* GDK - The GIMP Drawing Kit
2 * Copyright (C) 2017 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include "gdkprimary-wayland.h"
21
22 #include "gdkclipboardprivate.h"
23 #include "gdkcontentformats.h"
24 #include "gdkintl.h"
25 #include "gdkprivate-wayland.h"
26 #include "gdk-private.h"
27
28 #include <glib-unix.h>
29 #include <gio/gunixinputstream.h>
30 #include <gio/gunixoutputstream.h>
31
32 typedef struct _GdkWaylandPrimaryClass GdkWaylandPrimaryClass;
33
34 struct _GdkWaylandPrimary
35 {
36 GdkClipboard parent;
37
38 struct zwp_primary_selection_device_v1 *primary_data_device;
39
40 struct zwp_primary_selection_offer_v1 *pending;
41 GdkContentFormatsBuilder *pending_builder;
42
43 struct zwp_primary_selection_offer_v1 *offer;
44 GdkContentFormats *offer_formats;
45
46 struct zwp_primary_selection_source_v1 *source;
47 };
48
49 struct _GdkWaylandPrimaryClass
50 {
51 GdkClipboardClass parent_class;
52 };
53
G_DEFINE_TYPE(GdkWaylandPrimary,gdk_wayland_primary,GDK_TYPE_CLIPBOARD)54 G_DEFINE_TYPE (GdkWaylandPrimary, gdk_wayland_primary, GDK_TYPE_CLIPBOARD)
55
56 static void
57 gdk_wayland_primary_discard_pending (GdkWaylandPrimary *cb)
58 {
59 if (cb->pending_builder)
60 {
61 GdkContentFormats *ignore = gdk_content_formats_builder_free_to_formats (cb->pending_builder);
62 gdk_content_formats_unref (ignore);
63 cb->pending_builder = NULL;
64 }
65 g_clear_pointer (&cb->pending, zwp_primary_selection_offer_v1_destroy);
66 }
67
68 static void
gdk_wayland_primary_discard_offer(GdkWaylandPrimary * cb)69 gdk_wayland_primary_discard_offer (GdkWaylandPrimary *cb)
70 {
71 g_clear_pointer (&cb->offer_formats, gdk_content_formats_unref);
72 g_clear_pointer (&cb->offer, zwp_primary_selection_offer_v1_destroy);
73 }
74
75 static void
gdk_wayland_primary_discard_source(GdkWaylandPrimary * cb)76 gdk_wayland_primary_discard_source (GdkWaylandPrimary *cb)
77 {
78 g_clear_pointer (&cb->source, zwp_primary_selection_source_v1_destroy);
79 }
80
81 static void
gdk_wayland_primary_finalize(GObject * object)82 gdk_wayland_primary_finalize (GObject *object)
83 {
84 GdkWaylandPrimary *cb = GDK_WAYLAND_PRIMARY (object);
85
86 gdk_wayland_primary_discard_pending (cb);
87 gdk_wayland_primary_discard_offer (cb);
88 gdk_wayland_primary_discard_source (cb);
89
90 G_OBJECT_CLASS (gdk_wayland_primary_parent_class)->finalize (object);
91 }
92
93 static void
gdk_wayland_primary_claim_remote(GdkWaylandPrimary * cb,struct zwp_primary_selection_offer_v1 * offer,GdkContentFormats * formats)94 gdk_wayland_primary_claim_remote (GdkWaylandPrimary *cb,
95 struct zwp_primary_selection_offer_v1 *offer,
96 GdkContentFormats *formats)
97 {
98 g_return_if_fail (GDK_IS_WAYLAND_PRIMARY (cb));
99
100 if (cb->source)
101 {
102 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, g_message ("%p: Ignoring clipboard offer for self", cb));
103 gdk_content_formats_unref (formats);
104 return;
105 }
106
107 gdk_wayland_primary_discard_offer (cb);
108
109 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, char *s = gdk_content_formats_to_string (formats);
110 g_message ("%p: remote clipboard claim for %s", cb, s);
111 g_free (s); );
112 cb->offer_formats = formats;
113 cb->offer = offer;
114
115 gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb),
116 cb->offer_formats);
117 }
118
119 static void
primary_offer_offer(void * data,struct zwp_primary_selection_offer_v1 * offer,const char * type)120 primary_offer_offer (void *data,
121 struct zwp_primary_selection_offer_v1 *offer,
122 const char *type)
123 {
124 GdkWaylandPrimary *cb = data;
125
126 if (cb->pending != offer)
127 {
128 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), SELECTION, g_message ("%p: offer for unknown selection %p of %s",
129 cb, offer, type));
130 return;
131 }
132
133 gdk_content_formats_builder_add_mime_type (cb->pending_builder, type);
134 }
135
136 static const struct zwp_primary_selection_offer_v1_listener primary_offer_listener = {
137 primary_offer_offer,
138 };
139
140 static void
primary_selection_data_offer(void * data,struct zwp_primary_selection_device_v1 * device,struct zwp_primary_selection_offer_v1 * offer)141 primary_selection_data_offer (void *data,
142 struct zwp_primary_selection_device_v1 *device,
143 struct zwp_primary_selection_offer_v1 *offer)
144 {
145 GdkWaylandPrimary *cb = data;
146
147 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), SELECTION, g_message ("%p: new primary offer %p",
148 cb, offer));
149
150 gdk_wayland_primary_discard_pending (cb);
151
152 cb->pending = offer;
153 zwp_primary_selection_offer_v1_add_listener (offer,
154 &primary_offer_listener,
155 cb);
156
157 cb->pending_builder = gdk_content_formats_builder_new ();
158 }
159
160 static void
primary_selection_selection(void * data,struct zwp_primary_selection_device_v1 * device,struct zwp_primary_selection_offer_v1 * offer)161 primary_selection_selection (void *data,
162 struct zwp_primary_selection_device_v1 *device,
163 struct zwp_primary_selection_offer_v1 *offer)
164 {
165 GdkWaylandPrimary *cb = data;
166 GdkContentFormats *formats;
167
168 if (offer == NULL)
169 {
170 gdk_wayland_primary_claim_remote (cb, NULL, gdk_content_formats_new (NULL, 0));
171 return;
172 }
173
174 if (cb->pending != offer)
175 {
176 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), SELECTION, g_message ("%p: ignoring unknown data offer %p",
177 cb, offer));
178 return;
179 }
180
181 formats = gdk_content_formats_builder_free_to_formats (cb->pending_builder);
182 cb->pending_builder = NULL;
183 cb->pending = NULL;
184
185 gdk_wayland_primary_claim_remote (cb, offer, formats);
186 }
187
188 static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
189 primary_selection_data_offer,
190 primary_selection_selection,
191 };
192
193 static void
gdk_wayland_primary_write_done(GObject * clipboard,GAsyncResult * result,gpointer user_data)194 gdk_wayland_primary_write_done (GObject *clipboard,
195 GAsyncResult *result,
196 gpointer user_data)
197 {
198 GError *error = NULL;
199
200 if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, &error))
201 {
202 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (clipboard)), SELECTION, g_message ("%p: failed to write stream: %s", clipboard, error->message));
203 g_error_free (error);
204 }
205 }
206
207 static void
gdk_wayland_primary_data_source_send(void * data,struct zwp_primary_selection_source_v1 * source,const char * mime_type,int32_t fd)208 gdk_wayland_primary_data_source_send (void *data,
209 struct zwp_primary_selection_source_v1 *source,
210 const char *mime_type,
211 int32_t fd)
212 {
213 GdkWaylandPrimary *cb = GDK_WAYLAND_PRIMARY (data);
214 GOutputStream *stream;
215
216 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), SELECTION, g_message ("%p: data source send request for %s on fd %d",
217 source, mime_type, fd));
218
219 mime_type = gdk_intern_mime_type (mime_type);
220 stream = g_unix_output_stream_new (fd, TRUE);
221
222 gdk_clipboard_write_async (GDK_CLIPBOARD (cb),
223 mime_type,
224 stream,
225 G_PRIORITY_DEFAULT,
226 NULL,
227 gdk_wayland_primary_write_done,
228 cb);
229 g_object_unref (stream);
230 }
231
232 static void
gdk_wayland_primary_data_source_cancelled(void * data,struct zwp_primary_selection_source_v1 * source)233 gdk_wayland_primary_data_source_cancelled (void *data,
234 struct zwp_primary_selection_source_v1 *source)
235 {
236 GdkWaylandPrimary *cb = GDK_WAYLAND_PRIMARY (data);
237
238 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), CLIPBOARD, g_message ("%p: data source cancelled", data));
239
240 if (cb->source == source)
241 {
242 gdk_wayland_primary_discard_source (cb);
243 gdk_wayland_primary_claim_remote (cb, NULL, gdk_content_formats_new (NULL, 0));
244 }
245 }
246
247 static const struct zwp_primary_selection_source_v1_listener primary_source_listener = {
248 gdk_wayland_primary_data_source_send,
249 gdk_wayland_primary_data_source_cancelled,
250 };
251
252 static gboolean
gdk_wayland_primary_claim(GdkClipboard * clipboard,GdkContentFormats * formats,gboolean local,GdkContentProvider * content)253 gdk_wayland_primary_claim (GdkClipboard *clipboard,
254 GdkContentFormats *formats,
255 gboolean local,
256 GdkContentProvider *content)
257 {
258 GdkWaylandPrimary *cb = GDK_WAYLAND_PRIMARY (clipboard);
259
260 if (local)
261 {
262 GdkWaylandDisplay *wdisplay = GDK_WAYLAND_DISPLAY (gdk_clipboard_get_display (clipboard));
263 const char * const *mime_types;
264 gsize i, n_mime_types;
265
266 gdk_wayland_primary_discard_offer (cb);
267 gdk_wayland_primary_discard_source (cb);
268
269 cb->source = zwp_primary_selection_device_manager_v1_create_source (wdisplay->primary_selection_manager);
270 zwp_primary_selection_source_v1_add_listener (cb->source, &primary_source_listener, cb);
271
272 mime_types = gdk_content_formats_get_mime_types (formats, &n_mime_types);
273 for (i = 0; i < n_mime_types; i++)
274 {
275 zwp_primary_selection_source_v1_offer (cb->source, mime_types[i]);
276 }
277
278 zwp_primary_selection_device_v1_set_selection (cb->primary_data_device,
279 cb->source,
280 _gdk_wayland_display_get_serial (wdisplay));
281 }
282
283 return GDK_CLIPBOARD_CLASS (gdk_wayland_primary_parent_class)->claim (clipboard, formats, local, content);
284 }
285
286 static void
gdk_wayland_primary_read_async(GdkClipboard * clipboard,GdkContentFormats * formats,int io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)287 gdk_wayland_primary_read_async (GdkClipboard *clipboard,
288 GdkContentFormats *formats,
289 int io_priority,
290 GCancellable *cancellable,
291 GAsyncReadyCallback callback,
292 gpointer user_data)
293 {
294 GdkWaylandPrimary *cb = GDK_WAYLAND_PRIMARY (clipboard);
295 GInputStream *stream;
296 const char *mime_type;
297 int pipe_fd[2];
298 GError *error = NULL;
299 GTask *task;
300
301 task = g_task_new (clipboard, cancellable, callback, user_data);
302 g_task_set_priority (task, io_priority);
303 g_task_set_source_tag (task, gdk_wayland_primary_read_async);
304
305 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (clipboard), CLIPBOARD, char *s = gdk_content_formats_to_string (formats);
306 g_message ("%p: read for %s", cb, s);
307 g_free (s); );
308 mime_type = gdk_content_formats_match_mime_type (formats, cb->offer_formats);
309 if (mime_type == NULL)
310 {
311 g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
312 _("No compatible transfer format found"));
313 return;
314 }
315 /* offer formats should be empty if we have no offer */
316 g_assert (cb->offer);
317
318 g_task_set_task_data (task, (gpointer) mime_type, NULL);
319
320 if (!g_unix_open_pipe (pipe_fd, FD_CLOEXEC, &error))
321 {
322 g_task_return_error (task, error);
323 return;
324 }
325
326 zwp_primary_selection_offer_v1_receive (cb->offer, mime_type, pipe_fd[1]);
327 stream = g_unix_input_stream_new (pipe_fd[0], TRUE);
328 close (pipe_fd[1]);
329 g_task_return_pointer (task, stream, g_object_unref);
330 }
331
332 static GInputStream *
gdk_wayland_primary_read_finish(GdkClipboard * clipboard,GAsyncResult * result,const char ** out_mime_type,GError ** error)333 gdk_wayland_primary_read_finish (GdkClipboard *clipboard,
334 GAsyncResult *result,
335 const char **out_mime_type,
336 GError **error)
337 {
338 GInputStream *stream;
339 GTask *task;
340
341 g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (clipboard)), NULL);
342 task = G_TASK (result);
343 g_return_val_if_fail (g_task_get_source_tag (task) == gdk_wayland_primary_read_async, NULL);
344
345 stream = g_task_propagate_pointer (task, error);
346
347 if (stream)
348 {
349 if (out_mime_type)
350 *out_mime_type = g_task_get_task_data (task);
351 g_object_ref (stream);
352 }
353 else
354 {
355 if (out_mime_type)
356 *out_mime_type = NULL;
357 }
358
359 return stream;
360 }
361
362 static void
gdk_wayland_primary_class_init(GdkWaylandPrimaryClass * class)363 gdk_wayland_primary_class_init (GdkWaylandPrimaryClass *class)
364 {
365 GObjectClass *object_class = G_OBJECT_CLASS (class);
366 GdkClipboardClass *clipboard_class = GDK_CLIPBOARD_CLASS (class);
367
368 object_class->finalize = gdk_wayland_primary_finalize;
369
370 clipboard_class->claim = gdk_wayland_primary_claim;
371 clipboard_class->read_async = gdk_wayland_primary_read_async;
372 clipboard_class->read_finish = gdk_wayland_primary_read_finish;
373 }
374
375 static void
gdk_wayland_primary_init(GdkWaylandPrimary * cb)376 gdk_wayland_primary_init (GdkWaylandPrimary *cb)
377 {
378 }
379
380 GdkClipboard *
gdk_wayland_primary_new(GdkWaylandSeat * seat)381 gdk_wayland_primary_new (GdkWaylandSeat *seat)
382 {
383 GdkWaylandDisplay *wdisplay;
384 GdkWaylandPrimary *cb;
385
386 wdisplay = GDK_WAYLAND_DISPLAY (gdk_seat_get_display (GDK_SEAT (seat)));
387
388 cb = g_object_new (GDK_TYPE_WAYLAND_PRIMARY,
389 "display", wdisplay,
390 NULL);
391
392 cb->primary_data_device =
393 zwp_primary_selection_device_manager_v1_get_device (wdisplay->primary_selection_manager,
394 gdk_wayland_seat_get_wl_seat (GDK_SEAT (seat)));
395 zwp_primary_selection_device_v1_add_listener (cb->primary_data_device,
396 &primary_selection_device_listener, cb);
397
398 return GDK_CLIPBOARD (cb);
399 }
400