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
32typedef struct _GdkWaylandPrimaryClass GdkWaylandPrimaryClass;
33
34struct _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
49struct _GdkWaylandPrimaryClass
50{
51 GdkClipboardClass parent_class;
52};
53
54G_DEFINE_TYPE (GdkWaylandPrimary, gdk_wayland_primary, GDK_TYPE_CLIPBOARD)
55
56static void
57gdk_wayland_primary_discard_pending (GdkWaylandPrimary *cb)
58{
59 if (cb->pending_builder)
60 {
61 GdkContentFormats *ignore = gdk_content_formats_builder_free_to_formats (builder: cb->pending_builder);
62 gdk_content_formats_unref (formats: ignore);
63 cb->pending_builder = NULL;
64 }
65 g_clear_pointer (&cb->pending, zwp_primary_selection_offer_v1_destroy);
66}
67
68static void
69gdk_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
75static void
76gdk_wayland_primary_discard_source (GdkWaylandPrimary *cb)
77{
78 g_clear_pointer (&cb->source, zwp_primary_selection_source_v1_destroy);
79}
80
81static void
82gdk_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
93static void
94gdk_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 g_clear_pointer (&offer, zwp_primary_selection_offer_v1_destroy);
105 return;
106 }
107
108 gdk_wayland_primary_discard_offer (cb);
109
110 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), CLIPBOARD, char *s = gdk_content_formats_to_string (formats);
111 g_message ("%p: remote clipboard claim for %s", cb, s);
112 g_free (s); );
113 cb->offer_formats = formats;
114 cb->offer = offer;
115
116 gdk_clipboard_claim_remote (GDK_CLIPBOARD (cb),
117 formats: cb->offer_formats);
118}
119
120static void
121primary_offer_offer (void *data,
122 struct zwp_primary_selection_offer_v1 *offer,
123 const char *type)
124{
125 GdkWaylandPrimary *cb = data;
126
127 if (cb->pending != offer)
128 {
129 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), SELECTION, g_message ("%p: offer for unknown selection %p of %s",
130 cb, offer, type));
131 return;
132 }
133
134 gdk_content_formats_builder_add_mime_type (builder: cb->pending_builder, mime_type: type);
135}
136
137static const struct zwp_primary_selection_offer_v1_listener primary_offer_listener = {
138 primary_offer_offer,
139};
140
141static void
142primary_selection_data_offer (void *data,
143 struct zwp_primary_selection_device_v1 *device,
144 struct zwp_primary_selection_offer_v1 *offer)
145{
146 GdkWaylandPrimary *cb = data;
147
148 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), SELECTION, g_message ("%p: new primary offer %p",
149 cb, offer));
150
151 gdk_wayland_primary_discard_pending (cb);
152
153 cb->pending = offer;
154 zwp_primary_selection_offer_v1_add_listener (offer,
155 &primary_offer_listener,
156 cb);
157
158 cb->pending_builder = gdk_content_formats_builder_new ();
159}
160
161static void
162primary_selection_selection (void *data,
163 struct zwp_primary_selection_device_v1 *device,
164 struct zwp_primary_selection_offer_v1 *offer)
165{
166 GdkWaylandPrimary *cb = data;
167 GdkContentFormats *formats;
168
169 if (offer == NULL)
170 {
171 gdk_wayland_primary_claim_remote (cb, NULL, formats: gdk_content_formats_new (NULL, n_mime_types: 0));
172 return;
173 }
174
175 if (cb->pending != offer)
176 {
177 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (cb)), SELECTION, g_message ("%p: ignoring unknown data offer %p",
178 cb, offer));
179 return;
180 }
181
182 formats = gdk_content_formats_builder_free_to_formats (builder: cb->pending_builder);
183 cb->pending_builder = NULL;
184 cb->pending = NULL;
185
186 gdk_wayland_primary_claim_remote (cb, offer, formats);
187}
188
189static const struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
190 primary_selection_data_offer,
191 primary_selection_selection,
192};
193
194static void
195gdk_wayland_primary_write_done (GObject *clipboard,
196 GAsyncResult *result,
197 gpointer user_data)
198{
199 GError *error = NULL;
200
201 if (!gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, error: &error))
202 {
203 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (clipboard)), SELECTION, g_message ("%p: failed to write stream: %s", clipboard, error->message));
204 g_error_free (error);
205 }
206}
207
208static void
209gdk_wayland_primary_data_source_send (void *data,
210 struct zwp_primary_selection_source_v1 *source,
211 const char *mime_type,
212 int32_t fd)
213{
214 GdkWaylandPrimary *cb = GDK_WAYLAND_PRIMARY (data);
215 GOutputStream *stream;
216
217 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), SELECTION, g_message ("%p: data source send request for %s on fd %d",
218 source, mime_type, fd));
219
220 mime_type = gdk_intern_mime_type (string: mime_type);
221 stream = g_unix_output_stream_new (fd, TRUE);
222
223 gdk_clipboard_write_async (GDK_CLIPBOARD (cb),
224 mime_type,
225 stream,
226 G_PRIORITY_DEFAULT,
227 NULL,
228 callback: gdk_wayland_primary_write_done,
229 user_data: cb);
230 g_object_unref (object: stream);
231}
232
233static void
234gdk_wayland_primary_data_source_cancelled (void *data,
235 struct zwp_primary_selection_source_v1 *source)
236{
237 GdkWaylandPrimary *cb = GDK_WAYLAND_PRIMARY (data);
238
239 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (GDK_CLIPBOARD (data)), CLIPBOARD, g_message ("%p: data source cancelled", data));
240
241 if (cb->source == source)
242 {
243 gdk_wayland_primary_discard_source (cb);
244 gdk_wayland_primary_claim_remote (cb, NULL, formats: gdk_content_formats_new (NULL, n_mime_types: 0));
245 }
246}
247
248static const struct zwp_primary_selection_source_v1_listener primary_source_listener = {
249 gdk_wayland_primary_data_source_send,
250 gdk_wayland_primary_data_source_cancelled,
251};
252
253static gboolean
254gdk_wayland_primary_claim (GdkClipboard *clipboard,
255 GdkContentFormats *formats,
256 gboolean local,
257 GdkContentProvider *content)
258{
259 GdkWaylandPrimary *cb = GDK_WAYLAND_PRIMARY (clipboard);
260
261 if (local)
262 {
263 GdkWaylandDisplay *wdisplay = GDK_WAYLAND_DISPLAY (gdk_clipboard_get_display (clipboard));
264 const char * const *mime_types;
265 gsize i, n_mime_types;
266
267 gdk_wayland_primary_discard_offer (cb);
268 gdk_wayland_primary_discard_source (cb);
269
270 cb->source = zwp_primary_selection_device_manager_v1_create_source (wdisplay->primary_selection_manager);
271 zwp_primary_selection_source_v1_add_listener (cb->source, &primary_source_listener, cb);
272
273 mime_types = gdk_content_formats_get_mime_types (formats, n_mime_types: &n_mime_types);
274 for (i = 0; i < n_mime_types; i++)
275 {
276 zwp_primary_selection_source_v1_offer (cb->source, mime_types[i]);
277 }
278
279 zwp_primary_selection_device_v1_set_selection (cb->primary_data_device,
280 cb->source,
281 _gdk_wayland_display_get_serial (display_wayland: wdisplay));
282 }
283
284 return GDK_CLIPBOARD_CLASS (gdk_wayland_primary_parent_class)->claim (clipboard, formats, local, content);
285}
286
287static void
288gdk_wayland_primary_read_async (GdkClipboard *clipboard,
289 GdkContentFormats *formats,
290 int io_priority,
291 GCancellable *cancellable,
292 GAsyncReadyCallback callback,
293 gpointer user_data)
294{
295 GdkWaylandPrimary *cb = GDK_WAYLAND_PRIMARY (clipboard);
296 GInputStream *stream;
297 const char *mime_type;
298 int pipe_fd[2];
299 GError *error = NULL;
300 GTask *task;
301
302 task = g_task_new (source_object: clipboard, cancellable, callback, callback_data: user_data);
303 g_task_set_priority (task, priority: io_priority);
304 g_task_set_source_tag (task, gdk_wayland_primary_read_async);
305
306 GDK_DISPLAY_NOTE (gdk_clipboard_get_display (clipboard), CLIPBOARD, char *s = gdk_content_formats_to_string (formats);
307 g_message ("%p: read for %s", cb, s);
308 g_free (s); );
309 mime_type = gdk_content_formats_match_mime_type (first: formats, second: cb->offer_formats);
310 if (mime_type == NULL)
311 {
312 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED,
313 _("No compatible transfer format found"));
314 return;
315 }
316 /* offer formats should be empty if we have no offer */
317 g_assert (cb->offer);
318
319 g_task_set_task_data (task, task_data: (gpointer) mime_type, NULL);
320
321 if (!g_unix_open_pipe (fds: pipe_fd, FD_CLOEXEC, error: &error))
322 {
323 g_task_return_error (task, error);
324 return;
325 }
326
327 zwp_primary_selection_offer_v1_receive (cb->offer, mime_type, pipe_fd[1]);
328 stream = g_unix_input_stream_new (fd: pipe_fd[0], TRUE);
329 close (fd: pipe_fd[1]);
330 g_task_return_pointer (task, result: stream, result_destroy: g_object_unref);
331}
332
333static GInputStream *
334gdk_wayland_primary_read_finish (GdkClipboard *clipboard,
335 GAsyncResult *result,
336 const char **out_mime_type,
337 GError **error)
338{
339 GInputStream *stream;
340 GTask *task;
341
342 g_return_val_if_fail (g_task_is_valid (result, G_OBJECT (clipboard)), NULL);
343 task = G_TASK (result);
344 g_return_val_if_fail (g_task_get_source_tag (task) == gdk_wayland_primary_read_async, NULL);
345
346 stream = g_task_propagate_pointer (task, error);
347
348 if (stream)
349 {
350 if (out_mime_type)
351 *out_mime_type = g_task_get_task_data (task);
352 g_object_ref (stream);
353 }
354 else
355 {
356 if (out_mime_type)
357 *out_mime_type = NULL;
358 }
359
360 return stream;
361}
362
363static void
364gdk_wayland_primary_class_init (GdkWaylandPrimaryClass *class)
365{
366 GObjectClass *object_class = G_OBJECT_CLASS (class);
367 GdkClipboardClass *clipboard_class = GDK_CLIPBOARD_CLASS (class);
368
369 object_class->finalize = gdk_wayland_primary_finalize;
370
371 clipboard_class->claim = gdk_wayland_primary_claim;
372 clipboard_class->read_async = gdk_wayland_primary_read_async;
373 clipboard_class->read_finish = gdk_wayland_primary_read_finish;
374}
375
376static void
377gdk_wayland_primary_init (GdkWaylandPrimary *cb)
378{
379}
380
381GdkClipboard *
382gdk_wayland_primary_new (GdkWaylandSeat *seat)
383{
384 GdkWaylandDisplay *wdisplay;
385 GdkWaylandPrimary *cb;
386
387 wdisplay = GDK_WAYLAND_DISPLAY (gdk_seat_get_display (GDK_SEAT (seat)));
388
389 cb = g_object_new (GDK_TYPE_WAYLAND_PRIMARY,
390 first_property_name: "display", wdisplay,
391 NULL);
392
393 cb->primary_data_device =
394 zwp_primary_selection_device_manager_v1_get_device (wdisplay->primary_selection_manager,
395 gdk_wayland_seat_get_wl_seat (GDK_SEAT (seat)));
396 zwp_primary_selection_device_v1_add_listener (cb->primary_data_device,
397 &primary_selection_device_listener, cb);
398
399 return GDK_CLIPBOARD (cb);
400}
401

source code of gtk/gdk/wayland/gdkprimary-wayland.c