1/* GDK - The GIMP Drawing Kit
2 *
3 * Copyright (C) 2017 Benjamin Otte <otte@gnome.org>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "config.h"
20
21#include "gdkclipboardprivate.h"
22
23#include "gdkcontentdeserializer.h"
24#include "gdkcontentformats.h"
25#include "gdkcontentproviderimpl.h"
26#include "gdkcontentproviderprivate.h"
27#include "gdkcontentserializer.h"
28#include "gdkdisplay.h"
29#include "gdkintl.h"
30#include "gdkpipeiostreamprivate.h"
31#include "gdktexture.h"
32
33#include <gobject/gvaluecollector.h>
34
35/**
36 * GdkClipboard:
37 *
38 * The `GdkClipboard` object represents data shared between applications or
39 * inside an application.
40 *
41 * To get a `GdkClipboard` object, use [method@Gdk.Display.get_clipboard] or
42 * [method@Gdk.Display.get_primary_clipboard]. You can find out about the data
43 * that is currently available in a clipboard using
44 * [method@Gdk.Clipboard.get_formats].
45 *
46 * To make text or image data available in a clipboard, use
47 * [method@Gdk.Clipboard.set_text] or [method@Gdk.Clipboard.set_texture].
48 * For other data, you can use [method@Gdk.Clipboard.set_content], which
49 * takes a [class@Gdk.ContentProvider] object.
50 *
51 * To read textual or image data from a clipboard, use
52 * [method@Gdk.Clipboard.read_text_async] or
53 * [method@Gdk.Clipboard.read_texture_async]. For other data, use
54 * [method@Gdk.Clipboard.read_async], which provides a `GInputStream` object.
55 */
56
57typedef struct _GdkClipboardPrivate GdkClipboardPrivate;
58
59struct _GdkClipboardPrivate
60{
61 GdkDisplay *display;
62 GdkContentFormats *formats;
63 GdkContentProvider *content;
64
65 guint local : 1;
66};
67
68enum {
69 PROP_0,
70 PROP_DISPLAY,
71 PROP_FORMATS,
72 PROP_LOCAL,
73 PROP_CONTENT,
74 N_PROPERTIES
75};
76
77enum {
78 CHANGED,
79 N_SIGNALS
80};
81
82static GParamSpec *properties[N_PROPERTIES] = { NULL, };
83static guint signals[N_SIGNALS] = { 0 };
84
85G_DEFINE_TYPE_WITH_PRIVATE (GdkClipboard, gdk_clipboard, G_TYPE_OBJECT)
86
87static void
88gdk_clipboard_set_property (GObject *gobject,
89 guint prop_id,
90 const GValue *value,
91 GParamSpec *pspec)
92{
93 GdkClipboard *clipboard = GDK_CLIPBOARD (gobject);
94 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
95
96 switch (prop_id)
97 {
98 case PROP_DISPLAY:
99 priv->display = g_value_get_object (value);
100 g_assert (priv->display != NULL);
101 break;
102
103 default:
104 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
105 break;
106 }
107}
108
109static void
110gdk_clipboard_get_property (GObject *gobject,
111 guint prop_id,
112 GValue *value,
113 GParamSpec *pspec)
114{
115 GdkClipboard *clipboard = GDK_CLIPBOARD (gobject);
116 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
117
118 switch (prop_id)
119 {
120 case PROP_DISPLAY:
121 g_value_set_object (value, v_object: priv->display);
122 break;
123
124 case PROP_FORMATS:
125 g_value_set_boxed (value, v_boxed: priv->formats);
126 break;
127
128 case PROP_CONTENT:
129 g_value_set_object (value, v_object: priv->content);
130 break;
131
132 case PROP_LOCAL:
133 g_value_set_boolean (value, v_boolean: priv->local);
134 break;
135
136 default:
137 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
138 break;
139 }
140}
141
142static void
143gdk_clipboard_finalize (GObject *object)
144{
145 GdkClipboard *clipboard = GDK_CLIPBOARD (object);
146 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
147
148 g_clear_pointer (&priv->formats, gdk_content_formats_unref);
149
150 G_OBJECT_CLASS (gdk_clipboard_parent_class)->finalize (object);
151}
152
153static void
154gdk_clipboard_content_changed_cb (GdkContentProvider *provider,
155 GdkClipboard *clipboard);
156
157static gboolean
158gdk_clipboard_real_claim (GdkClipboard *clipboard,
159 GdkContentFormats *formats,
160 gboolean local,
161 GdkContentProvider *content)
162{
163 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
164
165 g_object_freeze_notify (G_OBJECT (clipboard));
166
167 gdk_content_formats_unref (formats: priv->formats);
168 gdk_content_formats_ref (formats);
169 formats = gdk_content_formats_union_deserialize_gtypes (formats);
170 priv->formats = formats;
171 g_object_notify_by_pspec (G_OBJECT (clipboard), pspec: properties[PROP_FORMATS]);
172 if (priv->local != local)
173 {
174 priv->local = local;
175 g_object_notify_by_pspec (G_OBJECT (clipboard), pspec: properties[PROP_LOCAL]);
176 }
177
178 if (priv->content != content)
179 {
180 GdkContentProvider *old_content = priv->content;
181
182 if (content)
183 priv->content = g_object_ref (content);
184 else
185 priv->content = NULL;
186
187 if (old_content)
188 {
189 g_signal_handlers_disconnect_by_func (old_content,
190 gdk_clipboard_content_changed_cb,
191 clipboard);
192 gdk_content_provider_detach_clipboard (provider: old_content, clipboard);
193 g_object_unref (object: old_content);
194 }
195 if (content)
196 {
197 gdk_content_provider_attach_clipboard (provider: content, clipboard);
198 g_signal_connect (content,
199 "content-changed",
200 G_CALLBACK (gdk_clipboard_content_changed_cb),
201 clipboard);
202 }
203
204 g_object_notify_by_pspec (G_OBJECT (clipboard), pspec: properties[PROP_CONTENT]);
205 }
206
207 g_object_thaw_notify (G_OBJECT (clipboard));
208
209 g_signal_emit (instance: clipboard, signal_id: signals[CHANGED], detail: 0);
210
211 return TRUE;
212}
213
214static void
215gdk_clipboard_store_default_async (GdkClipboard *clipboard,
216 int io_priority,
217 GCancellable *cancellable,
218 GAsyncReadyCallback callback,
219 gpointer user_data)
220{
221 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
222 GTask *task;
223
224 task = g_task_new (source_object: clipboard, cancellable, callback, callback_data: user_data);
225 g_task_set_priority (task, priority: io_priority);
226 g_task_set_source_tag (task, gdk_clipboard_store_default_async);
227
228 if (priv->local)
229 {
230 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED,
231 _("This clipboard cannot store data."));
232 }
233 else
234 {
235 g_task_return_boolean (task, TRUE);
236 }
237
238 g_object_unref (object: task);
239}
240
241static gboolean
242gdk_clipboard_store_default_finish (GdkClipboard *clipboard,
243 GAsyncResult *result,
244 GError **error)
245{
246 g_return_val_if_fail (g_task_is_valid (result, clipboard), FALSE);
247 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_clipboard_store_default_async, FALSE);
248
249 return g_task_propagate_boolean (G_TASK (result), error);
250}
251
252static void
253gdk_clipboard_read_local_write_done (GObject *clipboard,
254 GAsyncResult *result,
255 gpointer stream)
256{
257 /* we don't care about the error, we just want to clean up */
258 gdk_clipboard_write_finish (GDK_CLIPBOARD (clipboard), result, NULL);
259
260 /* XXX: Do we need to close_async() here? */
261 g_output_stream_close (stream, NULL, NULL);
262
263 g_object_unref (object: stream);
264}
265
266static void
267gdk_clipboard_read_local_async (GdkClipboard *clipboard,
268 GdkContentFormats *formats,
269 int io_priority,
270 GCancellable *cancellable,
271 GAsyncReadyCallback callback,
272 gpointer user_data)
273{
274 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
275 GdkContentFormats *content_formats;
276 const char *mime_type;
277 GTask *task;
278
279 task = g_task_new (source_object: clipboard, cancellable, callback, callback_data: user_data);
280 g_task_set_priority (task, priority: io_priority);
281 g_task_set_source_tag (task, gdk_clipboard_read_local_async);
282
283 if (priv->content == NULL)
284 {
285 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_FOUND,
286 _("Cannot read from empty clipboard."));
287 g_object_unref (object: task);
288 return;
289 }
290
291 content_formats = gdk_content_provider_ref_formats (provider: priv->content);
292 content_formats = gdk_content_formats_union_serialize_mime_types (formats: content_formats);
293 mime_type = gdk_content_formats_match_mime_type (first: content_formats, second: formats);
294
295 if (mime_type != NULL)
296 {
297 GOutputStream *output_stream;
298 GIOStream *stream;
299
300 stream = gdk_pipe_io_stream_new ();
301 output_stream = g_io_stream_get_output_stream (stream);
302 gdk_clipboard_write_async (clipboard,
303 mime_type,
304 stream: output_stream,
305 io_priority,
306 cancellable,
307 callback: gdk_clipboard_read_local_write_done,
308 g_object_ref (output_stream));
309 g_task_set_task_data (task, task_data: (gpointer) mime_type, NULL);
310 g_task_return_pointer (task, g_object_ref (g_io_stream_get_input_stream (stream)), result_destroy: g_object_unref);
311
312 g_object_unref (object: stream);
313 }
314 else
315 {
316 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED,
317 _("No compatible formats to transfer clipboard contents."));
318 }
319
320 gdk_content_formats_unref (formats: content_formats);
321 g_object_unref (object: task);
322}
323
324static GInputStream *
325gdk_clipboard_read_local_finish (GdkClipboard *clipboard,
326 GAsyncResult *result,
327 const char **out_mime_type,
328 GError **error)
329{
330 g_return_val_if_fail (g_task_is_valid (result, clipboard), NULL);
331 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_clipboard_read_local_async, NULL);
332
333 if (out_mime_type)
334 *out_mime_type = g_task_get_task_data (G_TASK (result));
335
336 return g_task_propagate_pointer (G_TASK (result), error);
337}
338
339static void
340gdk_clipboard_class_init (GdkClipboardClass *class)
341{
342 GObjectClass *object_class = G_OBJECT_CLASS (class);
343
344 object_class->get_property = gdk_clipboard_get_property;
345 object_class->set_property = gdk_clipboard_set_property;
346 object_class->finalize = gdk_clipboard_finalize;
347
348 class->claim = gdk_clipboard_real_claim;
349 class->store_async = gdk_clipboard_store_default_async;
350 class->store_finish = gdk_clipboard_store_default_finish;
351 class->read_async = gdk_clipboard_read_local_async;
352 class->read_finish = gdk_clipboard_read_local_finish;
353
354 /**
355 * GdkClipboard:display: (attributes org.gtk.Property.get=gdk_clipboard_get_display)
356 *
357 * The `GdkDisplay` that the clipboard belongs to.
358 */
359 properties[PROP_DISPLAY] =
360 g_param_spec_object (name: "display",
361 nick: "Display",
362 blurb: "Display owning this clipboard",
363 GDK_TYPE_DISPLAY,
364 flags: G_PARAM_READWRITE |
365 G_PARAM_CONSTRUCT_ONLY |
366 G_PARAM_STATIC_STRINGS |
367 G_PARAM_EXPLICIT_NOTIFY);
368
369 /**
370 * GdkClipboard:formats: (attributes org.gtk.Property.get=gdk_clipboard_get_formats)
371 *
372 * The possible formats that the clipboard can provide its data in.
373 */
374 properties[PROP_FORMATS] =
375 g_param_spec_boxed (name: "formats",
376 nick: "Formats",
377 blurb: "The possible formats for data",
378 GDK_TYPE_CONTENT_FORMATS,
379 flags: G_PARAM_READABLE |
380 G_PARAM_STATIC_STRINGS |
381 G_PARAM_EXPLICIT_NOTIFY);
382
383 /**
384 * GdkClipboard:local: (attributes org.gtk.Property.get=gdk_clipboard_is_local)
385 *
386 * %TRUE if the contents of the clipboard are owned by this process.
387 */
388 properties[PROP_LOCAL] =
389 g_param_spec_boolean (name: "local",
390 nick: "Local",
391 blurb: "If the contents are owned by this process",
392 TRUE,
393 flags: G_PARAM_READABLE |
394 G_PARAM_STATIC_STRINGS |
395 G_PARAM_EXPLICIT_NOTIFY);
396
397 /**
398 * GdkClipboard:content: (attributes org.gtk.Property.get=gdk_clipboard_get_content)
399 *
400 * The `GdkContentProvider` or %NULL if the clipboard is empty or contents are
401 * provided otherwise.
402 */
403 properties[PROP_CONTENT] =
404 g_param_spec_object (name: "content",
405 nick: "Content",
406 blurb: "Provider of the clipboard's content",
407 GDK_TYPE_CONTENT_PROVIDER,
408 flags: G_PARAM_READABLE |
409 G_PARAM_STATIC_STRINGS |
410 G_PARAM_EXPLICIT_NOTIFY);
411
412 /**
413 * GdkClipboard::changed:
414 * @clipboard: the object on which the signal was emitted
415 *
416 * Emitted when the clipboard changes ownership.
417 */
418 signals[CHANGED] =
419 g_signal_new (signal_name: "changed",
420 G_TYPE_FROM_CLASS (class),
421 signal_flags: G_SIGNAL_RUN_LAST,
422 G_STRUCT_OFFSET (GdkClipboardClass, changed),
423 NULL, NULL, NULL,
424 G_TYPE_NONE, n_params: 0);
425
426 g_object_class_install_properties (oclass: object_class, n_pspecs: N_PROPERTIES, pspecs: properties);
427}
428
429static void
430gdk_clipboard_init (GdkClipboard *clipboard)
431{
432 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
433
434 priv->formats = gdk_content_formats_new (NULL, n_mime_types: 0);
435 priv->local = TRUE;
436}
437
438/**
439 * gdk_clipboard_get_display: (attributes org.gtk.Method.get_property=display)
440 * @clipboard: a `GdkClipboard`
441 *
442 * Gets the `GdkDisplay` that the clipboard was created for.
443 *
444 * Returns: (transfer none): a `GdkDisplay`
445 */
446GdkDisplay *
447gdk_clipboard_get_display (GdkClipboard *clipboard)
448{
449 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
450
451 g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), NULL);
452
453 return priv->display;
454}
455
456/**
457 * gdk_clipboard_get_formats: (attributes org.gtk.Method.get_property=formats)
458 * @clipboard: a `GdkClipboard`
459 *
460 * Gets the formats that the clipboard can provide its current contents in.
461 *
462 * Returns: (transfer none): The formats of the clipboard
463 */
464GdkContentFormats *
465gdk_clipboard_get_formats (GdkClipboard *clipboard)
466{
467 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
468
469 g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), NULL);
470
471 return priv->formats;
472}
473
474/**
475 * gdk_clipboard_is_local: (attributes org.gtk.Method.get_property=local)
476 * @clipboard: a `GdkClipboard`
477 *
478 * Returns if the clipboard is local.
479 *
480 * A clipboard is considered local if it was last claimed
481 * by the running application.
482 *
483 * Note that [method@Gdk.Clipboard.get_content] may return %NULL
484 * even on a local clipboard. In this case the clipboard is empty.
485 *
486 * Returns: %TRUE if the clipboard is local
487 */
488gboolean
489gdk_clipboard_is_local (GdkClipboard *clipboard)
490{
491 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
492
493 g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), FALSE);
494
495 return priv->local;
496}
497
498/**
499 * gdk_clipboard_get_content: (attributes org.gtk.Method.get_property=content)
500 * @clipboard: a `GdkClipboard`
501 *
502 * Returns the `GdkContentProvider` currently set on @clipboard.
503 *
504 * If the @clipboard is empty or its contents are not owned by the
505 * current process, %NULL will be returned.
506 *
507 * Returns: (transfer none) (nullable): The content of a clipboard
508 * if the clipboard does not maintain any content
509 */
510GdkContentProvider *
511gdk_clipboard_get_content (GdkClipboard *clipboard)
512{
513 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
514
515 g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), NULL);
516
517 return priv->content;
518}
519
520/**
521 * gdk_clipboard_store_async:
522 * @clipboard: a `GdkClipboard`
523 * @io_priority: the I/O priority of the request
524 * @cancellable: (nullable): optional `GCancellable` object
525 * @callback: (scope async): callback to call when the request is satisfied
526 * @user_data: (closure): the data to pass to callback function
527 *
528 * Asynchronously instructs the @clipboard to store its contents remotely.
529 *
530 * If the clipboard is not local, this function does nothing but report success.
531 *
532 * The @callback must call [method@Gdk.Clipboard.store_finish].
533 *
534 * The purpose of this call is to preserve clipboard contents beyond the
535 * lifetime of an application, so this function is typically called on
536 * exit. Depending on the platform, the functionality may not be available
537 * unless a "clipboard manager" is running.
538 *
539 * This function is called automatically when a [class@Gtk.Application] is
540 * shut down, so you likely don't need to call it.
541 */
542void
543gdk_clipboard_store_async (GdkClipboard *clipboard,
544 int io_priority,
545 GCancellable *cancellable,
546 GAsyncReadyCallback callback,
547 gpointer user_data)
548{
549 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
550
551 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
552 g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
553 g_return_if_fail (callback != NULL);
554
555 if (priv->local)
556 {
557 GDK_CLIPBOARD_GET_CLASS (clipboard)->store_async (clipboard,
558 io_priority,
559 cancellable,
560 callback,
561 user_data);
562 }
563 else
564 {
565 gdk_clipboard_store_default_async (clipboard,
566 io_priority,
567 cancellable,
568 callback,
569 user_data);
570 }
571}
572
573/**
574 * gdk_clipboard_store_finish:
575 * @clipboard: a `GdkClipboard`
576 * @result: a `GAsyncResult`
577 * @error: a `GError` location to store the error occurring
578 *
579 * Finishes an asynchronous clipboard store.
580 *
581 * See [method@Gdk.Clipboard.store_async].
582 *
583 * Returns: %TRUE if storing was successful.
584 */
585gboolean
586gdk_clipboard_store_finish (GdkClipboard *clipboard,
587 GAsyncResult *result,
588 GError **error)
589{
590 g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), FALSE);
591 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
592
593 /* don't check priv->local here because it might have changed while the
594 * read was ongoing */
595 if (g_async_result_is_tagged (res: result, source_tag: gdk_clipboard_store_default_async))
596 {
597 return gdk_clipboard_store_default_finish (clipboard, result, error);
598 }
599 else
600 {
601 return GDK_CLIPBOARD_GET_CLASS (clipboard)->store_finish (clipboard, result, error);
602 }
603}
604
605static void
606gdk_clipboard_read_internal (GdkClipboard *clipboard,
607 GdkContentFormats *formats,
608 int io_priority,
609 GCancellable *cancellable,
610 GAsyncReadyCallback callback,
611 gpointer user_data)
612{
613 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
614
615 if (priv->local)
616 {
617 gdk_clipboard_read_local_async (clipboard,
618 formats,
619 io_priority,
620 cancellable,
621 callback,
622 user_data);
623 }
624 else
625 {
626 GDK_CLIPBOARD_GET_CLASS (clipboard)->read_async (clipboard,
627 formats,
628 io_priority,
629 cancellable,
630 callback,
631 user_data);
632 }
633}
634
635/**
636 * gdk_clipboard_read_async:
637 * @clipboard: a `GdkClipboard`
638 * @mime_types: (array zero-terminated=1): a %NULL-terminated array of mime types to choose from
639 * @io_priority: the I/O priority of the request
640 * @cancellable: (nullable): optional `GCancellable` object
641 * @callback: (scope async): callback to call when the request is satisfied
642 * @user_data: (closure): the data to pass to callback function
643 *
644 * Asynchronously requests an input stream to read the @clipboard's
645 * contents from.
646 *
647 * When the operation is finished @callback will be called. You must then
648 * call [method@Gdk.Clipboard.read_finish] to get the result of the operation.
649 *
650 * The clipboard will choose the most suitable mime type from the given list
651 * to fulfill the request, preferring the ones listed first.
652 */
653void
654gdk_clipboard_read_async (GdkClipboard *clipboard,
655 const char **mime_types,
656 int io_priority,
657 GCancellable *cancellable,
658 GAsyncReadyCallback callback,
659 gpointer user_data)
660{
661 GdkContentFormats *formats;
662
663 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
664 g_return_if_fail (mime_types != NULL && mime_types[0] != NULL);
665 g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
666 g_return_if_fail (callback != NULL);
667
668 formats = gdk_content_formats_new (mime_types, n_mime_types: g_strv_length (str_array: (char **) mime_types));
669
670 gdk_clipboard_read_internal (clipboard, formats, io_priority, cancellable, callback, user_data);
671
672 gdk_content_formats_unref (formats);
673}
674
675/**
676 * gdk_clipboard_read_finish:
677 * @clipboard: a `GdkClipboard`
678 * @result: a `GAsyncResult`
679 * @out_mime_type: (out) (optional) (transfer none): location to store
680 * the chosen mime type
681 * @error: a `GError` location to store the error occurring
682 *
683 * Finishes an asynchronous clipboard read.
684 *
685 * See [method@Gdk.Clipboard.read_async].
686 *
687 * Returns: (transfer full) (nullable): a `GInputStream`
688 */
689GInputStream *
690gdk_clipboard_read_finish (GdkClipboard *clipboard,
691 GAsyncResult *result,
692 const char **out_mime_type,
693 GError **error)
694{
695 g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), NULL);
696 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
697
698 /* don't check priv->local here because it might have changed while the
699 * read was ongoing */
700 if (g_async_result_is_tagged (res: result, source_tag: gdk_clipboard_read_local_async))
701 {
702 return gdk_clipboard_read_local_finish (clipboard, result, out_mime_type, error);
703 }
704 else
705 {
706 return GDK_CLIPBOARD_GET_CLASS (clipboard)->read_finish (clipboard, result, out_mime_type, error);
707 }
708}
709
710static void
711gdk_clipboard_read_value_done (GObject *source,
712 GAsyncResult *result,
713 gpointer data)
714{
715 GTask *task = data;
716 GError *error = NULL;
717 GValue *value;
718
719 value = g_task_get_task_data (task);
720
721 if (!gdk_content_deserialize_finish (result, value, error: &error))
722 g_task_return_error (task, error);
723 else
724 g_task_return_pointer (task, result: value, NULL);
725
726 g_object_unref (object: task);
727}
728
729static void
730gdk_clipboard_read_value_got_stream (GObject *source,
731 GAsyncResult *result,
732 gpointer data)
733{
734 GInputStream *stream;
735 GError *error = NULL;
736 GTask *task = data;
737 const char *mime_type;
738
739 stream = gdk_clipboard_read_finish (GDK_CLIPBOARD (source), result, out_mime_type: &mime_type, error: &error);
740 if (stream == NULL)
741 {
742 g_task_return_error (task, error);
743 return;
744 }
745
746 gdk_content_deserialize_async (stream,
747 mime_type,
748 G_VALUE_TYPE (g_task_get_task_data (task)),
749 io_priority: g_task_get_priority (task),
750 cancellable: g_task_get_cancellable (task),
751 callback: gdk_clipboard_read_value_done,
752 user_data: task);
753 g_object_unref (object: stream);
754}
755
756static void
757free_value (gpointer value)
758{
759 g_value_unset (value);
760 g_slice_free (GValue, value);
761}
762
763static void
764gdk_clipboard_read_value_internal (GdkClipboard *clipboard,
765 GType type,
766 gpointer source_tag,
767 int io_priority,
768 GCancellable *cancellable,
769 GAsyncReadyCallback callback,
770 gpointer user_data)
771{
772 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
773 GdkContentFormatsBuilder *builder;
774 GdkContentFormats *formats;
775 GValue *value;
776 GTask *task;
777
778 task = g_task_new (source_object: clipboard, cancellable, callback, callback_data: user_data);
779 g_task_set_priority (task, priority: io_priority);
780 g_task_set_source_tag (task, source_tag);
781 value = g_slice_new0 (GValue);
782 g_value_init (value, g_type: type);
783 g_task_set_task_data (task, task_data: value, task_data_destroy: free_value);
784
785 if (priv->local)
786 {
787 GError *error = NULL;
788
789 if (priv->content == NULL)
790 {
791 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_FOUND,
792 _("Cannot read from empty clipboard."));
793 g_object_unref (object: task);
794 return;
795 }
796
797 if (gdk_content_provider_get_value (provider: priv->content, value, error: &error))
798 {
799 g_task_return_pointer (task, result: value, NULL);
800 g_object_unref (object: task);
801 return;
802 }
803 else if (!g_error_matches (error, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED))
804 {
805 g_task_return_error (task, error);
806 g_object_unref (object: task);
807 return;
808 }
809 else
810 {
811 /* fall through to regular stream transfer */
812 g_clear_error (err: &error);
813 }
814 }
815
816 builder = gdk_content_formats_builder_new ();
817 gdk_content_formats_builder_add_gtype (builder, type);
818 formats = gdk_content_formats_builder_free_to_formats (builder);
819 formats = gdk_content_formats_union_deserialize_mime_types (formats);
820
821 gdk_clipboard_read_internal (clipboard,
822 formats,
823 io_priority,
824 cancellable,
825 callback: gdk_clipboard_read_value_got_stream,
826 user_data: task);
827
828 gdk_content_formats_unref (formats);
829}
830
831/**
832 * gdk_clipboard_read_value_async:
833 * @clipboard: a `GdkClipboard`
834 * @type: a `GType` to read
835 * @io_priority: the I/O priority of the request
836 * @cancellable: (nullable): optional `GCancellable` object
837 * @callback: (scope async): callback to call when the request is satisfied
838 * @user_data: (closure): the data to pass to callback function
839 *
840 * Asynchronously request the @clipboard contents converted to the given
841 * @type.
842 *
843 * When the operation is finished @callback will be called. You must then call
844 * [method@Gdk.Clipboard.read_value_finish] to get the resulting `GValue`.
845 *
846 * For local clipboard contents that are available in the given `GType`,
847 * the value will be copied directly. Otherwise, GDK will try to use
848 * [func@content_deserialize_async] to convert the clipboard's data.
849 */
850void
851gdk_clipboard_read_value_async (GdkClipboard *clipboard,
852 GType type,
853 int io_priority,
854 GCancellable *cancellable,
855 GAsyncReadyCallback callback,
856 gpointer user_data)
857{
858 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
859 g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
860 g_return_if_fail (callback != NULL);
861
862 gdk_clipboard_read_value_internal (clipboard,
863 type,
864 source_tag: gdk_clipboard_read_value_async,
865 io_priority,
866 cancellable,
867 callback,
868 user_data);
869}
870
871/**
872 * gdk_clipboard_read_value_finish:
873 * @clipboard: a `GdkClipboard`
874 * @result: a `GAsyncResult`
875 * @error: a GError` location to store the error occurring
876 *
877 * Finishes an asynchronous clipboard read.
878 *
879 * See [method@Gdk.Clipboard.read_value_async].
880 *
881 * Returns: (transfer none): a `GValue` containing the result.
882 */
883const GValue *
884gdk_clipboard_read_value_finish (GdkClipboard *clipboard,
885 GAsyncResult *result,
886 GError **error)
887{
888 g_return_val_if_fail (g_task_is_valid (result, clipboard), NULL);
889 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_clipboard_read_value_async, NULL);
890 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
891
892 return g_task_propagate_pointer (G_TASK (result), error);
893}
894
895/**
896 * gdk_clipboard_read_texture_async:
897 * @clipboard: a `GdkClipboard`
898 * @cancellable: (nullable): optional `GCancellable` object, %NULL to ignore.
899 * @callback: (scope async): callback to call when the request is satisfied
900 * @user_data: (closure): the data to pass to callback function
901 *
902 * Asynchronously request the @clipboard contents converted to a `GdkPixbuf`.
903 *
904 * When the operation is finished @callback will be called. You must then
905 * call [method@Gdk.Clipboard.read_texture_finish] to get the result.
906 *
907 * This is a simple wrapper around [method@Gdk.Clipboard.read_value_async].
908 * Use that function or [method@Gdk.Clipboard.read_async] directly if you
909 * need more control over the operation.
910 */
911void
912gdk_clipboard_read_texture_async (GdkClipboard *clipboard,
913 GCancellable *cancellable,
914 GAsyncReadyCallback callback,
915 gpointer user_data)
916{
917 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
918 g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
919 g_return_if_fail (callback != NULL);
920
921 gdk_clipboard_read_value_internal (clipboard,
922 GDK_TYPE_TEXTURE,
923 source_tag: gdk_clipboard_read_texture_async,
924 G_PRIORITY_DEFAULT,
925 cancellable,
926 callback,
927 user_data);
928}
929
930/**
931 * gdk_clipboard_read_texture_finish:
932 * @clipboard: a `GdkClipboard`
933 * @result: a `GAsyncResult`
934 * @error: a `GError` location to store the error occurring
935 *
936 * Finishes an asynchronous clipboard read.
937 *
938 * See [method@Gdk.Clipboard.read_texture_async].
939 *
940 * Returns: (transfer full) (nullable): a new `GdkTexture`
941 */
942GdkTexture *
943gdk_clipboard_read_texture_finish (GdkClipboard *clipboard,
944 GAsyncResult *result,
945 GError **error)
946{
947 const GValue *value;
948
949 g_return_val_if_fail (g_task_is_valid (result, clipboard), NULL);
950 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_clipboard_read_texture_async, NULL);
951 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
952
953 value = g_task_propagate_pointer (G_TASK (result), error);
954 if (!value)
955 return NULL;
956
957 return g_value_dup_object (value);
958}
959
960/**
961 * gdk_clipboard_read_text_async:
962 * @clipboard: a `GdkClipboard`
963 * @cancellable: (nullable): optional `GCancellable` object
964 * @callback: (scope async): callback to call when the request is satisfied
965 * @user_data: (closure): the data to pass to callback function
966 *
967 * Asynchronously request the @clipboard contents converted to a string.
968 *
969 * When the operation is finished @callback will be called. You must then
970 * call [method@Gdk.Clipboard.read_text_finish] to get the result.
971 *
972 * This is a simple wrapper around [method@Gdk.Clipboard.read_value_async].
973 * Use that function or [method@Gdk.Clipboard.read_async] directly if you
974 * need more control over the operation.
975 */
976void
977gdk_clipboard_read_text_async (GdkClipboard *clipboard,
978 GCancellable *cancellable,
979 GAsyncReadyCallback callback,
980 gpointer user_data)
981{
982 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
983 g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
984 g_return_if_fail (callback != NULL);
985
986 gdk_clipboard_read_value_internal (clipboard,
987 G_TYPE_STRING,
988 source_tag: gdk_clipboard_read_text_async,
989 G_PRIORITY_DEFAULT,
990 cancellable,
991 callback,
992 user_data);
993}
994
995/**
996 * gdk_clipboard_read_text_finish:
997 * @clipboard: a `GdkClipboard`
998 * @result: a `GAsyncResult`
999 * @error: a `GError` location to store the error occurring
1000 *
1001 * Finishes an asynchronous clipboard read.
1002 *
1003 * See [method@Gdk.Clipboard.read_text_async].
1004 *
1005 * Returns: (transfer full) (nullable): a new string
1006 */
1007char *
1008gdk_clipboard_read_text_finish (GdkClipboard *clipboard,
1009 GAsyncResult *result,
1010 GError **error)
1011{
1012 const GValue *value;
1013
1014 g_return_val_if_fail (g_task_is_valid (result, clipboard), NULL);
1015 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_clipboard_read_text_async, NULL);
1016 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1017
1018 value = g_task_propagate_pointer (G_TASK (result), error);
1019 if (!value)
1020 return NULL;
1021
1022 return g_value_dup_string (value);
1023}
1024
1025GdkClipboard *
1026gdk_clipboard_new (GdkDisplay *display)
1027{
1028 return g_object_new (GDK_TYPE_CLIPBOARD,
1029 first_property_name: "display", display,
1030 NULL);
1031}
1032
1033static void
1034gdk_clipboard_write_done (GObject *content,
1035 GAsyncResult *result,
1036 gpointer task)
1037{
1038 GError *error = NULL;
1039
1040 if (gdk_content_provider_write_mime_type_finish (GDK_CONTENT_PROVIDER (content), result, error: &error))
1041 g_task_return_boolean (task, TRUE);
1042 else
1043 g_task_return_error (task, error);
1044
1045 g_object_unref (object: task);
1046}
1047
1048static void
1049gdk_clipboard_write_serialize_done (GObject *content,
1050 GAsyncResult *result,
1051 gpointer task)
1052{
1053 GError *error = NULL;
1054
1055 if (gdk_content_serialize_finish (result, error: &error))
1056 g_task_return_boolean (task, TRUE);
1057 else
1058 g_task_return_error (task, error);
1059
1060 g_object_unref (object: task);
1061}
1062
1063void
1064gdk_clipboard_write_async (GdkClipboard *clipboard,
1065 const char *mime_type,
1066 GOutputStream *stream,
1067 int io_priority,
1068 GCancellable *cancellable,
1069 GAsyncReadyCallback callback,
1070 gpointer user_data)
1071{
1072 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
1073 GdkContentFormats *formats, *mime_formats;
1074 GTask *task;
1075 GType gtype;
1076
1077 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
1078 g_return_if_fail (priv->local);
1079 g_return_if_fail (mime_type != NULL);
1080 g_return_if_fail (mime_type == g_intern_string (mime_type));
1081 g_return_if_fail (G_IS_OUTPUT_STREAM (stream));
1082 g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
1083 g_return_if_fail (callback != NULL);
1084
1085 task = g_task_new (source_object: clipboard, cancellable, callback, callback_data: user_data);
1086 g_task_set_priority (task, priority: io_priority);
1087 g_task_set_source_tag (task, gdk_clipboard_write_async);
1088
1089 if (priv->content == NULL)
1090 {
1091 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_FOUND,
1092 _("Cannot read from empty clipboard."));
1093 g_object_unref (object: task);
1094 return;
1095 }
1096
1097 formats = gdk_content_provider_ref_formats (provider: priv->content);
1098 if (gdk_content_formats_contain_mime_type (formats, mime_type))
1099 {
1100 gdk_content_provider_write_mime_type_async (provider: priv->content,
1101 mime_type,
1102 stream,
1103 io_priority,
1104 cancellable,
1105 callback: gdk_clipboard_write_done,
1106 user_data: task);
1107 gdk_content_formats_unref (formats);
1108 return;
1109 }
1110
1111 mime_formats = gdk_content_formats_new (mime_types: (const char *[2]) { mime_type, NULL }, n_mime_types: 1);
1112 mime_formats = gdk_content_formats_union_serialize_gtypes (formats: mime_formats);
1113 gtype = gdk_content_formats_match_gtype (first: formats, second: mime_formats);
1114 if (gtype != G_TYPE_INVALID)
1115 {
1116 GValue value = G_VALUE_INIT;
1117 GError *error = NULL;
1118
1119 g_assert (gtype != G_TYPE_INVALID);
1120
1121 g_value_init (value: &value, g_type: gtype);
1122 if (gdk_content_provider_get_value (provider: priv->content, value: &value, error: &error))
1123 {
1124 gdk_content_serialize_async (stream,
1125 mime_type,
1126 value: &value,
1127 io_priority,
1128 cancellable,
1129 callback: gdk_clipboard_write_serialize_done,
1130 g_object_ref (task));
1131 }
1132 else
1133 {
1134 g_task_return_error (task, error);
1135 }
1136
1137 g_value_unset (value: &value);
1138 }
1139 else
1140 {
1141 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED,
1142 _("No compatible formats to transfer clipboard contents."));
1143 }
1144
1145 gdk_content_formats_unref (formats: mime_formats);
1146 gdk_content_formats_unref (formats);
1147 g_object_unref (object: task);
1148}
1149
1150gboolean
1151gdk_clipboard_write_finish (GdkClipboard *clipboard,
1152 GAsyncResult *result,
1153 GError **error)
1154{
1155 g_return_val_if_fail (g_task_is_valid (result, clipboard), FALSE);
1156 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gdk_clipboard_write_async, FALSE);
1157
1158 return g_task_propagate_boolean (G_TASK (result), error);
1159}
1160
1161static gboolean
1162gdk_clipboard_claim (GdkClipboard *clipboard,
1163 GdkContentFormats *formats,
1164 gboolean local,
1165 GdkContentProvider *content)
1166{
1167 return GDK_CLIPBOARD_GET_CLASS (clipboard)->claim (clipboard, formats, local, content);
1168}
1169
1170static void
1171gdk_clipboard_content_changed_cb (GdkContentProvider *provider,
1172 GdkClipboard *clipboard)
1173{
1174 GdkContentFormats *formats;
1175
1176 formats = gdk_content_provider_ref_formats (provider);
1177 formats = gdk_content_formats_union_serialize_mime_types (formats);
1178
1179 gdk_clipboard_claim (clipboard, formats, TRUE, content: provider);
1180
1181 gdk_content_formats_unref (formats);
1182}
1183
1184void
1185gdk_clipboard_claim_remote (GdkClipboard *clipboard,
1186 GdkContentFormats *formats)
1187{
1188 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
1189 g_return_if_fail (formats != NULL);
1190
1191 gdk_clipboard_claim (clipboard, formats, FALSE, NULL);
1192}
1193
1194/**
1195 * gdk_clipboard_set_content:
1196 * @clipboard: a `GdkClipboard`
1197 * @provider: (transfer none) (nullable): the new contents of @clipboard
1198 * or %NULL to clear the clipboard
1199 *
1200 * Sets a new content provider on @clipboard.
1201 *
1202 * The clipboard will claim the `GdkDisplay`'s resources and advertise
1203 * these new contents to other applications.
1204 *
1205 * In the rare case of a failure, this function will return %FALSE. The
1206 * clipboard will then continue reporting its old contents and ignore
1207 * @provider.
1208 *
1209 * If the contents are read by either an external application or the
1210 * @clipboard's read functions, @clipboard will select the best format to
1211 * transfer the contents and then request that format from @provider.
1212 *
1213 * Returns: %TRUE if setting the clipboard succeeded
1214 */
1215gboolean
1216gdk_clipboard_set_content (GdkClipboard *clipboard,
1217 GdkContentProvider *provider)
1218{
1219 GdkClipboardPrivate *priv = gdk_clipboard_get_instance_private (self: clipboard);
1220 GdkContentFormats *formats;
1221 gboolean result;
1222
1223 g_return_val_if_fail (GDK_IS_CLIPBOARD (clipboard), FALSE);
1224 g_return_val_if_fail (provider == NULL || GDK_IS_CONTENT_PROVIDER (provider), FALSE);
1225
1226 if (provider)
1227 {
1228 if (priv->content == provider)
1229 return TRUE;
1230
1231 formats = gdk_content_provider_ref_formats (provider);
1232 formats = gdk_content_formats_union_serialize_mime_types (formats);
1233 }
1234 else
1235 {
1236 if (priv->content == NULL && priv->local)
1237 return TRUE;
1238
1239 formats = gdk_content_formats_new (NULL, n_mime_types: 0);
1240 }
1241
1242 result = gdk_clipboard_claim (clipboard, formats, TRUE, content: provider);
1243
1244 gdk_content_formats_unref (formats);
1245
1246 return result;
1247}
1248
1249/**
1250 * gdk_clipboard_set:
1251 * @clipboard: a `GdkClipboard`
1252 * @type: type of value to set
1253 * @...: value contents conforming to @type
1254 *
1255 * Sets the clipboard to contain the value collected from the given varargs.
1256 *
1257 * Values should be passed the same way they are passed to other value
1258 * collecting APIs, such as [`method@GObject.Object.set`] or
1259 * [`func@GObject.signal_emit`].
1260 *
1261 * ```c
1262 * gdk_clipboard_set (clipboard, GTK_TYPE_STRING, "Hello World");
1263 *
1264 * gdk_clipboard_set (clipboard, GDK_TYPE_TEXTURE, some_texture);
1265 * ```
1266 */
1267void
1268gdk_clipboard_set (GdkClipboard *clipboard,
1269 GType type,
1270 ...)
1271{
1272 va_list args;
1273
1274 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
1275
1276 va_start (args, type);
1277 gdk_clipboard_set_valist (clipboard, type, args);
1278 va_end (args);
1279}
1280
1281/**
1282 * gdk_clipboard_set_valist: (skip)
1283 * @clipboard: a `GdkClipboard`
1284 * @type: type of value to set
1285 * @args: varargs containing the value of @type
1286 *
1287 * Sets the clipboard to contain the value collected from the given @args.
1288 */
1289void
1290gdk_clipboard_set_valist (GdkClipboard *clipboard,
1291 GType type,
1292 va_list args)
1293{
1294 GValue value = G_VALUE_INIT;
1295 char *error;
1296
1297 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
1298
1299 G_VALUE_COLLECT_INIT (&value, type,
1300 args, G_VALUE_NOCOPY_CONTENTS,
1301 &error);
1302 if (error)
1303 {
1304 g_warning ("%s: %s", G_STRLOC, error);
1305 g_free (mem: error);
1306 /* we purposely leak the value here, it might not be
1307 * in a sane state if an error condition occurred
1308 */
1309 return;
1310 }
1311
1312 gdk_clipboard_set_value (clipboard, value: &value);
1313 g_value_unset (value: &value);
1314}
1315
1316/**
1317 * gdk_clipboard_set_value: (rename-to gdk_clipboard_set)
1318 * @clipboard: a `GdkClipboard`
1319 * @value: a `GValue` to set
1320 *
1321 * Sets the @clipboard to contain the given @value.
1322 */
1323void
1324gdk_clipboard_set_value (GdkClipboard *clipboard,
1325 const GValue *value)
1326{
1327 GdkContentProvider *provider;
1328
1329 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
1330 g_return_if_fail (G_IS_VALUE (value));
1331
1332 provider = gdk_content_provider_new_for_value (value);
1333
1334 gdk_clipboard_set_content (clipboard, provider);
1335 g_object_unref (object: provider);
1336}
1337
1338/**
1339 * gdk_clipboard_set_text: (skip)
1340 * @clipboard: a `GdkClipboard`
1341 * @text: Text to put into the clipboard
1342 *
1343 * Puts the given @text into the clipboard.
1344 */
1345void
1346gdk_clipboard_set_text (GdkClipboard *clipboard,
1347 const char *text)
1348{
1349 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
1350
1351 gdk_clipboard_set (clipboard, G_TYPE_STRING, text);
1352}
1353
1354/**
1355 * gdk_clipboard_set_texture: (skip)
1356 * @clipboard: a `GdkClipboard`
1357 * @texture: a `GdkTexture` to put into the clipboard
1358 *
1359 * Puts the given @texture into the clipboard.
1360 */
1361void
1362gdk_clipboard_set_texture (GdkClipboard *clipboard,
1363 GdkTexture *texture)
1364{
1365 g_return_if_fail (GDK_IS_CLIPBOARD (clipboard));
1366 g_return_if_fail (GDK_IS_TEXTURE (texture));
1367
1368 gdk_clipboard_set (clipboard, GDK_TYPE_TEXTURE, texture);
1369}
1370

source code of gtk/gdk/gdkclipboard.c