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-plugin"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <libportal/portal.h>
9 : : #include <gio/gio.h>
10 : : #include <json-glib/json-glib.h>
11 : : #include <valent.h>
12 : :
13 : : #include "valent-notification-plugin.h"
14 : : #include "valent-notification-upload.h"
15 : :
16 : : #define DEFAULT_ICON_SIZE 512
17 : :
18 : :
19 : : struct _ValentNotificationPlugin
20 : : {
21 : : ValentDevicePlugin parent_instance;
22 : :
23 : : GCancellable *cancellable;
24 : : ValentNotifications *notifications;
25 : : ValentSession *session;
26 : :
27 : : GHashTable *cache;
28 : : unsigned int notifications_watch : 1;
29 : : };
30 : :
31 [ + + + - ]: 112 : G_DEFINE_FINAL_TYPE (ValentNotificationPlugin, valent_notification_plugin, VALENT_TYPE_DEVICE_PLUGIN)
32 : :
33 : : static void valent_notification_plugin_handle_notification (ValentNotificationPlugin *self,
34 : : JsonNode *packet);
35 : : static void valent_notification_plugin_handle_notification_action (ValentNotificationPlugin *self,
36 : : JsonNode *packet);
37 : : static void valent_notification_plugin_handle_notification_reply (ValentNotificationPlugin *self,
38 : : JsonNode *packet);
39 : : static void valent_notification_plugin_handle_notification_request (ValentNotificationPlugin *self,
40 : : JsonNode *packet);
41 : :
42 : : static void valent_notification_plugin_close_notification (ValentNotificationPlugin *self,
43 : : const char *id);
44 : : static void valent_notification_plugin_request_notifications (ValentNotificationPlugin *self);
45 : : static void valent_notification_plugin_send_notification (ValentNotificationPlugin *self,
46 : : const char *id,
47 : : const char *appName,
48 : : const char *title,
49 : : const char *body,
50 : : GIcon *icon);
51 : : static void valent_notification_plugin_show_notification (ValentNotificationPlugin *self,
52 : : JsonNode *packet,
53 : : GIcon *gicon);
54 : :
55 : :
56 : : /*
57 : : * ValentNotifications Callbacks
58 : : */
59 : : static void
60 : 5 : on_notification_added (ValentNotifications *listener,
61 : : ValentNotification *notification,
62 : : ValentNotificationPlugin *self)
63 : : {
64 : 5 : GSettings *settings;
65 : 5 : const char *application;
66 : 10 : g_auto (GStrv) deny = NULL;
67 : :
68 [ - + ]: 5 : g_assert (VALENT_IS_NOTIFICATIONS (listener));
69 [ + - ]: 5 : g_assert (VALENT_IS_NOTIFICATION (notification));
70 [ + - ]: 5 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
71 : :
72 : 5 : settings = valent_extension_get_settings (VALENT_EXTENSION (self));
73 : :
74 [ + - ]: 5 : if (!g_settings_get_boolean (settings, "forward-notifications"))
75 : : return;
76 : :
77 [ - + - - ]: 5 : if (!g_settings_get_boolean (settings, "forward-when-active") &&
78 : 0 : valent_session_get_active (self->session))
79 : : return;
80 : :
81 : 5 : application = valent_notification_get_application (notification);
82 : 5 : deny = g_settings_get_strv (settings, "forward-deny");
83 : :
84 [ + + - + ]: 5 : if (application && g_strv_contains ((const char * const *)deny, application))
85 [ # # ]: 0 : return;
86 : :
87 [ + - ]: 5 : valent_notification_plugin_send_notification (self,
88 : : valent_notification_get_id (notification),
89 : : valent_notification_get_application (notification),
90 : : valent_resource_get_title (VALENT_RESOURCE (notification)),
91 : : valent_notification_get_body (notification),
92 : : valent_notification_get_icon (notification));
93 : : }
94 : :
95 : : static void
96 : 1 : on_notification_removed (ValentNotifications *notifications,
97 : : const char *id,
98 : : ValentNotificationPlugin *self)
99 : : {
100 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATIONS (notifications));
101 [ + - ]: 1 : g_assert (id != NULL);
102 : :
103 : 1 : valent_notification_plugin_close_notification (self, id);
104 : 1 : }
105 : :
106 : : static void
107 : 25 : valent_notification_plugin_watch_notifications (ValentNotificationPlugin *self,
108 : : gboolean watch)
109 : : {
110 : 25 : ValentNotifications *notifications = valent_notifications_get_default ();
111 : :
112 [ - + ]: 25 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
113 : :
114 [ + + ]: 25 : if (self->notifications_watch == watch)
115 : : return;
116 : :
117 [ + + ]: 10 : if (watch)
118 : : {
119 : 5 : g_signal_connect_object (notifications,
120 : : "notification-added",
121 : : G_CALLBACK (on_notification_added),
122 : : self, 0);
123 : 5 : g_signal_connect_object (notifications,
124 : : "notification-removed",
125 : : G_CALLBACK (on_notification_removed),
126 : : self, 0);
127 : 5 : self->notifications_watch = TRUE;
128 : : }
129 : : else
130 : : {
131 : 5 : g_signal_handlers_disconnect_by_data (notifications, self);
132 : 5 : self->notifications_watch = FALSE;
133 : : }
134 : : }
135 : :
136 : : /*
137 : : * Icon Transfers
138 : : */
139 : : static GFile *
140 : 1 : valent_notification_plugin_get_icon_file (ValentNotificationPlugin *self,
141 : : JsonNode *packet)
142 : : {
143 : 2 : g_autoptr (GFile) file = NULL;
144 : 1 : const char *payload_hash;
145 : :
146 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
147 [ + - ]: 1 : g_assert (VALENT_IS_PACKET (packet));
148 : :
149 [ + - ]: 1 : if (valent_packet_get_string (packet, "payloadHash", &payload_hash))
150 : : {
151 : 1 : ValentContext *context = NULL;
152 : :
153 : 1 : context = valent_extension_get_context (VALENT_EXTENSION (self));
154 : 1 : file = valent_context_get_cache_file (context, payload_hash);
155 : : }
156 : : else
157 : : {
158 : 0 : g_autoptr (GFileIOStream) stream = NULL;
159 : :
160 [ # # ]: 0 : file = g_file_new_tmp ("valent-notification-icon.XXXXXX", &stream, NULL);
161 : : }
162 : :
163 : 1 : return g_steal_pointer (&file);
164 : : }
165 : :
166 : : static void download_icon_from_cache_cb (GFile *file,
167 : : GAsyncResult *result,
168 : : gpointer user_data);
169 : :
170 : : static void
171 : 1 : download_icon_from_device_cb (ValentTransfer *transfer,
172 : : GAsyncResult *result,
173 : : gpointer user_data)
174 : : {
175 : 2 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
176 [ - - + - ]: 1 : g_autoptr (GFile) file = NULL;
177 [ - - + - ]: 1 : g_autoptr (GBytes) bytes = NULL;
178 : 1 : GError *error = NULL;
179 : :
180 [ - + ]: 1 : if (!valent_transfer_execute_finish (transfer, result, &error))
181 : : {
182 : 0 : g_task_return_error (task, g_steal_pointer (&error));
183 [ # # ]: 0 : return;
184 : : }
185 : :
186 : 1 : file = valent_device_transfer_ref_file (VALENT_DEVICE_TRANSFER (transfer));
187 [ + - ]: 1 : g_file_load_bytes_async (file,
188 : : g_task_get_cancellable (task),
189 : : (GAsyncReadyCallback)download_icon_from_cache_cb,
190 : : g_object_ref (task));
191 : : }
192 : :
193 : : static void
194 : 2 : download_icon_from_cache_cb (GFile *file,
195 : : GAsyncResult *result,
196 : : gpointer user_data)
197 : : {
198 : 2 : g_autoptr (GTask) task = G_TASK (g_steal_pointer (&user_data));
199 : 2 : ValentNotificationPlugin *self = g_task_get_source_object (task);
200 : 2 : JsonNode *packet = g_task_get_task_data (task);
201 [ + - + - ]: 2 : g_autoptr (GBytes) bytes = NULL;
202 : 2 : ValentDevice *device = NULL;
203 [ + - ]: 2 : g_autoptr (ValentTransfer) transfer = NULL;
204 : :
205 : 2 : bytes = g_file_load_bytes_finish (file, result, NULL, NULL);
206 [ + + ]: 2 : if (bytes != NULL)
207 : : {
208 : 1 : g_task_return_pointer (task, g_bytes_icon_new (bytes), g_object_unref);
209 : 1 : return;
210 : : }
211 : :
212 : 1 : device = valent_resource_get_source (VALENT_RESOURCE (self));
213 : 1 : transfer = valent_device_transfer_new (device, packet, file);
214 [ + - ]: 1 : valent_transfer_execute (transfer,
215 : : g_task_get_cancellable (task),
216 : : (GAsyncReadyCallback)download_icon_from_device_cb,
217 : : g_object_ref (task));
218 : : }
219 : :
220 : : static void
221 : 1 : valent_notification_plugin_download_icon (ValentNotificationPlugin *self,
222 : : JsonNode *packet,
223 : : GCancellable *cancellable,
224 : : GAsyncReadyCallback callback,
225 : : gpointer user_data)
226 : : {
227 : 2 : g_autoptr (GTask) task = NULL;
228 [ + - ]: 1 : g_autoptr (GFile) file = NULL;
229 : :
230 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
231 [ + - ]: 1 : g_assert (VALENT_IS_PACKET (packet));
232 [ + - + - : 1 : g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
233 : :
234 : 1 : task = g_task_new (self, cancellable, callback, user_data);
235 [ + - ]: 1 : g_task_set_source_tag (task, valent_notification_plugin_download_icon);
236 : 1 : g_task_set_task_data (task, json_node_ref (packet),
237 : : (GDestroyNotify)json_node_unref);
238 : :
239 : 1 : file = valent_notification_plugin_get_icon_file (self, packet);
240 [ + - ]: 1 : g_file_load_bytes_async (file,
241 : : cancellable,
242 : : (GAsyncReadyCallback)download_icon_from_cache_cb,
243 : : g_object_ref (task));
244 : 1 : }
245 : :
246 : : static GIcon *
247 : 1 : valent_notification_plugin_download_icon_finish (ValentNotificationPlugin *self,
248 : : GAsyncResult *result,
249 : : GError **error)
250 : : {
251 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
252 [ + - ]: 1 : g_assert (g_task_is_valid (result, self));
253 [ + - + - ]: 1 : g_assert (error == NULL || *error == NULL);
254 : :
255 : 1 : return g_task_propagate_pointer (G_TASK (result), error);
256 : : }
257 : :
258 : : /*
259 : : * Remote Notifications
260 : : */
261 : : static void
262 : 4 : valent_notification_plugin_show_notification (ValentNotificationPlugin *self,
263 : : JsonNode *packet,
264 : : GIcon *gicon)
265 : : {
266 : 4 : ValentDevice *device;
267 : 4 : g_autoptr (GNotification) notification = NULL;
268 [ + - ]: 4 : g_autoptr (GIcon) icon = NULL;
269 : 4 : const char *id;
270 : 4 : const char *app_name = NULL;
271 : 4 : const char *title = NULL;
272 : 4 : const char *text = NULL;
273 : 4 : const char *reply_id;
274 : 4 : JsonArray *actions;
275 : :
276 : : /* Finish the icon task */
277 [ + + + - : 4 : if (G_IS_ICON (gicon))
+ - + - ]
278 : 1 : icon = g_object_ref (gicon);
279 : :
280 : : /* Ensure we have a notification id, appName and title */
281 [ - + ]: 4 : if (!valent_packet_get_string (packet, "id", &id))
282 : : {
283 : 0 : g_debug ("%s(): expected \"id\" field holding a string",
284 : : G_STRFUNC);
285 : 0 : return;
286 : : }
287 : :
288 [ - + ]: 4 : if (!valent_packet_get_string (packet, "appName", &app_name))
289 : : {
290 : 0 : g_debug ("%s(): expected \"appName\" field holding a string",
291 : : G_STRFUNC);
292 : 0 : return;
293 : : }
294 : :
295 [ - + ]: 4 : if (!valent_packet_get_string (packet, "title", &title))
296 : : {
297 : 0 : g_debug ("%s(): expected \"title\" field holding a string",
298 : : G_STRFUNC);
299 : 0 : return;
300 : : }
301 : :
302 [ - + ]: 4 : if (!valent_packet_get_string (packet, "text", &text))
303 : : {
304 [ # # ]: 0 : if (g_strcmp0 (app_name, title) != 0)
305 : : {
306 : 0 : text = title;
307 : 0 : title = app_name;
308 : : }
309 : : }
310 : :
311 : 4 : device = valent_resource_get_source (VALENT_RESOURCE (self));
312 : :
313 : 4 : notification = g_notification_new (title);
314 : 4 : g_notification_set_body (notification, text);
315 : :
316 [ + + ]: 4 : if (icon != NULL)
317 : 1 : g_notification_set_icon (notification, icon);
318 : :
319 : : /* Notification Actions */
320 [ + + ]: 4 : if (valent_packet_get_array (packet, "actions", &actions))
321 : : {
322 : 1 : unsigned int n_actions;
323 : :
324 : 1 : n_actions = json_array_get_length (actions);
325 : :
326 [ + + ]: 4 : for (unsigned int i = 0; i < n_actions; i++)
327 : : {
328 : 3 : JsonNode *element;
329 : 3 : const char *action;
330 : 3 : GVariant *target;
331 : :
332 [ + - - + ]: 6 : if ((element = json_array_get_element (actions, i)) == NULL ||
333 : 3 : json_node_get_value_type (element) != G_TYPE_STRING)
334 : 0 : continue;
335 : :
336 : 3 : action = json_node_get_string (element);
337 : 3 : target = g_variant_new ("(ss)", id, action);
338 : 3 : valent_notification_add_device_button (notification,
339 : : device,
340 : : action,
341 : : "notification.action",
342 : : target);
343 : : }
344 : : }
345 : :
346 : : /* Repliable Notification */
347 [ + + ]: 4 : if (valent_packet_get_string (packet, "requestReplyId", &reply_id))
348 : : {
349 : 1 : g_autoptr (ValentNotification) incoming = NULL;
350 : 1 : const char *time_str = NULL;
351 : 1 : int64_t time = 0;
352 : 1 : GVariant *target;
353 : :
354 [ + - ]: 1 : if (valent_packet_get_string (packet, "time", &time_str))
355 : 1 : time = g_ascii_strtoll (time_str, NULL, 10);
356 : :
357 : 1 : incoming = g_object_new (VALENT_TYPE_NOTIFICATION,
358 : : "id", id,
359 : : "application", app_name,
360 : : "icon", icon,
361 : : "title", title,
362 : : "body", text,
363 : : "time", time,
364 : : NULL);
365 : 1 : target = g_variant_new ("(ssv)",
366 : : reply_id,
367 : : "",
368 : : valent_notification_serialize (incoming));
369 : :
370 [ + - ]: 1 : valent_notification_set_device_action (notification,
371 : : device,
372 : : "notification.reply",
373 : : target);
374 : : }
375 : :
376 [ + + ]: 4 : valent_device_plugin_show_notification (VALENT_DEVICE_PLUGIN (self),
377 : : id,
378 : : notification);
379 : : }
380 : :
381 : : static void
382 : 1 : valent_notification_plugin_download_icon_cb (ValentNotificationPlugin *self,
383 : : GAsyncResult *result,
384 : : gpointer user_data)
385 : : {
386 : 1 : g_autoptr (JsonNode) packet = user_data;
387 [ - - + - ]: 1 : g_autoptr (GIcon) icon = NULL;
388 [ - - + - ]: 1 : g_autoptr (GError) error = NULL;
389 : :
390 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
391 [ + - ]: 1 : g_assert (g_task_is_valid (result, self));
392 : :
393 : 1 : icon = valent_notification_plugin_download_icon_finish (self, result, &error);
394 : :
395 [ - + ]: 1 : if (icon == NULL)
396 : : {
397 : : /* If the operation was cancelled, the plugin is being disposed */
398 [ # # ]: 0 : if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
399 [ # # ]: 0 : return;
400 : :
401 : 0 : g_warning ("Downloading icon: %s", error->message);
402 : : }
403 : :
404 [ - + ]: 1 : valent_notification_plugin_show_notification (self, packet, icon);
405 : : }
406 : :
407 : : static void
408 : 4 : valent_notification_plugin_handle_notification (ValentNotificationPlugin *self,
409 : : JsonNode *packet)
410 : : {
411 : 4 : const char *id;
412 : :
413 [ - + ]: 4 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
414 [ + - ]: 4 : g_assert (VALENT_IS_PACKET (packet));
415 : :
416 [ - + ]: 4 : if (!valent_packet_get_string (packet, "id", &id))
417 : : {
418 : 0 : g_debug ("%s(): expected \"id\" field holding a string",
419 : : G_STRFUNC);
420 : 0 : return;
421 : : }
422 : :
423 : : /* A report that a remote notification has been dismissed */
424 [ - + ]: 4 : if (valent_packet_check_field (packet, "isCancel"))
425 : : {
426 : 0 : g_hash_table_remove (self->cache, id);
427 : 0 : valent_device_plugin_hide_notification (VALENT_DEVICE_PLUGIN (self), id);
428 : 0 : return;
429 : : }
430 : :
431 : : /* A notification that should only be shown once, already existed on the
432 : : * device, and is already in the cache. This typically means the device just
433 : : * re-connected and is re-sending known notifications. */
434 [ - + - - ]: 4 : if (valent_packet_check_field (packet, "onlyOnce") &&
435 [ # # ]: 0 : valent_packet_check_field (packet, "silent") &&
436 : 0 : g_hash_table_contains (self->cache, id))
437 : : {
438 : : VALENT_NOTE ("skipping existing notification: %s", id);
439 : : return;
440 : : }
441 : :
442 [ - + ]: 4 : g_hash_table_replace (self->cache,
443 : 4 : g_strdup (id),
444 : 4 : json_node_ref (packet));
445 : :
446 [ + + ]: 4 : if (valent_packet_has_payload (packet))
447 : : {
448 : 1 : valent_notification_plugin_download_icon (self,
449 : : packet,
450 : : self->cancellable,
451 : : (GAsyncReadyCallback)valent_notification_plugin_download_icon_cb,
452 : 1 : json_node_ref (packet));
453 : : }
454 : : else
455 : : {
456 : 3 : valent_notification_plugin_show_notification (self, packet, NULL);
457 : : }
458 : : }
459 : :
460 : : static void
461 : 0 : valent_notification_plugin_handle_notification_action (ValentNotificationPlugin *self,
462 : : JsonNode *packet)
463 : : {
464 [ # # ]: 0 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
465 [ # # ]: 0 : g_assert (VALENT_IS_PACKET (packet));
466 : :
467 : 0 : VALENT_NOTE ("TODO: kdeconnect.notification.action");
468 : 0 : }
469 : :
470 : : static void
471 : 0 : valent_notification_plugin_handle_notification_reply (ValentNotificationPlugin *self,
472 : : JsonNode *packet)
473 : : {
474 [ # # ]: 0 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
475 [ # # ]: 0 : g_assert (VALENT_IS_PACKET (packet));
476 : :
477 : 0 : VALENT_NOTE ("TODO: kdeconnect.notification.reply");
478 : 0 : }
479 : :
480 : : static void
481 : 0 : valent_notification_plugin_handle_notification_request (ValentNotificationPlugin *self,
482 : : JsonNode *packet)
483 : : {
484 [ # # ]: 0 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
485 [ # # ]: 0 : g_assert (VALENT_IS_PACKET (packet));
486 : :
487 : 0 : VALENT_NOTE ("TODO: kdeconnect.notification.request");
488 : 0 : }
489 : :
490 : : static void
491 : 5 : valent_notification_plugin_request_notifications (ValentNotificationPlugin *self)
492 : : {
493 : 10 : g_autoptr (JsonBuilder) builder = NULL;
494 [ - + ]: 5 : g_autoptr (JsonNode) packet = NULL;
495 : :
496 [ - + ]: 5 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
497 : :
498 : 5 : valent_packet_init (&builder, "kdeconnect.notification.request");
499 : 5 : json_builder_set_member_name (builder, "request");
500 : 5 : json_builder_add_boolean_value (builder, TRUE);
501 : 5 : packet = valent_packet_end (&builder);
502 : :
503 [ + - ]: 5 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
504 : 5 : }
505 : :
506 : : static void
507 : 2 : valent_notification_plugin_close_notification (ValentNotificationPlugin *self,
508 : : const char *id)
509 : : {
510 : 4 : g_autoptr (JsonBuilder) builder = NULL;
511 [ - + ]: 2 : g_autoptr (JsonNode) packet = NULL;
512 : :
513 [ - + ]: 2 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
514 [ + - ]: 2 : g_assert (id != NULL);
515 : :
516 : 2 : valent_packet_init (&builder, "kdeconnect.notification.request");
517 : 2 : json_builder_set_member_name (builder, "cancel");
518 : 2 : json_builder_add_string_value (builder, id);
519 : 2 : packet = valent_packet_end (&builder);
520 : :
521 [ + - ]: 2 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
522 : 2 : }
523 : :
524 : : static void
525 : 4 : valent_notification_upload_execute_cb (GObject *object,
526 : : GAsyncResult *result,
527 : : gpointer user_data)
528 : : {
529 : 4 : ValentTransfer *transfer = VALENT_TRANSFER (object);
530 : 8 : g_autoptr (GError) error = NULL;
531 : :
532 [ + + + - ]: 6 : if (!valent_transfer_execute_finish (transfer, result, &error) &&
533 : 2 : !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
534 : : {
535 : 2 : g_autoptr (ValentDevice) device = NULL;
536 [ + - ]: 2 : g_autoptr (JsonNode) packet = NULL;
537 : :
538 : 2 : g_object_get (transfer,
539 : : "device", &device,
540 : : "packet", &packet,
541 : : NULL);
542 [ + - ]: 2 : valent_device_send_packet (device, packet, NULL, NULL, NULL);
543 : : }
544 : 4 : }
545 : :
546 : : static void
547 : 4 : valent_notification_plugin_send_notification_with_icon (ValentNotificationPlugin *self,
548 : : JsonNode *packet,
549 : : GIcon *icon)
550 : : {
551 [ - + ]: 4 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
552 [ + - ]: 4 : g_assert (VALENT_IS_PACKET (packet));
553 [ + - + - : 4 : g_assert (icon == NULL || G_IS_ICON (icon));
+ - + - ]
554 : :
555 [ + - + - : 4 : if (G_IS_ICON (icon))
+ - + - ]
556 : : {
557 : 4 : ValentDevice *device;
558 : 8 : g_autoptr (ValentTransfer) transfer = NULL;
559 : :
560 : 4 : device = valent_resource_get_source (VALENT_RESOURCE (self));
561 : 4 : transfer = valent_notification_upload_new (device, packet, icon);
562 : 4 : valent_transfer_execute (transfer,
563 : : NULL,
564 : : valent_notification_upload_execute_cb,
565 : : NULL);
566 [ + - ]: 4 : return;
567 : : }
568 : :
569 : 0 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
570 : : }
571 : :
572 : : /**
573 : : * valent_notification_plugin_send_notification:
574 : : * @self: a `ValentNotificationPlugin`
575 : : * @id: the notification id
576 : : * @appName: (nullable): the notifying application
577 : : * @title: (nullable): the notification title
578 : : * @body: (nullable): the notification body
579 : : * @icon: (nullable): a `GIcon`
580 : : *
581 : : * Send a notification to the remote device.
582 : : */
583 : : static void
584 : 6 : valent_notification_plugin_send_notification (ValentNotificationPlugin *self,
585 : : const char *id,
586 : : const char *appName,
587 : : const char *title,
588 : : const char *body,
589 : : GIcon *icon)
590 : : {
591 : 6 : g_autoptr (JsonBuilder) builder = NULL;
592 [ - + - - ]: 6 : g_autoptr (JsonNode) packet = NULL;
593 [ + - - - ]: 6 : g_autofree char *ticker = NULL;
594 : :
595 [ - + ]: 6 : g_return_if_fail (VALENT_IS_NOTIFICATION_PLUGIN (self));
596 [ + - ]: 6 : g_return_if_fail (id != NULL);
597 [ + + + - : 6 : g_return_if_fail (icon == NULL || G_IS_ICON (icon));
+ - + - ]
598 : :
599 : : /* Build packet */
600 : 6 : valent_packet_init (&builder, "kdeconnect.notification");
601 : 6 : json_builder_set_member_name (builder, "id");
602 : 6 : json_builder_add_string_value (builder, id);
603 : :
604 : : /* Application Name */
605 : 6 : json_builder_set_member_name (builder, "appName");
606 [ + + ]: 7 : json_builder_add_string_value (builder, appName ? appName : "Valent");
607 : :
608 : : /* Title & Body (aka Ticker) */
609 : 6 : json_builder_set_member_name (builder, "title");
610 [ + + ]: 7 : json_builder_add_string_value (builder, title ? title : "");
611 : 6 : json_builder_set_member_name (builder, "body");
612 [ + + ]: 7 : json_builder_add_string_value (builder, body ? body : "");
613 : :
614 : 6 : ticker = g_strdup_printf ("%s: %s", title, body);
615 : 6 : json_builder_set_member_name (builder, "ticker");
616 : 6 : json_builder_add_string_value (builder, ticker);
617 : :
618 : 6 : packet = valent_packet_end (&builder);
619 : :
620 [ + + ]: 6 : if (icon == NULL)
621 : 2 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
622 : : else
623 : 4 : valent_notification_plugin_send_notification_with_icon (self, packet, icon);
624 : : }
625 : :
626 : : /*
627 : : * GActions
628 : : */
629 : : static void
630 : 1 : notification_action_action (GSimpleAction *action,
631 : : GVariant *parameter,
632 : : gpointer user_data)
633 : : {
634 : 1 : ValentNotificationPlugin *self = VALENT_NOTIFICATION_PLUGIN (user_data);
635 : 2 : g_autoptr (JsonBuilder) builder = NULL;
636 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
637 : 1 : char *id;
638 : 1 : char *name;
639 : :
640 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
641 : :
642 : 1 : g_variant_get (parameter, "(&s&s)", &id, &name);
643 : :
644 : 1 : valent_packet_init (&builder, "kdeconnect.notification.action");
645 : 1 : json_builder_set_member_name (builder, "key");
646 : 1 : json_builder_add_string_value (builder, id);
647 : 1 : json_builder_set_member_name (builder, "action");
648 : 1 : json_builder_add_string_value (builder, name);
649 : 1 : packet = valent_packet_end (&builder);
650 : :
651 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
652 : 1 : }
653 : :
654 : : static void
655 : 1 : notification_cancel_action (GSimpleAction *action,
656 : : GVariant *parameter,
657 : : gpointer user_data)
658 : : {
659 : 1 : ValentNotificationPlugin *self = VALENT_NOTIFICATION_PLUGIN (user_data);
660 : 1 : const char *id;
661 : 2 : g_autoptr (JsonBuilder) builder = NULL;
662 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
663 : :
664 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
665 : :
666 : 1 : id = g_variant_get_string (parameter, NULL);
667 : :
668 : 1 : valent_packet_init (&builder, "kdeconnect.notification");
669 : 1 : json_builder_set_member_name (builder, "id");
670 : 1 : json_builder_add_string_value (builder, id);
671 : 1 : json_builder_set_member_name (builder, "isCancel");
672 : 1 : json_builder_add_boolean_value (builder, TRUE);
673 : 1 : packet = valent_packet_end (&builder);
674 : :
675 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
676 : 1 : }
677 : :
678 : : static void
679 : 1 : notification_close_action (GSimpleAction *action,
680 : : GVariant *parameter,
681 : : gpointer user_data)
682 : : {
683 : 1 : ValentNotificationPlugin *self = VALENT_NOTIFICATION_PLUGIN (user_data);
684 : 1 : const char *id;
685 : :
686 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
687 : :
688 : 1 : id = g_variant_get_string (parameter, NULL);
689 : 1 : valent_notification_plugin_close_notification (self, id);
690 : 1 : }
691 : :
692 : : static void
693 : 1 : notification_reply_action (GSimpleAction *action,
694 : : GVariant *parameter,
695 : : gpointer user_data)
696 : : {
697 : 1 : ValentNotificationPlugin *self = VALENT_NOTIFICATION_PLUGIN (user_data);
698 : 1 : const char *reply_id;
699 : 1 : const char *message;
700 : 1 : g_autoptr (GVariant) notificationv = NULL;
701 : :
702 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
703 : :
704 : 1 : g_variant_get (parameter, "(&s&sv)", &reply_id, &message, ¬ificationv);
705 : :
706 : : /* If the reply ID is empty, we've received a broken request */
707 [ + - - + ]: 1 : if (reply_id == NULL || *reply_id == '\0')
708 : : {
709 : 0 : g_debug ("%s(): expected requestReplyId", G_STRFUNC);
710 [ # # ]: 0 : return;
711 : : }
712 : :
713 : : /* If the message is empty, we're being asked to show the dialog */
714 [ + - - + ]: 1 : if (message == NULL || *message == '\0')
715 : : {
716 : 0 : g_warning ("%s(): reply is empty", G_STRFUNC);
717 : : }
718 : : else
719 : : {
720 : 1 : g_autoptr (JsonBuilder) builder = NULL;
721 [ - + ]: 1 : g_autoptr (JsonNode) packet = NULL;
722 : :
723 : 1 : valent_packet_init (&builder, "kdeconnect.notification.reply");
724 : 1 : json_builder_set_member_name (builder, "requestReplyId");
725 : 1 : json_builder_add_string_value (builder, reply_id);
726 : 1 : json_builder_set_member_name (builder, "message");
727 : 1 : json_builder_add_string_value (builder, message);
728 : 1 : packet = valent_packet_end (&builder);
729 : :
730 [ + - ]: 1 : valent_device_plugin_queue_packet (VALENT_DEVICE_PLUGIN (self), packet);
731 : : }
732 : : }
733 : :
734 : : static void
735 : 1 : notification_send_action (GSimpleAction *action,
736 : : GVariant *parameter,
737 : : gpointer user_data)
738 : : {
739 : 1 : ValentNotificationPlugin *self = VALENT_NOTIFICATION_PLUGIN (user_data);
740 : 1 : GVariantDict dict;
741 : 2 : g_autofree char *id = NULL;
742 : 1 : const char *app = NULL;
743 : 1 : const char *title = NULL;
744 : 1 : const char *body = NULL;
745 : 1 : g_autoptr (GVariant) iconv = NULL;
746 [ + - ]: 1 : g_autoptr (GIcon) icon = NULL;
747 : :
748 [ - + ]: 1 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
749 : :
750 : 1 : g_variant_dict_init (&dict, parameter);
751 : :
752 : : /* Use a random ID as a fallback */
753 [ - + ]: 1 : if (!g_variant_dict_lookup (&dict, "id", "s", &id))
754 : 0 : id = g_uuid_string_random ();
755 : :
756 : 1 : g_variant_dict_lookup (&dict, "application", "&s", &app);
757 : 1 : g_variant_dict_lookup (&dict, "title", "&s", &title);
758 : 1 : g_variant_dict_lookup (&dict, "body", "&s", &body);
759 : :
760 : : /* Check for a serialized GIcon */
761 : 1 : iconv = g_variant_dict_lookup_value (&dict, "icon", G_VARIANT_TYPE ("(sv)"));
762 : :
763 [ + - ]: 1 : if (iconv != NULL)
764 : 1 : icon = g_icon_deserialize (iconv);
765 : :
766 : 1 : valent_notification_plugin_send_notification (self, id, app, title, body, icon);
767 : :
768 [ + - ]: 1 : g_variant_dict_clear (&dict);
769 : 1 : }
770 : :
771 : : static const GActionEntry actions[] = {
772 : : {"action", notification_action_action, "(ss)", NULL, NULL},
773 : : {"cancel", notification_cancel_action, "s", NULL, NULL},
774 : : {"close", notification_close_action, "s", NULL, NULL},
775 : : {"reply", notification_reply_action, "(ssv)", NULL, NULL},
776 : : {"send", notification_send_action, "a{sv}", NULL, NULL},
777 : : };
778 : :
779 : : /*
780 : : * ValentDevicePlugin
781 : : */
782 : : static void
783 : 15 : valent_notification_plugin_update_state (ValentDevicePlugin *plugin,
784 : : ValentDeviceState state)
785 : : {
786 : 15 : ValentNotificationPlugin *self = VALENT_NOTIFICATION_PLUGIN (plugin);
787 : 15 : gboolean available;
788 : :
789 [ - + ]: 15 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (self));
790 : :
791 [ + + ]: 15 : available = (state & VALENT_DEVICE_STATE_CONNECTED) != 0 &&
792 [ - + ]: 5 : (state & VALENT_DEVICE_STATE_PAIRED) != 0;
793 : :
794 : 15 : valent_extension_toggle_actions (VALENT_EXTENSION (plugin), available);
795 : 15 : valent_notification_plugin_watch_notifications (self, available);
796 : :
797 : : /* Request Notifications */
798 [ + + ]: 15 : if (available)
799 : : {
800 : 5 : valent_notification_plugin_request_notifications (self);
801 : 15 : VALENT_NOTE ("TODO: send active notifications");
802 : : }
803 : 15 : }
804 : :
805 : : static void
806 : 4 : valent_notification_plugin_handle_packet (ValentDevicePlugin *plugin,
807 : : const char *type,
808 : : JsonNode *packet)
809 : : {
810 : 4 : ValentNotificationPlugin *self = VALENT_NOTIFICATION_PLUGIN (plugin);
811 : :
812 [ - + ]: 4 : g_assert (VALENT_IS_NOTIFICATION_PLUGIN (plugin));
813 [ + - ]: 4 : g_assert (type != NULL);
814 [ + - ]: 4 : g_assert (VALENT_IS_PACKET (packet));
815 : :
816 [ + - ]: 4 : if (g_str_equal (type, "kdeconnect.notification"))
817 : 4 : valent_notification_plugin_handle_notification (self, packet);
818 : :
819 [ # # ]: 0 : else if (g_str_equal (type, "kdeconnect.notification.action"))
820 : 0 : valent_notification_plugin_handle_notification_action (self, packet);
821 : :
822 [ # # ]: 0 : else if (g_str_equal (type, "kdeconnect.notification.reply"))
823 : 0 : valent_notification_plugin_handle_notification_reply (self, packet);
824 : :
825 [ # # ]: 0 : else if (g_str_equal (type, "kdeconnect.notification.request"))
826 : 0 : valent_notification_plugin_handle_notification_request (self, packet);
827 : :
828 : : else
829 : 0 : g_assert_not_reached ();
830 : 4 : }
831 : :
832 : : /*
833 : : * ValentObject
834 : : */
835 : : static void
836 : 10 : valent_notification_plugin_destroy (ValentObject *object)
837 : : {
838 : 10 : ValentNotificationPlugin *self = VALENT_NOTIFICATION_PLUGIN (object);
839 : :
840 : 10 : valent_notification_plugin_watch_notifications (self, FALSE);
841 : :
842 : : /* Clear the cache and cancel any pending operations
843 : : */
844 [ + + ]: 10 : g_clear_pointer (&self->cache, g_hash_table_unref);
845 : 10 : g_cancellable_cancel (self->cancellable);
846 [ + + ]: 10 : g_clear_object (&self->cancellable);
847 : :
848 : 10 : VALENT_OBJECT_CLASS (valent_notification_plugin_parent_class)->destroy (object);
849 : 10 : }
850 : :
851 : : /*
852 : : * GObject
853 : : */
854 : : static void
855 : 5 : valent_notification_plugin_constructed (GObject *object)
856 : : {
857 : 5 : ValentNotificationPlugin *self = VALENT_NOTIFICATION_PLUGIN (object);
858 : 5 : ValentDevicePlugin *plugin = VALENT_DEVICE_PLUGIN (object);
859 : :
860 : 5 : G_OBJECT_CLASS (valent_notification_plugin_parent_class)->constructed (object);
861 : :
862 : 5 : self->cancellable = g_cancellable_new ();
863 : 5 : self->notifications = valent_notifications_get_default();
864 : 5 : self->session = valent_session_get_default ();
865 : :
866 : 5 : g_action_map_add_action_entries (G_ACTION_MAP (plugin),
867 : : actions,
868 : : G_N_ELEMENTS (actions),
869 : : plugin);
870 : :
871 : 5 : self->cache = g_hash_table_new_full (g_str_hash,
872 : : g_str_equal,
873 : : g_free,
874 : : (GDestroyNotify)json_node_unref);
875 : 5 : }
876 : :
877 : : static void
878 : 11 : valent_notification_plugin_class_init (ValentNotificationPluginClass *klass)
879 : : {
880 : 11 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
881 : 11 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
882 : 11 : ValentDevicePluginClass *plugin_class = VALENT_DEVICE_PLUGIN_CLASS (klass);
883 : :
884 : 11 : object_class->constructed = valent_notification_plugin_constructed;
885 : :
886 : 11 : vobject_class->destroy = valent_notification_plugin_destroy;
887 : :
888 : 11 : plugin_class->handle_packet = valent_notification_plugin_handle_packet;
889 : 11 : plugin_class->update_state = valent_notification_plugin_update_state;
890 : : }
891 : :
892 : : static void
893 : 5 : valent_notification_plugin_init (ValentNotificationPlugin *self)
894 : : {
895 : 5 : }
896 : :
|