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