1/*
2 * Copyright (C) 2011 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 Library 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 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include <gtk/gtk.h>
19
20static GdkTexture *
21render_paintable_to_texture (GdkPaintable *paintable)
22{
23 GtkSnapshot *snapshot;
24 GskRenderNode *node;
25 int width, height;
26 cairo_surface_t *surface;
27 cairo_t *cr;
28 GdkTexture *texture;
29 GBytes *bytes;
30
31 width = gdk_paintable_get_intrinsic_width (paintable);
32 height = gdk_paintable_get_intrinsic_height (paintable);
33
34 surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width, height);
35
36 snapshot = gtk_snapshot_new ();
37 gdk_paintable_snapshot (paintable, snapshot, width, height);
38 node = gtk_snapshot_free_to_node (snapshot);
39
40 cr = cairo_create (target: surface);
41 gsk_render_node_draw (node, cr);
42 cairo_destroy (cr);
43
44 gsk_render_node_unref (node);
45
46 bytes = g_bytes_new_with_free_func (data: cairo_image_surface_get_data (surface),
47 size: cairo_image_surface_get_height (surface)
48 * cairo_image_surface_get_stride (surface),
49 free_func: (GDestroyNotify) cairo_surface_destroy,
50 user_data: cairo_surface_reference (surface));
51 texture = gdk_memory_texture_new (width: cairo_image_surface_get_width (surface),
52 height: cairo_image_surface_get_height (surface),
53 GDK_MEMORY_DEFAULT,
54 bytes,
55 stride: cairo_image_surface_get_stride (surface));
56 g_bytes_unref (bytes);
57 cairo_surface_destroy (surface);
58
59 return texture;
60}
61
62static void
63clipboard_changed_cb (GdkClipboard *clipboard,
64 GtkWidget *stack)
65{
66 GtkWidget *child;
67
68 gtk_stack_set_visible_child_name (GTK_STACK (stack), name: "info");
69
70 child = gtk_stack_get_child_by_name (GTK_STACK (stack), name: "image");
71 gtk_image_clear (GTK_IMAGE (child));
72
73 child = gtk_stack_get_child_by_name (GTK_STACK (stack), name: "text");
74 gtk_label_set_text (GTK_LABEL (child), str: "");
75}
76
77static void
78texture_loaded_cb (GObject *clipboard,
79 GAsyncResult *res,
80 gpointer data)
81{
82 GError *error = NULL;
83 GdkTexture *texture;
84
85 texture = gdk_clipboard_read_texture_finish (GDK_CLIPBOARD (clipboard), result: res, error: &error);
86 if (texture == NULL)
87 {
88 g_print (format: "%s\n", error->message);
89 g_error_free (error);
90 return;
91 }
92
93 gtk_image_set_from_paintable (image: data, paintable: GDK_PAINTABLE (ptr: texture));
94 g_object_unref (object: texture);
95}
96
97static void
98text_loaded_cb (GObject *clipboard,
99 GAsyncResult *res,
100 gpointer data)
101{
102 GError *error = NULL;
103 char *text;
104
105 text = gdk_clipboard_read_text_finish (GDK_CLIPBOARD (clipboard), result: res, error: &error);
106 if (text == NULL)
107 {
108 g_print (format: "%s\n", error->message);
109 g_error_free (error);
110 return;
111 }
112
113 gtk_label_set_text (self: data, str: text);
114 g_free (mem: text);
115}
116
117static void
118visible_child_changed_cb (GtkWidget *stack,
119 GParamSpec *pspec,
120 GdkClipboard *clipboard)
121{
122 const char *visible_child = gtk_stack_get_visible_child_name (GTK_STACK (stack));
123
124 if (visible_child == NULL)
125 {
126 /* nothing to do here but avoiding crashes in g_str_equal() */
127 }
128 else if (g_str_equal (v1: visible_child, v2: "image"))
129 {
130 GtkWidget *image = gtk_stack_get_child_by_name (GTK_STACK (stack), name: "image");
131
132 gdk_clipboard_read_texture_async (clipboard,
133 NULL,
134 callback: texture_loaded_cb,
135 user_data: image);
136 }
137 else if (g_str_equal (v1: visible_child, v2: "text"))
138 {
139 GtkWidget *label = gtk_stack_get_child_by_name (GTK_STACK (stack), name: "text");
140
141 gdk_clipboard_read_text_async (clipboard,
142 NULL,
143 callback: text_loaded_cb,
144 user_data: label);
145 }
146}
147
148#ifdef G_OS_UNIX /* portal usage supported on *nix only */
149
150static GSList *
151get_file_list (const char *dir)
152{
153 GFileEnumerator *enumerator;
154 GFile *file;
155 GFileInfo *info;
156 GSList *list = NULL;
157
158 file = g_file_new_for_path (path: dir);
159 enumerator = g_file_enumerate_children (file, attributes: "standard::name,standard::type", flags: 0, NULL, NULL);
160 g_object_unref (object: file);
161 if (enumerator == NULL)
162 return NULL;
163
164 while (g_file_enumerator_iterate (direnum: enumerator, out_info: &info, out_child: &file, NULL, NULL) && file != NULL)
165 {
166 /* the portal can't handle directories */
167 if (g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
168 continue;
169
170 list = g_slist_prepend (list, g_object_ref (file));
171 }
172
173 return g_slist_reverse (list);
174}
175
176#else /* G_OS_UNIX -- original non-portal-enabled code */
177
178static GList *
179get_file_list (const char *dir)
180{
181 GFileEnumerator *enumerator;
182 GFile *file;
183 GList *list = NULL;
184
185 file = g_file_new_for_path (dir);
186 enumerator = g_file_enumerate_children (file, "standard::name", 0, NULL, NULL);
187 g_object_unref (file);
188 if (enumerator == NULL)
189 return NULL;
190
191 while (g_file_enumerator_iterate (enumerator, NULL, &file, NULL, NULL) && file != NULL)
192 list = g_list_prepend (list, g_object_ref (file));
193
194 return g_list_reverse (list);
195}
196
197#endif /* !G_OS_UNIX */
198
199static void
200format_list_add_row (GtkWidget *list,
201 const char *format_name,
202 GdkContentFormats *formats)
203{
204 GtkWidget *box;
205
206 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 4);
207 gtk_box_append (GTK_BOX (box), child: gtk_label_new (str: format_name));
208
209 gdk_content_formats_unref (formats);
210 gtk_list_box_insert (GTK_LIST_BOX (list), child: box, position: -1);
211}
212
213static void
214clipboard_formats_change_cb (GdkClipboard *clipboard,
215 GParamSpec *pspec,
216 GtkWidget *list)
217{
218 GdkContentFormats *formats;
219 GtkWidget *row;
220 const char * const *mime_types;
221 const GType *gtypes;
222 gsize i, n;
223
224 while ((row = GTK_WIDGET (gtk_list_box_get_row_at_index (GTK_LIST_BOX (list), 0))))
225 gtk_list_box_remove (GTK_LIST_BOX (list), child: row);
226
227 formats = gdk_clipboard_get_formats (clipboard);
228
229 gtypes = gdk_content_formats_get_gtypes (formats, n_gtypes: &n);
230 for (i = 0; i < n; i++)
231 {
232 format_list_add_row (list,
233 format_name: g_type_name (type: gtypes[i]),
234 formats: gdk_content_formats_new_for_gtype (type: gtypes[i]));
235 }
236
237 mime_types = gdk_content_formats_get_mime_types (formats, n_mime_types: &n);
238 for (i = 0; i < n; i++)
239 {
240 format_list_add_row (list,
241 format_name: mime_types[i],
242 formats: gdk_content_formats_new (mime_types: (const char *[2]) { mime_types[i], NULL }, n_mime_types: 1));
243 }
244}
245
246static GtkWidget *
247get_formats_list (GdkClipboard *clipboard)
248{
249 GtkWidget *sw, *list;
250
251 sw = gtk_scrolled_window_new ();
252
253 list = gtk_list_box_new ();
254 g_signal_connect_object (instance: clipboard, detailed_signal: "notify::formats", G_CALLBACK (clipboard_formats_change_cb), gobject: list, connect_flags: 0);
255 clipboard_formats_change_cb (clipboard, NULL, list);
256 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child: list);
257
258 return sw;
259}
260
261static GtkWidget *
262get_contents_widget (GdkClipboard *clipboard)
263{
264 GtkWidget *stack, *child;
265
266 stack = gtk_stack_new ();
267 gtk_widget_set_hexpand (widget: stack, TRUE);
268 gtk_widget_set_vexpand (widget: stack, TRUE);
269 g_signal_connect (stack, "notify::visible-child", G_CALLBACK (visible_child_changed_cb), clipboard);
270 g_signal_connect_object (instance: clipboard, detailed_signal: "changed", G_CALLBACK (clipboard_changed_cb), gobject: stack, connect_flags: 0);
271
272 child = get_formats_list (clipboard);
273 gtk_stack_add_titled (GTK_STACK (stack), child, name: "info", title: "Info");
274
275 child = gtk_image_new ();
276 gtk_stack_add_titled (GTK_STACK (stack), child, name: "image", title: "Image");
277
278 child = gtk_label_new (NULL);
279 gtk_label_set_wrap (GTK_LABEL (child), TRUE);
280 gtk_stack_add_titled (GTK_STACK (stack), child, name: "text", title: "Text");
281
282 return stack;
283}
284
285static void
286provider_button_clicked_cb (GtkWidget *button,
287 GdkClipboard *clipboard)
288{
289 gdk_clipboard_set_content (clipboard,
290 provider: g_object_get_data (G_OBJECT (button), key: "provider"));
291}
292
293static void
294add_provider_button (GtkWidget *box,
295 GdkContentProvider *provider,
296 GdkClipboard *clipboard,
297 const char *name)
298{
299 GtkWidget *button;
300
301 button = gtk_button_new_with_label (label: name);
302 g_signal_connect (button, "clicked", G_CALLBACK (provider_button_clicked_cb), clipboard);
303 if (provider)
304 g_object_set_data_full (G_OBJECT (button), key: "provider", data: provider, destroy: g_object_unref);
305
306 gtk_box_append (GTK_BOX (box), child: button);
307}
308
309static GtkWidget *
310get_button_list (GdkClipboard *clipboard,
311 const char *info)
312{
313 static const guchar invalid_utf8[] = { 'L', 'i', 'b', 'e', 'r', 't', 0xe9, ',', ' ',
314 0xc9, 'g', 'a', 'l', 'i', 't', 0xe9, ',', ' ',
315 'F', 'r', 'a', 't', 'e', 'r', 'n', 'i', 't', 0xe9, 0 };
316 GtkWidget *box;
317 GtkIconPaintable *icon;
318 GdkTexture *texture;
319 GValue value = G_VALUE_INIT;
320
321 box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
322
323 gtk_box_append (GTK_BOX (box), child: gtk_label_new (str: info));
324
325 add_provider_button (box,
326 NULL,
327 clipboard,
328 name: "Empty");
329
330 g_value_init (value: &value, GDK_TYPE_PIXBUF);
331 icon = gtk_icon_theme_lookup_icon (self: gtk_icon_theme_get_for_display (display: gdk_clipboard_get_display (clipboard)),
332 icon_name: "utilities-terminal",
333 NULL,
334 size: 48, scale: 1,
335 direction: gtk_widget_get_direction (widget: box),
336 flags: 0);
337 texture = render_paintable_to_texture (paintable: GDK_PAINTABLE (ptr: icon));
338 g_value_take_object (value: &value, v_object: gdk_pixbuf_get_from_texture (texture));
339 g_object_unref (object: texture);
340 g_object_unref (object: icon);
341 add_provider_button (box,
342 provider: gdk_content_provider_new_for_value (value: &value),
343 clipboard,
344 name: "GdkPixbuf");
345 g_value_unset (value: &value);
346
347 add_provider_button (box,
348 provider: gdk_content_provider_new_typed (G_TYPE_STRING, "Hello Clipboard ☺"),
349 clipboard,
350 name: "gchararry");
351
352 add_provider_button (box,
353 provider: gdk_content_provider_new_for_bytes (mime_type: "text/plain;charset=utf-8",
354 bytes: g_bytes_new_static (data: "𝕳𝖊𝖑𝖑𝖔 𝖀𝖓𝖎𝖈𝖔𝖉𝖊",
355 size: strlen (s: "𝕳𝖊𝖑𝖑𝖔 𝖀𝖓𝖎𝖈𝖔𝖉𝖊") + 1)),
356 clipboard,
357 name: "text/plain");
358
359 add_provider_button (box,
360 provider: gdk_content_provider_new_for_bytes (mime_type: "text/plain;charset=utf-8",
361 bytes: g_bytes_new_static (data: invalid_utf8, size: sizeof(invalid_utf8))),
362 clipboard,
363 name: "Invalid UTF-8");
364
365 g_value_init (value: &value, G_TYPE_FILE);
366 g_value_take_object (value: &value, v_object: g_file_new_for_path (path: g_get_home_dir ()));
367 add_provider_button (box,
368 provider: gdk_content_provider_new_for_value (value: &value),
369 clipboard,
370 name: "home directory");
371 g_value_unset (value: &value);
372
373 g_value_init (value: &value, GDK_TYPE_FILE_LIST);
374 g_value_take_boxed (value: &value, v_boxed: get_file_list (dir: g_get_home_dir ()));
375 add_provider_button (box,
376 provider: gdk_content_provider_new_for_value (value: &value),
377 clipboard,
378 name: "files in home");
379 return box;
380}
381
382static GtkWidget *
383get_clipboard_widget (GdkClipboard *clipboard,
384 GdkClipboard *alt_clipboard,
385 const char *name)
386{
387 GtkWidget *vbox, *hbox, *stack, *switcher;
388
389 hbox = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 0);
390 vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 0);
391 gtk_box_append (GTK_BOX (hbox), child: vbox);
392 gtk_box_append (GTK_BOX (vbox), child: gtk_label_new (str: name));
393 switcher = gtk_stack_switcher_new ();
394 gtk_box_append (GTK_BOX (vbox), child: switcher);
395 stack = get_contents_widget (clipboard);
396 gtk_box_append (GTK_BOX (vbox), child: stack);
397 gtk_stack_switcher_set_stack (GTK_STACK_SWITCHER (switcher), GTK_STACK (stack));
398 gtk_box_append (GTK_BOX (hbox), child: get_button_list (clipboard, info: "Set Locally:"));
399 if (clipboard != alt_clipboard)
400 gtk_box_append (GTK_BOX (hbox), child: get_button_list (clipboard: alt_clipboard, info: "Set Remotely:"));
401
402 return hbox;
403}
404
405static GtkWidget *
406get_window_contents (GdkDisplay *display,
407 GdkDisplay *alt_display)
408{
409 GtkWidget *box;
410
411 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 6);
412 gtk_box_set_homogeneous (GTK_BOX (box), TRUE);
413 gtk_box_append (GTK_BOX (box),
414 child: get_clipboard_widget (clipboard: gdk_display_get_clipboard (display),
415 alt_clipboard: gdk_display_get_clipboard (display: alt_display),
416 name: "Clipboard"));
417 gtk_box_append (GTK_BOX (box),
418 child: get_clipboard_widget (clipboard: gdk_display_get_primary_clipboard (display),
419 alt_clipboard: gdk_display_get_primary_clipboard (display: alt_display),
420 name: "Primary Clipboard"));
421
422 return box;
423}
424
425static void
426quit_cb (GtkWidget *widget,
427 gpointer data)
428{
429 gboolean *done = data;
430
431 *done = TRUE;
432
433 g_main_context_wakeup (NULL);
434}
435
436int
437main (int argc, char **argv)
438{
439 GtkWidget *window;
440 GdkDisplay *alt_display;
441 gboolean done = FALSE;
442
443 gtk_init ();
444
445 alt_display = gdk_display_open (NULL);
446 if (alt_display == NULL)
447 alt_display = gdk_display_get_default ();
448
449 window = gtk_window_new ();
450 g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done);
451 gtk_window_set_child (GTK_WINDOW (window),
452 child: get_window_contents (display: gtk_widget_get_display (widget: window),
453 alt_display));
454
455 gtk_widget_show (widget: window);
456
457 while (!done)
458 g_main_context_iteration (NULL, TRUE);
459
460 return 0;
461}
462

source code of gtk/tests/testclipboard2.c