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 | |
20 | static GdkTexture * |
21 | render_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 | |
62 | static void |
63 | clipboard_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 | |
77 | static void |
78 | texture_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 | |
97 | static void |
98 | text_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 | |
117 | static void |
118 | visible_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 | |
150 | static GSList * |
151 | get_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 | |
178 | static GList * |
179 | get_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 | |
199 | static void |
200 | format_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 | |
213 | static void |
214 | clipboard_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 | |
246 | static GtkWidget * |
247 | get_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 | |
261 | static GtkWidget * |
262 | get_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 | |
285 | static void |
286 | provider_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 | |
293 | static void |
294 | add_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 | |
309 | static GtkWidget * |
310 | get_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 | |
382 | static GtkWidget * |
383 | get_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 | |
405 | static GtkWidget * |
406 | get_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 | |
425 | static void |
426 | quit_cb (GtkWidget *widget, |
427 | gpointer data) |
428 | { |
429 | gboolean *done = data; |
430 | |
431 | *done = TRUE; |
432 | |
433 | g_main_context_wakeup (NULL); |
434 | } |
435 | |
436 | int |
437 | main (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 | |