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