Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-notification-upload"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <gdk-pixbuf/gdk-pixbuf.h>
10 : : #include <json-glib/json-glib.h>
11 : : #include <valent.h>
12 : :
13 : : #ifdef HAVE_GTK4
14 : : #include <gtk/gtk.h>
15 : : #endif /* HAVE_GTK4 */
16 : :
17 : : #include "valent-notification-upload.h"
18 : :
19 : : #define DEFAULT_ICON_SIZE 512
20 : :
21 : :
22 : : /**
23 : : * ValentNotificationUpload:
24 : : *
25 : : * A class for notification icon uploads.
26 : : *
27 : : * `ValentNotificationUpload` is a class that abstracts uploading notifications
28 : : * with icon payloads for `ValentNotificationPlugin`.
29 : : */
30 : :
31 : : struct _ValentNotificationUpload
32 : : {
33 : : ValentTransfer parent_instance;
34 : :
35 : : ValentDevice *device;
36 : : JsonNode *packet;
37 : : GIcon *icon;
38 : : };
39 : :
40 [ + + + - ]: 14 : G_DEFINE_FINAL_TYPE (ValentNotificationUpload, valent_notification_upload, VALENT_TYPE_TRANSFER)
41 : :
42 : : enum {
43 : : PROP_0,
44 : : PROP_DEVICE,
45 : : PROP_ICON,
46 : : PROP_PACKET,
47 : : N_PROPERTIES,
48 : : };
49 : :
50 : : static GParamSpec *properties[N_PROPERTIES] = { 0, };
51 : :
52 : :
53 : : #ifdef HAVE_GTK4
54 : : static GtkIconTheme *
55 : 2 : _gtk_icon_theme_get_default (void)
56 : : {
57 : 2 : static GtkIconTheme *icon_theme = NULL;
58 : 2 : size_t guard = 0;
59 : :
60 [ + - + - ]: 2 : if (g_once_init_enter (&guard))
61 : : {
62 [ - + ]: 2 : if (gtk_is_initialized ())
63 : : {
64 : 0 : GdkDisplay *display = NULL;
65 : :
66 [ # # ]: 0 : if ((display = gdk_display_get_default ()) != NULL)
67 : 0 : icon_theme = gtk_icon_theme_get_for_display (display);
68 : : }
69 : :
70 : 2 : g_once_init_leave (&guard, 1);
71 : : }
72 : :
73 : 2 : return icon_theme;
74 : : }
75 : :
76 : : static int
77 : 0 : _gtk_icon_theme_get_largest_icon (GtkIconTheme *theme,
78 : : const char *name)
79 : : {
80 : 0 : g_autofree int *sizes = NULL;
81 : 0 : int ret = 0;
82 : :
83 [ # # # # : 0 : g_assert (GTK_IS_ICON_THEME (theme));
# # # # ]
84 [ # # ]: 0 : g_assert (name != NULL);
85 : :
86 [ # # ]: 0 : if (!gtk_icon_theme_has_icon (theme, name))
87 : : return ret;
88 : :
89 : 0 : sizes = gtk_icon_theme_get_icon_sizes (theme, name);
90 : :
91 [ # # ]: 0 : for (unsigned int i = 0; sizes[i] != 0; i++)
92 : : {
93 [ # # ]: 0 : if (sizes[i] == -1)
94 : : return -1;
95 : :
96 : 0 : if (sizes[i] > ret)
97 : : ret = sizes[i];
98 : : }
99 : :
100 : : return ret;
101 : : }
102 : :
103 : : static GFile *
104 : 2 : get_largest_icon_file (GIcon *icon)
105 : : {
106 : 2 : GtkIconTheme *icon_theme = NULL;
107 : 2 : const char * const *names;
108 : 4 : g_autoptr (GtkIconPaintable) info = NULL;
109 : :
110 [ + - + - : 2 : g_assert (G_IS_THEMED_ICON (icon));
- + - - ]
111 : :
112 : 2 : icon_theme = _gtk_icon_theme_get_default ();
113 [ - + ]: 2 : if (icon_theme == NULL)
114 : : return NULL;
115 : :
116 : 0 : names = g_themed_icon_get_names (G_THEMED_ICON (icon));
117 [ # # ]: 0 : for (size_t i = 0; names[i]; i++)
118 : : {
119 : 0 : int size;
120 : :
121 : 0 : size = _gtk_icon_theme_get_largest_icon (icon_theme, names[i]);
122 [ # # ]: 0 : if (size == 0)
123 : 0 : continue;
124 : :
125 : 0 : info = gtk_icon_theme_lookup_icon (icon_theme,
126 : : names[i],
127 : : NULL,
128 : : size,
129 : : 1,
130 : : GTK_TEXT_DIR_NONE,
131 : : 0);
132 : :
133 [ # # ]: 0 : if (info != NULL)
134 : 0 : return gtk_icon_paintable_get_file (info);
135 : : }
136 : :
137 : : return NULL;
138 : : }
139 : : #endif /* HAVE_GTK4 */
140 : :
141 : : static void
142 : 2 : on_size_prepared (GdkPixbufLoader *loader,
143 : : int width,
144 : : int height,
145 : : gpointer user_data)
146 : : {
147 : 2 : GdkPixbufFormat *format = gdk_pixbuf_loader_get_format (loader);
148 : :
149 [ - + ]: 2 : if (!gdk_pixbuf_format_is_scalable (format))
150 : : return;
151 : :
152 [ # # ]: 0 : if (width >= DEFAULT_ICON_SIZE || height >= DEFAULT_ICON_SIZE)
153 : : return;
154 : :
155 : 0 : gdk_pixbuf_loader_set_size (loader, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
156 : : }
157 : :
158 : : static GBytes *
159 : 4 : valent_notification_upload_get_icon_bytes (GIcon *icon,
160 : : GCancellable *cancellable,
161 : : GError **error)
162 : : {
163 : 8 : g_autoptr (GBytes) bytes = NULL;
164 [ + + ]: 4 : g_autoptr (GdkPixbufLoader) loader = NULL;
165 : 4 : GdkPixbuf *pixbuf = NULL;
166 [ + + ]: 4 : g_autoptr (GError) warn = NULL;
167 : 4 : char *data;
168 : 4 : size_t size;
169 : :
170 [ + - + - : 4 : g_assert (G_IS_ICON (icon));
+ - - + ]
171 [ + - + - : 4 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
172 [ + - - + ]: 4 : g_assert (error == NULL || *error == NULL);
173 : :
174 : : /* First try to get the bytes of the GIcon */
175 [ + - + + : 4 : if (G_IS_THEMED_ICON (icon))
- + ]
176 : : {
177 : 6 : g_autoptr (GFile) file = NULL;
178 : :
179 : : #ifdef HAVE_GTK4
180 : 2 : file = get_largest_icon_file (icon);
181 [ - + ]: 2 : if (file == NULL)
182 : : {
183 : 2 : g_set_error (error,
184 : : G_IO_ERROR,
185 : : G_IO_ERROR_FAILED,
186 : : "Failed to load themed icon");
187 : : }
188 : : #else
189 : : g_set_error (error,
190 : : G_IO_ERROR,
191 : : G_IO_ERROR_NOT_SUPPORTED,
192 : : "GTK is not initialized");
193 : : #endif /* HAVE_GTK4 */
194 : :
195 : 2 : if (file != NULL)
196 : 0 : bytes = g_file_load_bytes (file, cancellable, NULL, error);
197 : : }
198 [ + - + + : 2 : else if (G_IS_FILE_ICON (icon))
- + ]
199 : : {
200 : 1 : GFile *file;
201 : :
202 : 1 : file = g_file_icon_get_file (G_FILE_ICON (icon));
203 : 1 : bytes = g_file_load_bytes (file, cancellable, NULL, error);
204 : : }
205 [ + - - + : 1 : else if (G_IS_BYTES_ICON (icon))
- - ]
206 : : {
207 : 1 : GBytes *buffer;
208 : :
209 : 1 : buffer = g_bytes_icon_get_bytes (G_BYTES_ICON (icon));
210 : :
211 [ - + ]: 1 : if (buffer == NULL)
212 : : {
213 : 0 : g_set_error (error,
214 : : G_IO_ERROR,
215 : : G_IO_ERROR_FAILED,
216 : : "Failed to load icon bytes");
217 : 0 : return NULL;
218 : : }
219 : :
220 : 1 : bytes = g_bytes_ref (buffer);
221 : : }
222 [ # # # # : 0 : else if (GDK_IS_PIXBUF (icon))
# # ]
223 : : {
224 : 0 : bytes = gdk_pixbuf_read_pixel_bytes (GDK_PIXBUF (icon));
225 : : }
226 : :
227 [ + + ]: 4 : if (bytes == NULL)
228 : 2 : return NULL;
229 : :
230 : : /* Now attempt to load the bytes as a pixbuf */
231 : 2 : loader = gdk_pixbuf_loader_new ();
232 : :
233 : 2 : g_signal_connect_object (loader,
234 : : "size-prepared",
235 : : G_CALLBACK (on_size_prepared),
236 : : NULL, 0);
237 : :
238 [ + - - + ]: 4 : if (!gdk_pixbuf_loader_write_bytes (loader, bytes, &warn) ||
239 : 2 : !gdk_pixbuf_loader_close (loader, &warn))
240 : : {
241 : 0 : g_debug ("%s(): %s", G_STRFUNC, warn->message);
242 : 0 : return g_steal_pointer (&bytes);
243 : : }
244 : :
245 [ - + ]: 2 : if ((pixbuf = gdk_pixbuf_loader_get_pixbuf (loader)) == NULL)
246 : : {
247 : 0 : g_debug ("%s(): Failed to create pixbuf from bytes", G_STRFUNC);
248 : 0 : return g_steal_pointer (&bytes);
249 : : }
250 : :
251 [ - + ]: 2 : if (!gdk_pixbuf_save_to_buffer (pixbuf, &data, &size, "png", &warn, NULL))
252 : : {
253 : 0 : g_debug ("%s(): %s", G_STRFUNC, warn->message);
254 : 0 : return g_steal_pointer (&bytes);
255 : : }
256 : :
257 : 2 : return g_bytes_new_take (data, size);
258 : : }
259 : :
260 : : /*
261 : : * ValentTransfer
262 : : */
263 : : static void
264 : 4 : valent_notification_upload_execute_task (GTask *task,
265 : : gpointer source_object,
266 : : gpointer task_data,
267 : : GCancellable *cancellable)
268 : : {
269 : 4 : ValentNotificationUpload *self = VALENT_NOTIFICATION_UPLOAD (source_object);
270 : 4 : g_autoptr (ValentChannel) channel = NULL;
271 [ + - ]: 4 : g_autoptr (GIcon) icon = NULL;
272 [ + - + - ]: 4 : g_autoptr (JsonNode) packet = NULL;
273 [ + - + - ]: 4 : g_autoptr (GBytes) bytes = NULL;
274 [ - + ]: 4 : g_autoptr (GIOStream) target = NULL;
275 [ - + ]: 4 : g_autofree char *payload_hash = NULL;
276 : 4 : const uint8_t *payload_data = NULL;
277 : 4 : size_t payload_size = 0;
278 : 4 : gboolean ret = FALSE;
279 : 4 : GError *error = NULL;
280 : :
281 [ + - ]: 4 : g_assert (VALENT_IS_NOTIFICATION_UPLOAD (self));
282 : :
283 [ + - ]: 4 : if (g_task_return_error_if_cancelled (task))
284 : : return;
285 : :
286 : 4 : valent_object_lock (VALENT_OBJECT (self));
287 : 4 : channel = valent_device_ref_channel (self->device);
288 : 4 : icon = g_object_ref (self->icon);
289 : 4 : packet = json_node_ref (self->packet);
290 : 4 : valent_object_unlock (VALENT_OBJECT (self));
291 : :
292 [ - + ]: 4 : if (channel == NULL)
293 : : {
294 : 0 : g_task_return_new_error (task,
295 : : G_IO_ERROR,
296 : : G_IO_ERROR_NOT_CONNECTED,
297 : : "Device is disconnected");
298 : 0 : return;
299 : : }
300 : :
301 : : /* Try to get the icon bytes */
302 : 4 : bytes = valent_notification_upload_get_icon_bytes (icon, cancellable, &error);
303 : :
304 [ + + ]: 4 : if (bytes == NULL)
305 : 2 : return g_task_return_error (task, error);
306 : :
307 : : /* A payload hash is included, allowing the remote device to ignore icon
308 : : * transfers that it already has cached. */
309 : 2 : payload_data = g_bytes_get_data (bytes, &payload_size);
310 : 2 : payload_hash = g_compute_checksum_for_data (G_CHECKSUM_MD5,
311 : : payload_data,
312 : : payload_size);
313 : 2 : json_object_set_string_member (valent_packet_get_body (packet),
314 : : "payloadHash",
315 : : payload_hash);
316 : 2 : valent_packet_set_payload_size (packet, payload_size);
317 : :
318 : 2 : target = valent_channel_upload (channel, packet, cancellable, &error);
319 : :
320 [ - + ]: 2 : if (target == NULL)
321 : 0 : return g_task_return_error (task, error);
322 : :
323 : : /* Upload the icon */
324 : 2 : ret = g_output_stream_write_all (g_io_stream_get_output_stream (target),
325 : : payload_data,
326 : : payload_size,
327 : : NULL,
328 : : cancellable,
329 : : &error);
330 : :
331 [ - + ]: 2 : if (!ret)
332 : 0 : return g_task_return_error (task, error);
333 : :
334 : 2 : g_task_return_boolean (task, TRUE);
335 : : }
336 : :
337 : : static void
338 : 4 : valent_notification_upload_execute (ValentTransfer *transfer,
339 : : GCancellable *cancellable,
340 : : GAsyncReadyCallback callback,
341 : : gpointer user_data)
342 : : {
343 : 8 : g_autoptr (GTask) task = NULL;
344 : :
345 [ + - ]: 4 : g_assert (VALENT_IS_NOTIFICATION_UPLOAD (transfer));
346 [ + - + - : 4 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
347 : :
348 : 4 : task = g_task_new (transfer, cancellable, callback, user_data);
349 [ + - ]: 4 : g_task_set_source_tag (task, valent_notification_upload_execute);
350 [ + - ]: 4 : g_task_run_in_thread (task, valent_notification_upload_execute_task);
351 : 4 : }
352 : :
353 : : /*
354 : : * GObject
355 : : */
356 : : static void
357 : 4 : valent_notification_upload_finalize (GObject *object)
358 : : {
359 : 4 : ValentNotificationUpload *self = VALENT_NOTIFICATION_UPLOAD (object);
360 : :
361 : 4 : valent_object_lock (VALENT_OBJECT (self));
362 [ + - ]: 4 : g_clear_object (&self->device);
363 [ + - ]: 4 : g_clear_object (&self->icon);
364 [ + - ]: 4 : g_clear_pointer (&self->packet, json_node_unref);
365 : 4 : valent_object_unlock (VALENT_OBJECT (self));
366 : :
367 : 4 : G_OBJECT_CLASS (valent_notification_upload_parent_class)->finalize (object);
368 : 4 : }
369 : :
370 : : static void
371 : 4 : valent_notification_upload_get_property (GObject *object,
372 : : guint prop_id,
373 : : GValue *value,
374 : : GParamSpec *pspec)
375 : : {
376 : 4 : ValentNotificationUpload *self = VALENT_NOTIFICATION_UPLOAD (object);
377 : :
378 [ + - + - ]: 4 : switch (prop_id)
379 : : {
380 : : case PROP_DEVICE:
381 : 2 : valent_object_lock (VALENT_OBJECT (self));
382 : 2 : g_value_set_object (value, self->device);
383 : 2 : valent_object_unlock (VALENT_OBJECT (self));
384 : 2 : break;
385 : :
386 : : case PROP_ICON:
387 : 0 : valent_object_lock (VALENT_OBJECT (self));
388 : 0 : g_value_set_object (value, self->icon);
389 : 0 : valent_object_unlock (VALENT_OBJECT (self));
390 : 0 : break;
391 : :
392 : : case PROP_PACKET:
393 : 2 : valent_object_lock (VALENT_OBJECT (self));
394 : 2 : g_value_set_boxed (value, self->packet);
395 : 2 : valent_object_unlock (VALENT_OBJECT (self));
396 : 2 : break;
397 : :
398 : 0 : default:
399 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
400 : : }
401 : 4 : }
402 : :
403 : : static void
404 : 12 : valent_notification_upload_set_property (GObject *object,
405 : : guint prop_id,
406 : : const GValue *value,
407 : : GParamSpec *pspec)
408 : : {
409 : 12 : ValentNotificationUpload *self = VALENT_NOTIFICATION_UPLOAD (object);
410 : :
411 [ + + + - ]: 12 : switch (prop_id)
412 : : {
413 : : case PROP_DEVICE:
414 : 4 : valent_object_lock (VALENT_OBJECT (self));
415 : 4 : self->device = g_value_dup_object (value);
416 : 4 : valent_object_unlock (VALENT_OBJECT (self));
417 : 4 : break;
418 : :
419 : : case PROP_ICON:
420 : 4 : valent_object_lock (VALENT_OBJECT (self));
421 : 4 : self->icon = g_value_dup_object (value);
422 : 4 : valent_object_unlock (VALENT_OBJECT (self));
423 : 4 : break;
424 : :
425 : : case PROP_PACKET:
426 : 4 : valent_object_lock (VALENT_OBJECT (self));
427 : 4 : self->packet = g_value_dup_boxed (value);
428 : 4 : valent_object_unlock (VALENT_OBJECT (self));
429 : 4 : break;
430 : :
431 : 0 : default:
432 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
433 : : }
434 : 12 : }
435 : :
436 : : static void
437 : 1 : valent_notification_upload_class_init (ValentNotificationUploadClass *klass)
438 : : {
439 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
440 : 1 : ValentTransferClass *transfer_class = VALENT_TRANSFER_CLASS (klass);
441 : :
442 : 1 : object_class->finalize = valent_notification_upload_finalize;
443 : 1 : object_class->get_property = valent_notification_upload_get_property;
444 : 1 : object_class->set_property = valent_notification_upload_set_property;
445 : :
446 : 1 : transfer_class->execute = valent_notification_upload_execute;
447 : :
448 : : /**
449 : : * ValentNotificationUpload:device:
450 : : *
451 : : * The [class@Valent.Device] this transfer is for.
452 : : */
453 : 2 : properties [PROP_DEVICE] =
454 : 1 : g_param_spec_object ("device", NULL, NULL,
455 : : VALENT_TYPE_DEVICE,
456 : : (G_PARAM_READWRITE |
457 : : G_PARAM_CONSTRUCT_ONLY |
458 : : G_PARAM_EXPLICIT_NOTIFY |
459 : : G_PARAM_STATIC_STRINGS));
460 : :
461 : : /**
462 : : * ValentNotificationUpload:icon:
463 : : *
464 : : * The [iface@Gio.Icon] for the notification.
465 : : */
466 : 2 : properties [PROP_ICON] =
467 : 1 : g_param_spec_object ("icon", NULL, NULL,
468 : : G_TYPE_ICON,
469 : : (G_PARAM_READWRITE |
470 : : G_PARAM_CONSTRUCT_ONLY |
471 : : G_PARAM_EXPLICIT_NOTIFY |
472 : : G_PARAM_STATIC_STRINGS));
473 : :
474 : : /**
475 : : * ValentNotificationUpload:packet:
476 : : *
477 : : * The packet to send the payload with.
478 : : */
479 : 2 : properties [PROP_PACKET] =
480 : 1 : g_param_spec_boxed ("packet", NULL, NULL,
481 : : JSON_TYPE_NODE,
482 : : (G_PARAM_READWRITE |
483 : : G_PARAM_CONSTRUCT_ONLY |
484 : : G_PARAM_EXPLICIT_NOTIFY |
485 : : G_PARAM_STATIC_STRINGS));
486 : :
487 : 1 : g_object_class_install_properties (object_class, N_PROPERTIES, properties);
488 : 1 : }
489 : :
490 : : static void
491 : 4 : valent_notification_upload_init (ValentNotificationUpload *self)
492 : : {
493 : 4 : }
494 : :
495 : : /**
496 : : * valent_notification_upload_new:
497 : : * @device: a `ValentDevice`
498 : : * @packet: a KDE Connect packet
499 : : * @icon: a `GIcon`
500 : : *
501 : : * Create a new `ValentNotificationUpload` for @packet and @icon.
502 : : *
503 : : * Returns: (transfer full): a `ValentNotificationUpload`
504 : : */
505 : : ValentTransfer *
506 : 4 : valent_notification_upload_new (ValentDevice *device,
507 : : JsonNode *packet,
508 : : GIcon *icon)
509 : : {
510 [ + - ]: 4 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
511 [ - + ]: 4 : g_return_val_if_fail (VALENT_IS_PACKET (packet), NULL);
512 [ + - + - : 4 : g_return_val_if_fail (G_IS_ICON (icon), NULL);
+ - - + ]
513 : :
514 : 4 : return g_object_new (VALENT_TYPE_NOTIFICATION_UPLOAD,
515 : : "device", device,
516 : : "icon", icon,
517 : : "packet", packet,
518 : : NULL);
519 : : }
520 : :
|