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-device"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <inttypes.h>
9 : : #include <math.h>
10 : :
11 : : #include <glib/gi18n-lib.h>
12 : : #include <gio/gio.h>
13 : : #include <libvalent-core.h>
14 : :
15 : : #include "../core/valent-component-private.h"
16 : : #include "valent-certificate.h"
17 : : #include "valent-channel.h"
18 : : #include "valent-device-common.h"
19 : : #include "valent-device-enums.h"
20 : : #include "valent-device-plugin.h"
21 : : #include "valent-packet.h"
22 : :
23 : : #include "valent-device.h"
24 : : #include "valent-device-private.h"
25 : :
26 : : #define DEVICE_TYPE_DESKTOP "desktop"
27 : : #define DEVICE_TYPE_LAPTOP "laptop"
28 : : #define DEVICE_TYPE_PHONE "phone"
29 : : #define DEVICE_TYPE_TABLET "tablet"
30 : : #define DEVICE_TYPE_TV "tv"
31 : :
32 : : #define PAIR_REQUEST_ID "pair-request"
33 : : #define PAIR_REQUEST_TIMEOUT (30)
34 : : #define PAIR_REQUEST_THRESHOLD (1800)
35 : :
36 : :
37 : : /**
38 : : * ValentDevice:
39 : : *
40 : : * A class representing a remote device, such as a smartphone or desktop.
41 : : *
42 : : * Device functionality is limited to pairing and sending packets, while other
43 : : * functionality is delegated to [class@Valent.DevicePlugin] extensions.
44 : : *
45 : : * `ValentDevice` implements the [iface@Gio.ActionGroup] interface, acting as an
46 : : * aggregate action group for plugins. Plugin actions are automatically included
47 : : * in the device action group with the plugin module name as a prefix
48 : : * (eg. `share.files`).
49 : : *
50 : : * Since: 1.0
51 : : */
52 : :
53 : : struct _ValentDevice
54 : : {
55 : : ValentResource parent_instance;
56 : :
57 : : ValentContext *context;
58 : : GSettings *settings;
59 : :
60 : : /* Properties */
61 : : char *icon_name;
62 : : char *id;
63 : : char *name;
64 : : char *type;
65 : : char **incoming_capabilities;
66 : : char **outgoing_capabilities;
67 : : int64_t protocol_version;
68 : :
69 : : /* State */
70 : : ValentChannel *channel;
71 : : gboolean paired;
72 : : unsigned int incoming_pair;
73 : : unsigned int outgoing_pair;
74 : : int64_t pair_timestamp;
75 : :
76 : : /* Plugins */
77 : : PeasEngine *engine;
78 : : GHashTable *plugins;
79 : : GHashTable *handlers;
80 : : GHashTable *actions;
81 : : GMenu *menu;
82 : : };
83 : :
84 : : static void valent_device_reload_plugins (ValentDevice *device);
85 : : static void valent_device_update_plugins (ValentDevice *device);
86 : : static gboolean valent_device_supports_plugin (ValentDevice *device,
87 : : PeasPluginInfo *info);
88 : :
89 : : static void g_action_group_iface_init (GActionGroupInterface *iface);
90 : :
91 [ + + + - ]: 6926 : G_DEFINE_FINAL_TYPE_WITH_CODE (ValentDevice, valent_device, VALENT_TYPE_RESOURCE,
92 : : G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, g_action_group_iface_init))
93 : :
94 : :
95 : : enum {
96 : : PROP_0,
97 : : PROP_CONTEXT,
98 : : PROP_ICON_NAME,
99 : : PROP_ID,
100 : : PROP_NAME,
101 : : PROP_PLUGINS,
102 : : PROP_STATE,
103 : : N_PROPERTIES
104 : : };
105 : :
106 : : static GParamSpec *properties[N_PROPERTIES] = { NULL, };
107 : :
108 : :
109 : : /*
110 : : * GActionGroup
111 : : */
112 : : static void
113 : 36 : valent_device_activate_action (GActionGroup *action_group,
114 : : const char *action_name,
115 : : GVariant *parameter)
116 : : {
117 : 36 : ValentDevice *self = VALENT_DEVICE (action_group);
118 : 36 : GAction *action;
119 : :
120 [ + - ]: 36 : if ((action = g_hash_table_lookup (self->actions, action_name)) != NULL)
121 : 36 : g_action_activate (action, parameter);
122 : 36 : }
123 : :
124 : : static void
125 : 3 : valent_device_change_action_state (GActionGroup *action_group,
126 : : const char *action_name,
127 : : GVariant *value)
128 : : {
129 : 3 : ValentDevice *self = VALENT_DEVICE (action_group);
130 : 3 : GAction *action;
131 : :
132 [ + - ]: 3 : if ((action = g_hash_table_lookup (self->actions, action_name)) != NULL)
133 : 3 : g_action_change_state (action, value);
134 : 3 : }
135 : :
136 : : static char **
137 : 9 : valent_device_list_actions (GActionGroup *action_group)
138 : : {
139 : 9 : ValentDevice *self = VALENT_DEVICE (action_group);
140 : 18 : g_auto (GStrv) actions = NULL;
141 : 9 : GHashTableIter iter;
142 : 9 : gpointer key;
143 : 9 : unsigned int i = 0;
144 : :
145 [ - + ]: 9 : actions = g_new0 (char *, g_hash_table_size (self->actions) + 1);
146 : :
147 : 9 : g_hash_table_iter_init (&iter, self->actions);
148 : :
149 [ + + ]: 48 : while (g_hash_table_iter_next (&iter, &key, NULL))
150 [ - + ]: 78 : actions[i++] = g_strdup (key);
151 : :
152 : 9 : return g_steal_pointer (&actions);
153 : : }
154 : :
155 : : static gboolean
156 : 203 : valent_device_query_action (GActionGroup *action_group,
157 : : const char *action_name,
158 : : gboolean *enabled,
159 : : const GVariantType **parameter_type,
160 : : const GVariantType **state_type,
161 : : GVariant **state_hint,
162 : : GVariant **state)
163 : : {
164 : 203 : ValentDevice *self = VALENT_DEVICE (action_group);
165 : 203 : GAction *action;
166 : :
167 [ + + ]: 203 : if ((action = g_hash_table_lookup (self->actions, action_name)) == NULL)
168 : : return FALSE;
169 : :
170 [ + + ]: 172 : if (enabled)
171 : 88 : *enabled = g_action_get_enabled (action);
172 : :
173 [ + + ]: 172 : if (parameter_type)
174 : 36 : *parameter_type = g_action_get_parameter_type (action);
175 : :
176 [ + + ]: 172 : if (state_type)
177 : 1 : *state_type = g_action_get_state_type (action);
178 : :
179 [ + + ]: 172 : if (state_hint)
180 : 1 : *state_hint = g_action_get_state_hint (action);
181 : :
182 [ + + ]: 172 : if (state)
183 : 55 : *state = g_action_get_state (action);
184 : :
185 : : return TRUE;
186 : : }
187 : :
188 : : static void
189 : 32 : g_action_group_iface_init (GActionGroupInterface *iface)
190 : : {
191 : 32 : iface->activate_action = valent_device_activate_action;
192 : 32 : iface->change_action_state = valent_device_change_action_state;
193 : 32 : iface->list_actions = valent_device_list_actions;
194 : 32 : iface->query_action = valent_device_query_action;
195 : 32 : }
196 : :
197 : :
198 : : /*
199 : : * Private plugin methods
200 : : */
201 : : static void
202 : 354 : on_plugin_action_added (GActionGroup *action_group,
203 : : const char *action_name,
204 : : ValentPlugin *plugin)
205 : : {
206 : 354 : ValentDevice *self = VALENT_DEVICE (plugin->parent);
207 : 708 : g_autofree char *full_name = NULL;
208 : 354 : GAction *action;
209 : :
210 : 354 : full_name = g_strdup_printf ("%s.%s",
211 : 354 : peas_plugin_info_get_module_name (plugin->info),
212 : : action_name);
213 : 354 : action = g_action_map_lookup_action (G_ACTION_MAP (action_group),
214 : : action_name);
215 : :
216 : 354 : g_hash_table_replace (self->actions,
217 [ - + ]: 708 : g_strdup (full_name),
218 : : g_object_ref (action));
219 : 354 : g_action_group_action_added (G_ACTION_GROUP (plugin->parent), full_name);
220 : 354 : }
221 : :
222 : : static void
223 : 602 : on_plugin_action_enabled_changed (GActionGroup *action_group,
224 : : const char *action_name,
225 : : gboolean enabled,
226 : : ValentPlugin *plugin)
227 : : {
228 : 1204 : g_autofree char *full_name = NULL;
229 : :
230 : 602 : full_name = g_strdup_printf ("%s.%s",
231 : 602 : peas_plugin_info_get_module_name (plugin->info),
232 : : action_name);
233 : 602 : g_action_group_action_enabled_changed (G_ACTION_GROUP (plugin->parent),
234 : : full_name,
235 : : enabled);
236 : 602 : }
237 : :
238 : : static void
239 : 340 : on_plugin_action_removed (GActionGroup *action_group,
240 : : const char *action_name,
241 : : ValentPlugin *plugin)
242 : : {
243 : 340 : ValentDevice *self = VALENT_DEVICE (plugin->parent);
244 : 680 : g_autofree char *full_name = NULL;
245 : :
246 : 340 : full_name = g_strdup_printf ("%s.%s",
247 : 340 : peas_plugin_info_get_module_name (plugin->info),
248 : : action_name);
249 : :
250 : 340 : g_action_group_action_removed (G_ACTION_GROUP (plugin->parent), full_name);
251 : 340 : g_hash_table_remove (self->actions, full_name);
252 : 340 : }
253 : :
254 : : static void
255 : 19 : on_plugin_action_state_changed (GActionGroup *action_group,
256 : : const char *action_name,
257 : : GVariant *value,
258 : : ValentPlugin *plugin)
259 : : {
260 : 38 : g_autofree char *full_name = NULL;
261 : :
262 : 19 : full_name = g_strdup_printf ("%s.%s",
263 : 19 : peas_plugin_info_get_module_name (plugin->info),
264 : : action_name);
265 : 19 : g_action_group_action_state_changed (G_ACTION_GROUP (plugin->parent),
266 : : full_name,
267 : : value);
268 : 19 : }
269 : :
270 : : static void
271 : 214 : valent_device_enable_plugin (ValentDevice *device,
272 : : ValentPlugin *plugin)
273 : : {
274 : 428 : g_auto (GStrv) actions = NULL;
275 [ + - ]: 214 : g_autofree char *urn = NULL;
276 : 214 : const char *incoming = NULL;
277 : 214 : const char *title = NULL;
278 : 214 : const char *description = NULL;
279 : :
280 [ + - ]: 214 : g_assert (VALENT_IS_DEVICE (device));
281 [ - + ]: 214 : g_assert (plugin != NULL);
282 : :
283 : 214 : title = peas_plugin_info_get_name (plugin->info);
284 : 214 : description = peas_plugin_info_get_description (plugin->info);
285 : 214 : plugin->extension = peas_engine_create_extension (device->engine,
286 : : plugin->info,
287 : : VALENT_TYPE_DEVICE_PLUGIN,
288 : : "context", plugin->context,
289 : : "source", plugin->parent,
290 : : "title", title,
291 : : "description", description,
292 : : NULL);
293 [ - + ]: 214 : g_return_if_fail (G_IS_OBJECT (plugin->extension));
294 : :
295 : : /* Register packet handlers */
296 : 214 : incoming = peas_plugin_info_get_external_data (plugin->info,
297 : : "DevicePluginIncoming");
298 : :
299 [ + + ]: 214 : if (incoming != NULL)
300 : : {
301 : 319 : g_auto (GStrv) capabilities = NULL;
302 : :
303 : 105 : capabilities = g_strsplit (incoming, ";", -1);
304 : :
305 [ + + ]: 302 : for (unsigned int i = 0; capabilities[i] != NULL; i++)
306 : : {
307 : 197 : GPtrArray *handlers = NULL;
308 : 197 : const char *type = capabilities[i];
309 : :
310 [ + + ]: 197 : if ((handlers = g_hash_table_lookup (device->handlers, type)) == NULL)
311 : : {
312 : 189 : handlers = g_ptr_array_new ();
313 [ - + ]: 378 : g_hash_table_insert (device->handlers, g_strdup (type), handlers);
314 : : }
315 : :
316 : 197 : g_ptr_array_add (handlers, plugin->extension);
317 : : }
318 : : }
319 : :
320 : : /* Register plugin actions */
321 : 214 : actions = g_action_group_list_actions (G_ACTION_GROUP (plugin->extension));
322 : :
323 [ + + ]: 568 : for (unsigned int i = 0; actions[i] != NULL; i++)
324 : : {
325 : 354 : on_plugin_action_added (G_ACTION_GROUP (plugin->extension),
326 : : actions[i],
327 : : plugin);
328 : : }
329 : :
330 : 214 : g_signal_connect (plugin->extension,
331 : : "action-added",
332 : : G_CALLBACK (on_plugin_action_added),
333 : : plugin);
334 : 214 : g_signal_connect (plugin->extension,
335 : : "action-enabled-changed",
336 : : G_CALLBACK (on_plugin_action_enabled_changed),
337 : : plugin);
338 : 214 : g_signal_connect (plugin->extension,
339 : : "action-removed",
340 : : G_CALLBACK (on_plugin_action_removed),
341 : : plugin);
342 : 214 : g_signal_connect (plugin->extension,
343 : : "action-state-changed",
344 : : G_CALLBACK (on_plugin_action_state_changed),
345 : : plugin);
346 : :
347 : : /* Bootstrap the newly instantiated plugin */
348 : 214 : valent_device_plugin_update_state (VALENT_DEVICE_PLUGIN (plugin->extension),
349 : : valent_device_get_state (device));
350 : : }
351 : :
352 : : static void
353 : 2 : valent_device_disable_plugin (ValentDevice *device,
354 : : ValentPlugin *plugin)
355 : : {
356 : 4 : g_auto (GStrv) actions = NULL;
357 : 2 : const char *incoming = NULL;
358 : :
359 [ + - ]: 2 : g_assert (VALENT_IS_DEVICE (device));
360 [ - + ]: 2 : g_assert (plugin != NULL);
361 [ - + ]: 2 : g_return_if_fail (G_IS_OBJECT (plugin->extension));
362 : :
363 : : /* Unregister actions */
364 : 2 : g_signal_handlers_disconnect_by_data (plugin->extension, plugin);
365 : 2 : actions = g_action_group_list_actions (G_ACTION_GROUP (plugin->extension));
366 : :
367 [ + + ]: 5 : for (unsigned int i = 0; actions[i]; i++)
368 : : {
369 : 3 : on_plugin_action_removed (G_ACTION_GROUP (plugin->extension),
370 : : actions[i],
371 : : plugin);
372 : : }
373 : :
374 : : /* Unregister packet handlers */
375 : 2 : incoming = peas_plugin_info_get_external_data (plugin->info,
376 : : "DevicePluginIncoming");
377 : :
378 [ + + ]: 2 : if (incoming != NULL)
379 : : {
380 : 3 : g_auto (GStrv) capabilities = NULL;
381 : :
382 : 1 : capabilities = g_strsplit (incoming, ";", -1);
383 : :
384 [ + + ]: 3 : for (unsigned int i = 0; capabilities[i] != NULL; i++)
385 : : {
386 : 2 : const char *type = capabilities[i];
387 : 2 : GPtrArray *handlers = NULL;
388 : :
389 [ - + ]: 2 : if ((handlers = g_hash_table_lookup (device->handlers, type)) == NULL)
390 : 0 : continue;
391 : :
392 [ + - - + ]: 2 : if (g_ptr_array_remove (handlers, plugin->extension) && handlers->len == 0)
393 : 0 : g_hash_table_remove (device->handlers, type);
394 : : }
395 : : }
396 : :
397 : : /* `::action-removed` needs to be emitted before the plugin is freed */
398 : 2 : valent_object_destroy (VALENT_OBJECT (plugin->extension));
399 [ + - + - ]: 2 : g_clear_object (&plugin->extension);
400 : : }
401 : :
402 : : static void
403 : 4 : on_plugin_enabled_changed (ValentPlugin *plugin)
404 : : {
405 [ + - ]: 4 : g_assert (plugin != NULL);
406 [ - + ]: 4 : g_assert (VALENT_IS_DEVICE (plugin->parent));
407 : :
408 [ + + ]: 4 : if (valent_plugin_get_enabled (plugin))
409 : 2 : valent_device_enable_plugin (plugin->parent, plugin);
410 : : else
411 : 2 : valent_device_disable_plugin (plugin->parent, plugin);
412 : 4 : }
413 : :
414 : :
415 : : /*
416 : : * Private pairing methods
417 : : */
418 : : static gboolean
419 : 213 : valent_device_reset_pair (gpointer object)
420 : : {
421 : 213 : ValentDevice *device = VALENT_DEVICE (object);
422 : 213 : GApplication *application = g_application_get_default ();
423 : :
424 [ + - ]: 213 : g_assert (VALENT_IS_DEVICE (device));
425 : :
426 [ - + ]: 213 : if (application != NULL)
427 : : {
428 : 0 : g_autofree char *notification_id = NULL;
429 : :
430 : 0 : notification_id = g_strdup_printf ("%s::%s", device->id, PAIR_REQUEST_ID);
431 : 0 : g_application_withdraw_notification (application, notification_id);
432 : : }
433 : :
434 [ + + ]: 213 : g_clear_handle_id (&device->incoming_pair, g_source_remove);
435 [ + + ]: 213 : g_clear_handle_id (&device->outgoing_pair, g_source_remove);
436 : 213 : device->pair_timestamp = 0;
437 : :
438 : 213 : g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_STATE]);
439 : :
440 : 213 : return G_SOURCE_REMOVE;
441 : : }
442 : :
443 : : static void
444 : 6 : send_pair_cb (ValentChannel *channel,
445 : : GAsyncResult *result,
446 : : ValentDevice *device)
447 : : {
448 : 12 : g_autoptr (GError) error = NULL;
449 : :
450 [ + - ]: 6 : g_assert (VALENT_IS_CHANNEL (channel));
451 [ - + ]: 6 : g_assert (VALENT_IS_DEVICE (device));
452 : :
453 [ - + ]: 6 : if (!valent_channel_write_packet_finish (channel, result, &error))
454 : : {
455 : 0 : VALENT_NOTE ("%s: %s", device->name, error->message);
456 : :
457 : 0 : valent_device_reset_pair (device);
458 : :
459 : 0 : valent_object_lock (VALENT_OBJECT (device));
460 [ # # ]: 0 : if (device->channel == channel)
461 : 0 : valent_device_set_channel (device, NULL);
462 : 0 : valent_object_unlock (VALENT_OBJECT (device));
463 : : }
464 : :
465 [ - + ]: 6 : g_object_unref (device);
466 : 6 : }
467 : :
468 : : static void
469 : 8 : valent_device_send_pair (ValentDevice *device,
470 : : gboolean pair)
471 : : {
472 : 8 : g_autoptr (JsonBuilder) builder = NULL;
473 [ - + - + ]: 8 : g_autoptr (JsonNode) packet = NULL;
474 [ - + + - ]: 8 : g_autoptr (GCancellable) cancellable = NULL;
475 : :
476 [ + - ]: 8 : g_assert (VALENT_IS_DEVICE (device));
477 : :
478 : 8 : valent_object_lock (VALENT_OBJECT (device));
479 : :
480 [ + + ]: 8 : if (device->channel == NULL)
481 : : {
482 : 2 : valent_object_unlock (VALENT_OBJECT (device));
483 [ - + ]: 2 : return;
484 : : }
485 : :
486 : 6 : valent_packet_init (&builder, "kdeconnect.pair");
487 : 6 : json_builder_set_member_name (builder, "pair");
488 : 6 : json_builder_add_boolean_value (builder, pair);
489 [ + - ]: 6 : if (device->protocol_version >= VALENT_NETWORK_PROTOCOL_V8)
490 : : {
491 : 6 : device->pair_timestamp = (int64_t)floor (valent_timestamp_ms () / 1000);
492 : 6 : json_builder_set_member_name (builder, "timestamp");
493 : 6 : json_builder_add_int_value (builder, device->pair_timestamp);
494 : : }
495 : 6 : packet = valent_packet_end (&builder);
496 : :
497 : 6 : cancellable = valent_object_ref_cancellable (VALENT_OBJECT (device));
498 : 6 : valent_channel_write_packet (device->channel,
499 : : packet,
500 : : cancellable,
501 : : (GAsyncReadyCallback)send_pair_cb,
502 : : g_object_ref (device));
503 : :
504 [ + - ]: 6 : valent_object_unlock (VALENT_OBJECT (device));
505 : : }
506 : :
507 : : static void
508 : 2 : valent_device_notify_pair_requested (ValentDevice *device)
509 : : {
510 : 2 : GApplication *application = NULL;
511 : 2 : g_autofree char *notification_id = NULL;
512 : 2 : g_autoptr (GNotification) notification = NULL;
513 [ - - ]: 2 : g_autoptr (GIcon) icon = NULL;
514 [ - - - + ]: 2 : g_autofree char *title = NULL;
515 : 2 : g_autofree char *verification_key = NULL;
516 : :
517 [ + - ]: 2 : g_assert (VALENT_IS_DEVICE (device));
518 : :
519 : 2 : application = g_application_get_default ();
520 [ - + ]: 2 : if (application == NULL)
521 : : return;
522 : :
523 : 0 : title = g_strdup_printf (_("Pairing request from “%s”"), device->name);
524 : 0 : verification_key = valent_device_get_verification_key (device);
525 : 0 : icon = g_themed_icon_new (APPLICATION_ID);
526 : :
527 [ # # ]: 0 : g_return_if_fail (verification_key != NULL);
528 : :
529 : 0 : notification = g_notification_new (title);
530 : 0 : g_notification_set_body (notification, verification_key);
531 : 0 : g_notification_set_icon (notification, icon);
532 : 0 : g_notification_set_priority (notification, G_NOTIFICATION_PRIORITY_URGENT);
533 : 0 : g_notification_add_button_with_target (notification, _("Reject"), "app.device",
534 : : "(ssav)",
535 : : device->id,
536 : : "unpair",
537 : : NULL);
538 : 0 : g_notification_add_button_with_target (notification, _("Accept"), "app.device",
539 : : "(ssav)",
540 : : device->id,
541 : : "pair",
542 : : NULL);
543 : :
544 : : /* Show the pairing notification and set a timeout for 30s
545 : : */
546 : 0 : notification_id = g_strdup_printf ("%s::%s", device->id, PAIR_REQUEST_ID);
547 : 0 : g_application_send_notification (application,
548 : : notification_id,
549 : : notification);
550 : : }
551 : :
552 : : static void
553 : 0 : valent_device_notify_pair_failed (ValentDevice *device)
554 : : {
555 : 0 : GApplication *application = NULL;
556 : 0 : g_autofree char *notification_id = NULL;
557 : 0 : g_autoptr (GNotification) notification = NULL;
558 [ # # ]: 0 : g_autoptr (GIcon) icon = NULL;
559 [ # # ]: 0 : g_autofree char *title = NULL;
560 : 0 : const char *body = NULL;
561 : :
562 [ # # ]: 0 : g_assert (VALENT_IS_DEVICE (device));
563 : :
564 : 0 : application = g_application_get_default ();
565 [ # # ]: 0 : if (application == NULL)
566 : 0 : return;
567 : :
568 : 0 : title = g_strdup_printf (_("Failed to pair with “%s”"), device->name);
569 [ # # ]: 0 : body = g_strdup (_("Device clocks are out of sync"));
570 : 0 : icon = g_themed_icon_new ("dialog-warning-symbolic");
571 : :
572 : 0 : notification = g_notification_new (title);
573 : 0 : g_notification_set_body (notification, body);
574 : 0 : g_notification_set_icon (notification, icon);
575 : 0 : g_notification_set_priority (notification, G_NOTIFICATION_PRIORITY_URGENT);
576 : :
577 : 0 : notification_id = g_strdup_printf ("%s::%s", device->id, PAIR_REQUEST_ID);
578 : 0 : g_application_send_notification (application,
579 : : notification_id,
580 : : notification);
581 : : }
582 : :
583 : : static void
584 : 4 : valent_device_handle_pair (ValentDevice *device,
585 : : JsonNode *packet)
586 : : {
587 : 4 : gboolean pair;
588 : :
589 : 4 : VALENT_ENTRY;
590 : :
591 [ + - ]: 4 : g_assert (VALENT_IS_DEVICE (device));
592 [ - + ]: 4 : g_assert (VALENT_IS_PACKET (packet));
593 : :
594 [ - + ]: 4 : if (!valent_packet_get_boolean (packet, "pair", &pair))
595 : : {
596 : 0 : g_warning ("%s(): expected \"pair\" field holding a boolean from \"%s\"",
597 : : G_STRFUNC,
598 : : device->name);
599 : 0 : VALENT_EXIT;
600 : : }
601 : :
602 [ + + ]: 4 : if (pair)
603 : : {
604 [ + + ]: 3 : if (device->outgoing_pair > 0)
605 : : {
606 : 1 : VALENT_NOTE ("Pairing accepted by \"%s\"", device->name);
607 : 1 : valent_device_set_paired (device, TRUE);
608 : : }
609 : : else
610 : : {
611 : 2 : int64_t timestamp = 0;
612 : :
613 : 2 : valent_device_reset_pair (device);
614 : :
615 : 2 : VALENT_NOTE ("Pairing requested by \"%s\"", device->name);
616 : :
617 [ + - - + ]: 4 : if (device->protocol_version >= VALENT_NETWORK_PROTOCOL_V8 &&
618 : 2 : !valent_packet_get_int (packet, "timestamp", ×tamp))
619 : : {
620 : 0 : g_warning ("%s(): expected \"timestamp\" field holding an integer",
621 : : G_STRFUNC);
622 : 0 : VALENT_EXIT;
623 : : }
624 : :
625 : 2 : device->pair_timestamp = timestamp;
626 : 2 : device->incoming_pair = g_timeout_add_seconds (PAIR_REQUEST_TIMEOUT,
627 : : valent_device_reset_pair,
628 : : device);
629 : 2 : valent_device_notify_pair_requested (device);
630 : : }
631 : : }
632 : : else
633 : : {
634 : 1 : VALENT_NOTE ("Pairing rejected by \"%s\"", device->name);
635 : 1 : valent_device_set_paired (device, FALSE);
636 : : }
637 : :
638 : 4 : g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_STATE]);
639 : :
640 : 4 : VALENT_EXIT;
641 : : }
642 : :
643 : : /*
644 : : * Private identity methods
645 : : */
646 : : static void
647 : 182 : valent_device_handle_identity (ValentDevice *device,
648 : : JsonNode *packet)
649 : : {
650 : 182 : const char *device_id;
651 : 182 : const char *device_name;
652 : 182 : const char *device_type;
653 : :
654 : 182 : VALENT_ENTRY;
655 : :
656 [ + - ]: 182 : g_assert (VALENT_IS_DEVICE (device));
657 [ - + ]: 182 : g_assert (VALENT_IS_PACKET (packet));
658 : :
659 : 182 : valent_object_lock (VALENT_OBJECT (device));
660 : :
661 : : /* Device ID, which MUST exist and MUST match the construct-time value */
662 [ + - ]: 182 : if (!valent_packet_get_string (packet, "deviceId", &device_id) ||
663 [ - + ]: 182 : !g_str_equal (device->id, device_id))
664 : : {
665 : 0 : g_critical ("%s(): expected \"deviceId\" field holding \"%s\"",
666 : : G_STRFUNC,
667 : : device->id);
668 : 0 : valent_object_unlock (VALENT_OBJECT (device));
669 : 0 : VALENT_EXIT;
670 : : }
671 : :
672 : : /* Device Name */
673 [ - + ]: 182 : if (!valent_packet_get_string (packet, "deviceName", &device_name))
674 : 0 : device_name = "Unnamed";
675 : :
676 [ + + ]: 182 : if (g_set_str (&device->name, device_name))
677 : 103 : g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_NAME]);
678 : :
679 : : /* Device Type */
680 [ - + ]: 182 : if (!valent_packet_get_string (packet, "deviceType", &device_type))
681 : 0 : device_type = DEVICE_TYPE_DESKTOP;
682 : :
683 [ + + ]: 182 : if (g_set_str (&device->type, device_type))
684 : : {
685 : 103 : const char *device_icon = "computer-symbolic";
686 : :
687 [ - + ]: 103 : if (g_str_equal (device_type, DEVICE_TYPE_DESKTOP))
688 : : device_icon = "computer-symbolic";
689 [ + - ]: 103 : else if (g_str_equal (device_type, DEVICE_TYPE_LAPTOP))
690 : : device_icon = "laptop-symbolic";
691 [ - + ]: 103 : else if (g_str_equal (device_type, DEVICE_TYPE_PHONE))
692 : : device_icon = "phone-symbolic";
693 [ # # ]: 0 : else if (g_str_equal (device_type, DEVICE_TYPE_TABLET))
694 : : device_icon = "tablet-symbolic";
695 [ # # ]: 0 : else if (g_str_equal (device_type, DEVICE_TYPE_TV))
696 : 103 : device_icon = "tv-symbolic";
697 : :
698 [ + - ]: 103 : if (g_set_str (&device->icon_name, device_icon))
699 : 103 : g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_ICON_NAME]);
700 : : }
701 : :
702 : : /* Generally, these should be static, but could change if the connection type
703 : : * changes between eg. TCP and Bluetooth */
704 [ + + ]: 182 : g_clear_pointer (&device->incoming_capabilities, g_strfreev);
705 : 182 : device->incoming_capabilities = valent_packet_dup_strv (packet,
706 : : "incomingCapabilities");
707 : :
708 [ + + ]: 182 : g_clear_pointer (&device->outgoing_capabilities, g_strfreev);
709 : 182 : device->outgoing_capabilities = valent_packet_dup_strv (packet,
710 : : "outgoingCapabilities");
711 : :
712 : : /* Protocol Version */
713 [ - + ]: 182 : if (!valent_packet_get_int (packet, "protocolVersion", &device->protocol_version))
714 : 0 : device->protocol_version = VALENT_NETWORK_PROTOCOL_V8;
715 : :
716 : 182 : valent_object_unlock (VALENT_OBJECT (device));
717 : :
718 : : /* Recheck plugins and load or unload if capabilities have changed */
719 : 182 : valent_device_reload_plugins (device);
720 : :
721 : 182 : VALENT_EXIT;
722 : : }
723 : :
724 : : static void
725 : 124 : valent_device_handle_packet (ValentDevice *device,
726 : : JsonNode *packet)
727 : : {
728 : 124 : GPtrArray *handlers = NULL;
729 : 124 : const char *type;
730 : :
731 [ + - ]: 124 : g_assert (VALENT_IS_DEVICE (device));
732 [ - + ]: 124 : g_assert (VALENT_IS_PACKET (packet));
733 : :
734 : 124 : VALENT_JSON (packet, device->name);
735 : :
736 : 124 : type = valent_packet_get_type (packet);
737 : :
738 [ + + ]: 124 : if G_UNLIKELY (strcmp (type, "kdeconnect.pair") == 0)
739 : : {
740 : 4 : valent_device_handle_pair (device, packet);
741 : : }
742 [ + + ]: 120 : else if G_UNLIKELY (!device->paired)
743 : : {
744 : 1 : valent_device_send_pair (device, FALSE);
745 : : }
746 [ + - ]: 119 : else if ((handlers = g_hash_table_lookup (device->handlers, type)) != NULL)
747 : : {
748 [ + + ]: 238 : for (unsigned int i = 0, len = handlers->len; i < len; i++)
749 : : {
750 : 119 : ValentDevicePlugin *handler = g_ptr_array_index (handlers, i);
751 : :
752 : 119 : valent_device_plugin_handle_packet (handler, type, packet);
753 : : }
754 : : }
755 : : else
756 : : {
757 : 124 : VALENT_NOTE ("%s: Unsupported packet \"%s\"", device->name, type);
758 : : }
759 : 124 : }
760 : :
761 : : /*
762 : : * ValentEngine callbacks
763 : : */
764 : : static void
765 : 918 : on_load_plugin (PeasEngine *engine,
766 : : PeasPluginInfo *info,
767 : : ValentDevice *self)
768 : : {
769 : 918 : ValentPlugin *plugin;
770 : :
771 [ + - ]: 918 : g_assert (PEAS_IS_ENGINE (engine));
772 [ - + ]: 918 : g_assert (info != NULL);
773 [ - + ]: 918 : g_assert (VALENT_IS_DEVICE (self));
774 : :
775 [ + + ]: 918 : if (!valent_device_supports_plugin (self, info))
776 : : return;
777 : :
778 [ + + ]: 473 : if (g_hash_table_contains (self->plugins, info))
779 : : return;
780 : :
781 : 212 : VALENT_NOTE ("%s: %s",
782 : : self->name,
783 : : peas_plugin_info_get_module_name (info));
784 : :
785 : : /* Register the plugin & data (hash tables are ref owners) */
786 : 212 : plugin = valent_plugin_new (self, self->context, info,
787 : : G_CALLBACK (on_plugin_enabled_changed));
788 : 212 : g_hash_table_insert (self->plugins, info, plugin);
789 : :
790 [ + - ]: 212 : if (valent_plugin_get_enabled (plugin))
791 : 212 : valent_device_enable_plugin (self, plugin);
792 : :
793 : 212 : g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PLUGINS]);
794 : : }
795 : :
796 : : static void
797 : 513 : on_unload_plugin (PeasEngine *engine,
798 : : PeasPluginInfo *info,
799 : : ValentDevice *device)
800 : : {
801 [ + - ]: 513 : g_assert (PEAS_IS_ENGINE (engine));
802 [ - + ]: 513 : g_assert (info != NULL);
803 [ - + ]: 513 : g_assert (VALENT_IS_DEVICE (device));
804 : :
805 [ + + ]: 513 : if (!g_hash_table_contains (device->plugins, info))
806 : : return;
807 : :
808 : 6 : VALENT_NOTE ("%s: %s",
809 : : device->name,
810 : : peas_plugin_info_get_module_name (info));
811 : :
812 : 6 : g_hash_table_remove (device->plugins, info);
813 : 6 : g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_PLUGINS]);
814 : : }
815 : :
816 : :
817 : : /*
818 : : * GActions
819 : : */
820 : : static void
821 : 4 : pair_action (GSimpleAction *action,
822 : : GVariant *parameter,
823 : : gpointer user_data)
824 : : {
825 : 4 : ValentDevice *device = VALENT_DEVICE (user_data);
826 : :
827 [ + + ]: 4 : if (device->incoming_pair > 0)
828 : : {
829 : 1 : VALENT_NOTE ("Accepting pair request from \"%s\"", device->name);
830 : :
831 [ + - ]: 1 : if (device->protocol_version >= VALENT_NETWORK_PROTOCOL_V8)
832 : : {
833 : 1 : int64_t timestamp = 0;
834 : 1 : int64_t timestamp_diff = 0;
835 : :
836 : 1 : timestamp = (int64_t)floor (valent_timestamp_ms () / 1000);
837 : 1 : timestamp_diff = ABS (device->pair_timestamp - timestamp);
838 [ - + ]: 1 : if (timestamp_diff > PAIR_REQUEST_THRESHOLD)
839 : : {
840 : 0 : valent_device_set_paired (device, FALSE);
841 : 0 : valent_device_notify_pair_failed (device);
842 : 0 : return;
843 : : }
844 : : }
845 : :
846 : 1 : valent_device_send_pair (device, TRUE);
847 : 1 : valent_device_set_paired (device, TRUE);
848 : 1 : g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_STATE]);
849 : : }
850 [ + - ]: 3 : else if (!device->paired)
851 : : {
852 : 3 : VALENT_NOTE ("Sending pair request to \"%s\"", device->name);
853 : :
854 : 3 : valent_device_reset_pair (device);
855 : 3 : valent_device_send_pair (device, TRUE);
856 : 3 : device->outgoing_pair = g_timeout_add_seconds (PAIR_REQUEST_TIMEOUT,
857 : : valent_device_reset_pair,
858 : : device);
859 : 3 : g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_STATE]);
860 : : }
861 : : }
862 : :
863 : : static void
864 : 3 : unpair_action (GSimpleAction *action,
865 : : GVariant *parameter,
866 : : gpointer user_data)
867 : : {
868 : 3 : ValentDevice *device = VALENT_DEVICE (user_data);
869 : :
870 : 3 : valent_device_send_pair (device, FALSE);
871 : 3 : valent_device_set_paired (device, FALSE);
872 : 3 : }
873 : :
874 : : /*
875 : : * ValentObject
876 : : */
877 : : static void
878 : 104 : valent_device_destroy (ValentObject *object)
879 : : {
880 : 104 : ValentDevice *self = VALENT_DEVICE (object);
881 : :
882 : : /* State */
883 : 104 : valent_device_reset_pair (self);
884 : 104 : valent_device_set_channel (self, NULL);
885 : :
886 : : /* Plugins */
887 : 104 : g_signal_handlers_disconnect_by_data (self->engine, self);
888 : 104 : g_hash_table_remove_all (self->plugins);
889 : 104 : g_hash_table_remove_all (self->actions);
890 : 104 : g_hash_table_remove_all (self->handlers);
891 : :
892 : 104 : VALENT_OBJECT_CLASS (valent_device_parent_class)->destroy (object);
893 : 104 : }
894 : :
895 : : /*
896 : : * GObject
897 : : */
898 : : static void
899 : 106 : valent_device_constructed (GObject *object)
900 : : {
901 : 106 : ValentDevice *self = VALENT_DEVICE (object);
902 : 212 : g_autofree char *path = NULL;
903 : 106 : unsigned int n_plugins = 0;
904 : :
905 : 106 : G_OBJECT_CLASS (valent_device_parent_class)->constructed (object);
906 : :
907 : : /* We must at least have a device ID */
908 [ + - ]: 106 : g_assert (self->id != NULL);
909 : :
910 [ + + ]: 106 : if (self->context == NULL)
911 : 100 : self->context = valent_context_new (NULL, "device", self->id);
912 : :
913 : 106 : path = g_strdup_printf ("/ca/andyholmes/valent/device/%s/", self->id);
914 : 106 : self->settings = g_settings_new_with_path ("ca.andyholmes.Valent.Device", path);
915 : 106 : self->paired = g_settings_get_boolean (self->settings, "paired");
916 : :
917 : 106 : self->engine = valent_get_plugin_engine ();
918 : 106 : g_signal_connect_object (self->engine,
919 : : "load-plugin",
920 : : G_CALLBACK (on_load_plugin),
921 : : self,
922 : : G_CONNECT_AFTER);
923 : :
924 : 106 : g_signal_connect_object (self->engine,
925 : : "unload-plugin",
926 : : G_CALLBACK (on_unload_plugin),
927 : : self,
928 : : G_CONNECT_DEFAULT);
929 : :
930 : 106 : n_plugins = g_list_model_get_n_items (G_LIST_MODEL (self->engine));
931 [ + + ]: 657 : for (unsigned int i = 0; i < n_plugins; i++)
932 : : {
933 : 551 : g_autoptr (PeasPluginInfo) info = NULL;
934 : :
935 : 551 : info = g_list_model_get_item (G_LIST_MODEL (self->engine), i);
936 [ + + ]: 551 : if (peas_plugin_info_is_loaded (info))
937 : 550 : on_load_plugin (self->engine, info, self);
938 : : }
939 : 106 : }
940 : :
941 : : static void
942 : 102 : valent_device_finalize (GObject *object)
943 : : {
944 : 102 : ValentDevice *self = VALENT_DEVICE (object);
945 : :
946 [ + - ]: 102 : g_clear_object (&self->context);
947 [ + - ]: 102 : g_clear_object (&self->settings);
948 : :
949 : : /* Properties */
950 [ + + ]: 102 : g_clear_pointer (&self->icon_name, g_free);
951 [ + - ]: 102 : g_clear_pointer (&self->id, g_free);
952 [ + + ]: 102 : g_clear_pointer (&self->name, g_free);
953 [ + + ]: 102 : g_clear_pointer (&self->type, g_free);
954 [ + + ]: 102 : g_clear_pointer (&self->incoming_capabilities, g_strfreev);
955 [ + + ]: 102 : g_clear_pointer (&self->outgoing_capabilities, g_strfreev);
956 : :
957 : : /* State */
958 [ - + ]: 102 : g_clear_object (&self->channel);
959 : :
960 : : /* Plugins */
961 [ + - ]: 102 : g_clear_pointer (&self->plugins, g_hash_table_unref);
962 [ + - ]: 102 : g_clear_pointer (&self->actions, g_hash_table_unref);
963 [ + - ]: 102 : g_clear_pointer (&self->handlers, g_hash_table_unref);
964 [ + - ]: 102 : g_clear_object (&self->menu);
965 : :
966 : 102 : G_OBJECT_CLASS (valent_device_parent_class)->finalize (object);
967 : 102 : }
968 : :
969 : : static void
970 : 24 : valent_device_get_property (GObject *object,
971 : : guint prop_id,
972 : : GValue *value,
973 : : GParamSpec *pspec)
974 : : {
975 : 24 : ValentDevice *self = VALENT_DEVICE (object);
976 : :
977 [ + + + + : 24 : switch (prop_id)
+ + - ]
978 : : {
979 : 1 : case PROP_CONTEXT:
980 : 1 : g_value_set_object (value, self->context);
981 : 1 : break;
982 : :
983 : 4 : case PROP_ICON_NAME:
984 : 4 : g_value_set_string (value, self->icon_name);
985 : 4 : break;
986 : :
987 : 5 : case PROP_ID:
988 : 5 : g_value_set_string (value, self->id);
989 : 5 : break;
990 : :
991 : 10 : case PROP_NAME:
992 : 10 : g_value_set_string (value, self->name);
993 : 10 : break;
994 : :
995 : 2 : case PROP_PLUGINS:
996 : 2 : g_value_take_boxed (value, valent_device_get_plugins (self));
997 : 2 : break;
998 : :
999 : 2 : case PROP_STATE:
1000 : 2 : g_value_set_flags (value, valent_device_get_state (self));
1001 : 2 : break;
1002 : :
1003 : 0 : default:
1004 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1005 : : }
1006 : 24 : }
1007 : :
1008 : : static void
1009 : 212 : valent_device_set_property (GObject *object,
1010 : : guint prop_id,
1011 : : const GValue *value,
1012 : : GParamSpec *pspec)
1013 : : {
1014 : 212 : ValentDevice *self = VALENT_DEVICE (object);
1015 : :
1016 [ + + - ]: 212 : switch (prop_id)
1017 : : {
1018 : 106 : case PROP_CONTEXT:
1019 : 106 : self->context = g_value_dup_object (value);
1020 : 106 : break;
1021 : :
1022 : 106 : case PROP_ID:
1023 : 106 : self->id = g_value_dup_string (value);
1024 : 106 : break;
1025 : :
1026 : 0 : default:
1027 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1028 : : }
1029 : 212 : }
1030 : :
1031 : : static void
1032 : 106 : valent_device_init (ValentDevice *self)
1033 : : {
1034 : 106 : GSimpleAction *action = NULL;
1035 : :
1036 : : /* Plugins */
1037 : 106 : self->plugins = g_hash_table_new_full (NULL, NULL, NULL, valent_plugin_free);
1038 : 106 : self->handlers = g_hash_table_new_full (g_str_hash,
1039 : : g_str_equal,
1040 : : g_free,
1041 : : (GDestroyNotify)g_ptr_array_unref);
1042 : 106 : self->actions = g_hash_table_new_full (g_str_hash,
1043 : : g_str_equal,
1044 : : g_free,
1045 : : g_object_unref);
1046 : 106 : self->menu = g_menu_new ();
1047 : :
1048 : : /* Stock Actions */
1049 : 106 : action = g_simple_action_new ("pair", NULL);
1050 : 106 : g_signal_connect_object (action,
1051 : : "activate",
1052 : : G_CALLBACK (pair_action),
1053 : : self, 0);
1054 : 106 : g_hash_table_replace (self->actions,
1055 : 106 : g_strdup ("pair"),
1056 : : g_steal_pointer (&action));
1057 : :
1058 : 106 : action = g_simple_action_new ("unpair", NULL);
1059 : 106 : g_signal_connect_object (action,
1060 : : "activate",
1061 : : G_CALLBACK (unpair_action),
1062 : : self, 0);
1063 : 106 : g_hash_table_replace (self->actions,
1064 : 106 : g_strdup ("unpair"),
1065 : : g_steal_pointer (&action));
1066 : 106 : }
1067 : :
1068 : : static void
1069 : 32 : valent_device_class_init (ValentDeviceClass *klass)
1070 : : {
1071 : 32 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
1072 : 32 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
1073 : :
1074 : 32 : object_class->constructed = valent_device_constructed;
1075 : 32 : object_class->finalize = valent_device_finalize;
1076 : 32 : object_class->get_property = valent_device_get_property;
1077 : 32 : object_class->set_property = valent_device_set_property;
1078 : :
1079 : 32 : vobject_class->destroy = valent_device_destroy;
1080 : :
1081 : : /**
1082 : : * ValentDevice:context: (getter get_context)
1083 : : *
1084 : : * The data context.
1085 : : *
1086 : : * Since: 1.0
1087 : : */
1088 : 64 : properties [PROP_CONTEXT] =
1089 : 32 : g_param_spec_object ("context", NULL, NULL,
1090 : : VALENT_TYPE_CONTEXT,
1091 : : (G_PARAM_READWRITE |
1092 : : G_PARAM_CONSTRUCT_ONLY |
1093 : : G_PARAM_EXPLICIT_NOTIFY |
1094 : : G_PARAM_STATIC_STRINGS));
1095 : :
1096 : : /**
1097 : : * ValentDevice:icon-name: (getter get_icon_name)
1098 : : *
1099 : : * A symbolic icon name for the device.
1100 : : *
1101 : : * Since: 1.0
1102 : : */
1103 : 64 : properties [PROP_ICON_NAME] =
1104 : 32 : g_param_spec_string ("icon-name", NULL, NULL,
1105 : : "computer-symbolic",
1106 : : (G_PARAM_READABLE |
1107 : : G_PARAM_EXPLICIT_NOTIFY |
1108 : : G_PARAM_STATIC_STRINGS));
1109 : :
1110 : : /**
1111 : : * ValentDevice:id: (getter get_id)
1112 : : *
1113 : : * A unique ID for the device.
1114 : : *
1115 : : * By convention, the single source of truth for a device ID in KDE Connect is
1116 : : * the common name of its TLS certificate. It is not well-defined how this ID
1117 : : * is generated, however.
1118 : : *
1119 : : * Since: 1.0
1120 : : */
1121 : 64 : properties [PROP_ID] =
1122 : 32 : g_param_spec_string ("id", NULL, NULL,
1123 : : NULL,
1124 : : (G_PARAM_READWRITE |
1125 : : G_PARAM_CONSTRUCT_ONLY |
1126 : : G_PARAM_EXPLICIT_NOTIFY |
1127 : : G_PARAM_STATIC_STRINGS));
1128 : :
1129 : : /**
1130 : : * ValentDevice:name: (getter get_name)
1131 : : *
1132 : : * A display name for the device.
1133 : : *
1134 : : * Since: 1.0
1135 : : */
1136 : 64 : properties [PROP_NAME] =
1137 : 32 : g_param_spec_string ("name", NULL, NULL,
1138 : : NULL,
1139 : : (G_PARAM_READABLE |
1140 : : G_PARAM_EXPLICIT_NOTIFY |
1141 : : G_PARAM_STATIC_STRINGS));
1142 : :
1143 : : /**
1144 : : * ValentDevice:plugins: (getter get_plugins)
1145 : : *
1146 : : * A list of loaded plugin names.
1147 : : *
1148 : : * Since: 1.0
1149 : : */
1150 : 64 : properties [PROP_PLUGINS] =
1151 : 32 : g_param_spec_boxed ("plugins", NULL, NULL,
1152 : : G_TYPE_STRV,
1153 : : (G_PARAM_READABLE |
1154 : : G_PARAM_EXPLICIT_NOTIFY |
1155 : : G_PARAM_STATIC_STRINGS));
1156 : :
1157 : : /**
1158 : : * ValentDevice:state: (getter get_state)
1159 : : *
1160 : : * The state of the device.
1161 : : *
1162 : : * Since: 1.0
1163 : : */
1164 : 64 : properties [PROP_STATE] =
1165 : 32 : g_param_spec_flags ("state", NULL, NULL,
1166 : : VALENT_TYPE_DEVICE_STATE,
1167 : : VALENT_DEVICE_STATE_NONE,
1168 : : (G_PARAM_READABLE |
1169 : : G_PARAM_EXPLICIT_NOTIFY |
1170 : : G_PARAM_STATIC_STRINGS));
1171 : :
1172 : 32 : g_object_class_install_properties (object_class, N_PROPERTIES, properties);
1173 : 32 : }
1174 : :
1175 : : /**
1176 : : * valent_device_new:
1177 : : * @id: (not nullable): a device ID
1178 : : *
1179 : : * Create a new device for @id.
1180 : : *
1181 : : * Returns: (transfer full) (nullable): a new `ValentDevice`
1182 : : *
1183 : : * Since: 1.0
1184 : : */
1185 : : ValentDevice *
1186 : 2 : valent_device_new (const char *id)
1187 : : {
1188 [ + - ]: 2 : g_return_val_if_fail (valent_device_validate_id (id), NULL);
1189 : :
1190 : 2 : return g_object_new (VALENT_TYPE_DEVICE,
1191 : : "id", id,
1192 : : NULL);
1193 : : }
1194 : :
1195 : : /*< private >
1196 : : * valent_device_new_full:
1197 : : * @identity: a KDE Connect identity packet
1198 : : * @context: (nullable): a `ValentContext`
1199 : : *
1200 : : * Create a new device for @identity.
1201 : : *
1202 : : * Returns: (transfer full) (nullable): a new `ValentDevice`
1203 : : */
1204 : : ValentDevice *
1205 : 102 : valent_device_new_full (JsonNode *identity,
1206 : : ValentContext *context)
1207 : : {
1208 : 102 : ValentDevice *ret;
1209 : 102 : const char *id;
1210 : :
1211 [ + - ]: 102 : g_return_val_if_fail (VALENT_IS_PACKET (identity), NULL);
1212 : :
1213 [ - + ]: 102 : if (!valent_packet_get_string (identity, "deviceId", &id))
1214 : : {
1215 : 0 : g_critical ("%s(): missing \"deviceId\" field", G_STRFUNC);
1216 : 0 : return NULL;
1217 : : }
1218 : :
1219 [ - + ]: 102 : if (!valent_device_validate_id (id))
1220 : : {
1221 : 0 : g_critical ("%s(): invalid device ID \"%s\"", G_STRFUNC, id);
1222 : 0 : return NULL;
1223 : : }
1224 : :
1225 : 102 : ret = g_object_new (VALENT_TYPE_DEVICE,
1226 : : "id", id,
1227 : : "context", context,
1228 : : NULL);
1229 : 102 : valent_device_handle_identity (ret, identity);
1230 : :
1231 : 102 : return ret;
1232 : : }
1233 : :
1234 : : static void
1235 : 139 : valent_device_send_packet_cb (ValentChannel *channel,
1236 : : GAsyncResult *result,
1237 : : gpointer user_data)
1238 : : {
1239 : 139 : g_autoptr (GTask) task = G_TASK (user_data);
1240 : 139 : ValentDevice *device = g_task_get_source_object (task);
1241 [ + - + - ]: 139 : g_autoptr (GError) error = NULL;
1242 : :
1243 [ + - ]: 139 : g_assert (VALENT_IS_DEVICE (device));
1244 : :
1245 [ + + ]: 139 : if (valent_channel_write_packet_finish (channel, result, &error))
1246 [ - + ]: 134 : return g_task_return_boolean (task, TRUE);
1247 : :
1248 : 5 : VALENT_NOTE ("%s: %s", device->name, error->message);
1249 : 5 : g_task_return_error (task, g_steal_pointer (&error));
1250 : :
1251 : 5 : valent_object_lock (VALENT_OBJECT (device));
1252 [ + + ]: 5 : if (device->channel == channel)
1253 : 2 : valent_device_set_channel (device, NULL);
1254 [ - + ]: 5 : valent_object_unlock (VALENT_OBJECT (device));
1255 : : }
1256 : :
1257 : : /**
1258 : : * valent_device_send_packet:
1259 : : * @device: a `ValentDevice`
1260 : : * @packet: a KDE Connect packet
1261 : : * @cancellable: (nullable): a `GCancellable`
1262 : : * @callback: (scope async): a `GAsyncReadyCallback`
1263 : : * @user_data: user supplied data
1264 : : *
1265 : : * Send a KDE Connect packet to the device.
1266 : : *
1267 : : * Call [method@Valent.Device.send_packet_finish] to get the result.
1268 : : *
1269 : : * If @device is disconnected or unpaired when this method is called,
1270 : : * %G_IO_ERROR_NOT_CONNECTED or %G_IO_ERROR_PERMISSION_DENIED will be set on the
1271 : : * result, respectively.
1272 : : *
1273 : : * Since: 1.0
1274 : : */
1275 : : void
1276 : 141 : valent_device_send_packet (ValentDevice *device,
1277 : : JsonNode *packet,
1278 : : GCancellable *cancellable,
1279 : : GAsyncReadyCallback callback,
1280 : : gpointer user_data)
1281 : : {
1282 : 141 : g_autoptr (GTask) task = NULL;
1283 : 282 : g_autoptr (GCancellable) destroy = NULL;
1284 : :
1285 [ + - ]: 141 : g_return_if_fail (VALENT_IS_DEVICE (device));
1286 [ - + ]: 141 : g_return_if_fail (VALENT_IS_PACKET (packet));
1287 : :
1288 : 141 : valent_object_lock (VALENT_OBJECT (device));
1289 : :
1290 [ + + ]: 141 : if G_UNLIKELY (device->channel == NULL)
1291 : : {
1292 : 1 : valent_object_unlock (VALENT_OBJECT (device));
1293 : 1 : return g_task_report_new_error (device,
1294 : : callback,
1295 : : user_data,
1296 : : valent_device_send_packet,
1297 : : G_IO_ERROR,
1298 : : G_IO_ERROR_NOT_CONNECTED,
1299 : : "%s is disconnected", device->name);
1300 : : }
1301 : :
1302 [ + + ]: 140 : if G_UNLIKELY (!device->paired)
1303 : : {
1304 : 1 : valent_object_unlock (VALENT_OBJECT (device));
1305 : 1 : return g_task_report_new_error (device,
1306 : : callback,
1307 : : user_data,
1308 : : valent_device_send_packet,
1309 : : G_IO_ERROR,
1310 : : G_IO_ERROR_PERMISSION_DENIED,
1311 : : "%s is unpaired", device->name);
1312 : : }
1313 : :
1314 : 139 : destroy = valent_object_chain_cancellable (VALENT_OBJECT (device),
1315 : : cancellable);
1316 : 139 : task = g_task_new (device, destroy, callback, user_data);
1317 [ + - ]: 139 : g_task_set_source_tag (task, valent_device_send_packet);
1318 : :
1319 : 139 : VALENT_JSON (packet, device->name);
1320 : 139 : valent_channel_write_packet (device->channel,
1321 : : packet,
1322 : : destroy,
1323 : : (GAsyncReadyCallback)valent_device_send_packet_cb,
1324 : : g_steal_pointer (&task));
1325 : :
1326 [ + - ]: 139 : valent_object_unlock (VALENT_OBJECT (device));
1327 : : }
1328 : :
1329 : : /**
1330 : : * valent_device_send_packet_finish:
1331 : : * @device: a `ValentDevice`
1332 : : * @result: a `GAsyncResult`
1333 : : * @error: (nullable): a `GError`
1334 : : *
1335 : : * Finish an operation started by [method@Valent.Device.send_packet].
1336 : : *
1337 : : * Returns: %TRUE if successful, or %FALSE with @error set
1338 : : *
1339 : : * Since: 1.0
1340 : : */
1341 : : gboolean
1342 : 121 : valent_device_send_packet_finish (ValentDevice *device,
1343 : : GAsyncResult *result,
1344 : : GError **error)
1345 : : {
1346 [ + - ]: 121 : g_return_val_if_fail (VALENT_IS_DEVICE (device), FALSE);
1347 [ - + ]: 121 : g_return_val_if_fail (g_task_is_valid (result, device), FALSE);
1348 [ + - - + ]: 121 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1349 : :
1350 : 121 : return g_task_propagate_boolean (G_TASK (result), error);
1351 : : }
1352 : :
1353 : : /**
1354 : : * valent_device_ref_channel:
1355 : : * @device: a `ValentDevice`
1356 : : *
1357 : : * Get the active channel.
1358 : : *
1359 : : * Returns: (transfer full) (nullable): a `ValentChannel`, or %NULL if disconnected
1360 : : *
1361 : : * Since: 1.0
1362 : : */
1363 : : ValentChannel *
1364 : 33 : valent_device_ref_channel (ValentDevice *device)
1365 : : {
1366 : 33 : ValentChannel *ret = NULL;
1367 : :
1368 [ + - ]: 33 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
1369 : :
1370 : 33 : valent_object_lock (VALENT_OBJECT (device));
1371 [ + - ]: 33 : if (device->channel != NULL)
1372 : 33 : ret = g_object_ref (device->channel);
1373 : 33 : valent_object_unlock (VALENT_OBJECT (device));
1374 : :
1375 : 33 : return ret;
1376 : : }
1377 : :
1378 : : static void
1379 : 203 : read_packet_cb (ValentChannel *channel,
1380 : : GAsyncResult *result,
1381 : : ValentDevice *device)
1382 : : {
1383 : 406 : g_autoptr (GError) error = NULL;
1384 [ + + ]: 203 : g_autoptr (JsonNode) packet = NULL;
1385 : :
1386 [ + - ]: 203 : g_assert (VALENT_IS_CHANNEL (channel));
1387 [ - + ]: 203 : g_assert (VALENT_IS_DEVICE (device));
1388 : :
1389 : 203 : packet = valent_channel_read_packet_finish (channel, result, &error);
1390 : :
1391 : : /* On success, queue another read before handling the packet */
1392 [ + + ]: 203 : if (packet != NULL)
1393 : : {
1394 : 124 : valent_channel_read_packet (channel,
1395 : : g_task_get_cancellable (G_TASK (result)),
1396 : : (GAsyncReadyCallback)read_packet_cb,
1397 : : g_object_ref (device));
1398 : 124 : valent_device_handle_packet (device, packet);
1399 : : }
1400 : :
1401 : : /* On failure, drop our reference if it's still the active channel */
1402 : : else
1403 : : {
1404 : 79 : VALENT_NOTE ("%s: %s", device->name, error->message);
1405 : :
1406 : 79 : valent_object_lock (VALENT_OBJECT (device));
1407 [ + + ]: 79 : if (device->channel == channel)
1408 : 72 : valent_device_set_channel (device, NULL);
1409 : 79 : valent_object_unlock (VALENT_OBJECT (device));
1410 : : }
1411 : :
1412 [ + + ]: 203 : g_object_unref (device);
1413 : 203 : }
1414 : :
1415 : : /**
1416 : : * valent_device_set_channel:
1417 : : * @device: A `ValentDevice`
1418 : : * @channel: (nullable): A `ValentChannel`
1419 : : *
1420 : : * Sets the active channel.
1421 : : *
1422 : : * Since: 1.0
1423 : : */
1424 : : void
1425 : 265 : valent_device_set_channel (ValentDevice *device,
1426 : : ValentChannel *channel)
1427 : : {
1428 : 265 : gboolean was_connected;
1429 : 265 : gboolean is_connected;
1430 : :
1431 [ + - ]: 265 : g_return_if_fail (VALENT_IS_DEVICE (device));
1432 [ + + - + ]: 265 : g_return_if_fail (channel == NULL || VALENT_IS_CHANNEL (channel));
1433 : :
1434 : 265 : valent_object_lock (VALENT_OBJECT (device));
1435 : :
1436 [ + + ]: 265 : if (device->channel == channel)
1437 : : {
1438 : 106 : valent_object_unlock (VALENT_OBJECT (device));
1439 : 106 : return;
1440 : : }
1441 : :
1442 : : /* If there's an active channel, close it asynchronously and drop our
1443 : : * reference so the task holds the final reference. */
1444 [ + + ]: 159 : if ((was_connected = (device->channel != NULL)))
1445 : : {
1446 : 79 : valent_channel_close_async (device->channel, NULL, NULL, NULL);
1447 [ + - ]: 79 : g_clear_object (&device->channel);
1448 : : }
1449 : :
1450 : : /* If there's a new channel, handle the peer identity and queue the first
1451 : : * read operation before notifying of the state change. */
1452 [ + + ]: 159 : if ((is_connected = g_set_object (&device->channel, channel)))
1453 : : {
1454 : 239 : g_autoptr (GCancellable) cancellable = NULL;
1455 : 80 : JsonNode *peer_identity;
1456 : :
1457 : : /* Handle the peer identity packet */
1458 : 80 : peer_identity = valent_channel_get_peer_identity (channel);
1459 : 80 : valent_device_handle_identity (device, peer_identity);
1460 : :
1461 : : /* Start receiving packets */
1462 : 80 : cancellable = valent_object_ref_cancellable (VALENT_OBJECT (device));
1463 [ + - ]: 80 : valent_channel_read_packet (channel,
1464 : : cancellable,
1465 : : (GAsyncReadyCallback)read_packet_cb,
1466 : : g_object_ref (device));
1467 : : }
1468 : :
1469 : 159 : valent_object_unlock (VALENT_OBJECT (device));
1470 : :
1471 : : /* If the state changed, update the plugins and notify */
1472 [ + - ]: 159 : if (is_connected == was_connected)
1473 : : return;
1474 : :
1475 : 159 : valent_device_update_plugins (device);
1476 : 159 : g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_STATE]);
1477 : : }
1478 : :
1479 : : /**
1480 : : * valent_device_get_context: (get-property context)
1481 : : * @device: a `ValentDevice`
1482 : : *
1483 : : * Get the data context.
1484 : : *
1485 : : * Returns: (transfer full): a `ValentContext`
1486 : : *
1487 : : * Since: 1.0
1488 : : */
1489 : : ValentContext *
1490 : 115 : valent_device_get_context (ValentDevice *device)
1491 : : {
1492 [ + - ]: 115 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
1493 : :
1494 : 115 : return device->context;
1495 : : }
1496 : :
1497 : : /**
1498 : : * valent_device_get_icon_name: (get-property icon-name)
1499 : : * @device: a `ValentDevice`
1500 : : *
1501 : : * Get the symbolic icon name.
1502 : : *
1503 : : * Returns: (transfer none): the icon name.
1504 : : *
1505 : : * Since: 1.0
1506 : : */
1507 : : const char *
1508 : 3 : valent_device_get_icon_name (ValentDevice *device)
1509 : : {
1510 [ + - ]: 3 : g_return_val_if_fail (VALENT_IS_DEVICE (device), "computer-symbolic");
1511 : :
1512 : 3 : return device->icon_name;
1513 : : }
1514 : :
1515 : : /**
1516 : : * valent_device_get_id: (get-property id)
1517 : : * @device: a `ValentDevice`
1518 : : *
1519 : : * Get the unique ID.
1520 : : *
1521 : : * Returns: (transfer none): the device id.
1522 : : *
1523 : : * Since: 1.0
1524 : : */
1525 : : const char *
1526 : 73 : valent_device_get_id (ValentDevice *device)
1527 : : {
1528 [ + - ]: 73 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
1529 [ - + ]: 73 : g_return_val_if_fail (device->id != NULL, NULL);
1530 : :
1531 : : return device->id;
1532 : : }
1533 : :
1534 : : /**
1535 : : * valent_device_get_menu:
1536 : : * @device: a `ValentDevice`
1537 : : *
1538 : : * Get the [class@Gio.MenuModel] of the device.
1539 : : *
1540 : : * Plugins may add items and submenus to this when they want to expose actions
1541 : : * with presentation details like a label or icon.
1542 : : *
1543 : : * Returns: (transfer none): a `GMenuModel`
1544 : : *
1545 : : * Since: 1.0
1546 : : */
1547 : : GMenuModel *
1548 : 169 : valent_device_get_menu (ValentDevice *device)
1549 : : {
1550 [ + - ]: 169 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
1551 : :
1552 : 169 : return G_MENU_MODEL (device->menu);
1553 : : }
1554 : :
1555 : : /**
1556 : : * valent_device_get_name:
1557 : : * @device: a `ValentDevice`
1558 : : *
1559 : : * Get the display name of the device.
1560 : : *
1561 : : * Returns: (transfer none) (nullable): a display name, or %NULL if unset
1562 : : *
1563 : : * Since: 1.0
1564 : : */
1565 : : const char *
1566 : 54 : valent_device_get_name (ValentDevice *device)
1567 : : {
1568 [ + - ]: 54 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
1569 : :
1570 : 54 : return device->name;
1571 : : }
1572 : :
1573 : : /**
1574 : : * valent_device_set_paired:
1575 : : * @device: a `ValentDevice`
1576 : : * @paired: %TRUE if paired, %FALSE if unpaired
1577 : : *
1578 : : * Set the paired state of the device.
1579 : : *
1580 : : * NOTE: since valent_device_update_plugins() will be called as a side effect,
1581 : : * this must be called after valent_device_send_pair().
1582 : : */
1583 : : void
1584 : 104 : valent_device_set_paired (ValentDevice *device,
1585 : : gboolean paired)
1586 : : {
1587 [ + - ]: 104 : g_assert (VALENT_IS_DEVICE (device));
1588 : :
1589 : 104 : valent_object_lock (VALENT_OBJECT (device));
1590 : :
1591 : : /* If nothing's changed, only reset pending pair timeouts */
1592 [ + + ]: 104 : if (device->paired == paired)
1593 : : {
1594 : 3 : valent_device_reset_pair (device);
1595 : 3 : valent_object_unlock (VALENT_OBJECT (device));
1596 : 3 : return;
1597 : : }
1598 : :
1599 : : /* FIXME: If we're connected store/clear connection data */
1600 [ + + + + ]: 101 : if (paired && device->channel != NULL)
1601 : 6 : valent_channel_store_data (device->channel, device->context);
1602 : : else if (!paired)
1603 : 6 : valent_context_clear (device->context);
1604 : :
1605 : 101 : device->paired = paired;
1606 : 101 : g_settings_set_boolean (device->settings, "paired", device->paired);
1607 : :
1608 : 101 : valent_object_unlock (VALENT_OBJECT (device));
1609 : :
1610 : : /* Update plugins and notify */
1611 : 101 : valent_device_update_plugins (device);
1612 : 101 : valent_device_reset_pair (device);
1613 : : }
1614 : :
1615 : : /**
1616 : : * valent_device_get_plugins: (get-property plugins)
1617 : : * @device: a `ValentDevice`
1618 : : *
1619 : : * Get a list of the loaded plugins.
1620 : : *
1621 : : * Returns: (transfer full): a list of loaded plugins
1622 : : *
1623 : : * Since: 1.0
1624 : : */
1625 : : GStrv
1626 : 99 : valent_device_get_plugins (ValentDevice *device)
1627 : : {
1628 : 198 : g_autoptr (GStrvBuilder) builder = NULL;
1629 : 99 : GHashTableIter iter;
1630 : 99 : PeasPluginInfo *info;
1631 : :
1632 [ + - ]: 99 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
1633 : :
1634 : 99 : builder = g_strv_builder_new ();
1635 : 99 : g_hash_table_iter_init (&iter, device->plugins);
1636 : :
1637 [ + + ]: 289 : while (g_hash_table_iter_next (&iter, (void **)&info, NULL))
1638 : 190 : g_strv_builder_add (builder, peas_plugin_info_get_module_name (info));
1639 : :
1640 : 99 : return g_strv_builder_end (builder);
1641 : : }
1642 : :
1643 : : /**
1644 : : * valent_device_get_state: (get-property state)
1645 : : * @device: a `ValentDevice`
1646 : : *
1647 : : * Get the state of the device.
1648 : : *
1649 : : * Returns: `ValentDeviceState` flags describing the state of the device
1650 : : *
1651 : : * Since: 1.0
1652 : : */
1653 : : ValentDeviceState
1654 : 573 : valent_device_get_state (ValentDevice *device)
1655 : : {
1656 : 573 : ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
1657 : :
1658 [ + - ]: 573 : g_return_val_if_fail (VALENT_IS_DEVICE (device), state);
1659 : :
1660 : 573 : valent_object_lock (VALENT_OBJECT (device));
1661 : :
1662 [ + + ]: 573 : if (device->channel != NULL)
1663 : 143 : state |= VALENT_DEVICE_STATE_CONNECTED;
1664 : :
1665 [ + + ]: 573 : if (device->paired)
1666 : 291 : state |= VALENT_DEVICE_STATE_PAIRED;
1667 : :
1668 [ + + ]: 573 : if (device->incoming_pair > 0)
1669 : 3 : state |= VALENT_DEVICE_STATE_PAIR_INCOMING;
1670 : :
1671 [ + + ]: 573 : if (device->outgoing_pair > 0)
1672 : 2 : state |= VALENT_DEVICE_STATE_PAIR_OUTGOING;
1673 : :
1674 : 573 : valent_object_unlock (VALENT_OBJECT (device));
1675 : :
1676 : 573 : return state;
1677 : : }
1678 : :
1679 : : /**
1680 : : * valent_device_get_verification_key:
1681 : : * @device: a `ValentDevice`
1682 : : *
1683 : : * Get a verification key for the device connection.
1684 : : *
1685 : : * Returns: (nullable) (transfer full): a verification key
1686 : : *
1687 : : * Since: 1.0
1688 : : */
1689 : : char *
1690 : 4 : valent_device_get_verification_key (ValentDevice *device)
1691 : : {
1692 : 4 : char *verification_key = NULL;
1693 : :
1694 : 4 : VALENT_ENTRY;
1695 : :
1696 [ + - ]: 4 : g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
1697 : :
1698 : 4 : valent_object_lock (VALENT_OBJECT (device));
1699 [ + + ]: 4 : if (device->channel != NULL)
1700 : : {
1701 : 6 : g_autoptr (GChecksum) checksum = NULL;
1702 : 2 : GTlsCertificate *cert = NULL;
1703 : 2 : GTlsCertificate *peer_cert = NULL;
1704 : 2 : GByteArray *pubkey;
1705 : 2 : GByteArray *peer_pubkey;
1706 : 2 : size_t cmplen;
1707 : :
1708 : 2 : cert = valent_channel_get_certificate (device->channel);
1709 : 2 : peer_cert = valent_channel_get_peer_certificate (device->channel);
1710 [ - + ]: 2 : g_return_val_if_fail (cert != NULL || peer_cert != NULL, NULL);
1711 : :
1712 : 2 : pubkey = valent_certificate_get_public_key (cert);
1713 : 2 : peer_pubkey = valent_certificate_get_public_key (peer_cert);
1714 [ - + ]: 2 : g_return_val_if_fail (pubkey != NULL || peer_pubkey != NULL, NULL);
1715 : :
1716 : 2 : checksum = g_checksum_new (G_CHECKSUM_SHA256);
1717 : 2 : cmplen = MIN (pubkey->len, peer_pubkey->len);
1718 [ + + ]: 2 : if (memcmp (pubkey->data, peer_pubkey->data, cmplen) > 0)
1719 : : {
1720 : 1 : g_checksum_update (checksum, pubkey->data, pubkey->len);
1721 : 1 : g_checksum_update (checksum, peer_pubkey->data, peer_pubkey->len);
1722 : : }
1723 : : else
1724 : : {
1725 : 1 : g_checksum_update (checksum, peer_pubkey->data, peer_pubkey->len);
1726 : 1 : g_checksum_update (checksum, pubkey->data, pubkey->len);
1727 : : }
1728 : :
1729 [ + - ]: 2 : if (device->protocol_version >= VALENT_NETWORK_PROTOCOL_V8)
1730 : : {
1731 : 2 : g_autofree char *timestamp_str = NULL;
1732 : :
1733 : 2 : timestamp_str = g_strdup_printf ("%"PRId64, device->pair_timestamp);
1734 : 2 : g_checksum_update (checksum, (const unsigned char *)timestamp_str, -1);
1735 : : }
1736 : :
1737 [ + - ]: 2 : verification_key = g_ascii_strup (g_checksum_get_string (checksum), 8);
1738 : : }
1739 : 4 : valent_object_unlock (VALENT_OBJECT (device));
1740 : :
1741 : 4 : VALENT_RETURN (verification_key);
1742 : : }
1743 : :
1744 : : /**
1745 : : * valent_device_generate_id:
1746 : : *
1747 : : * Generate a new KDE Connect device ID.
1748 : : *
1749 : : * See [func@Valent.Device.validate_id] for a description of valid device ID.
1750 : : *
1751 : : * Returns: (transfer full): a new KDE Connect device ID
1752 : : *
1753 : : * Since: 1.0
1754 : : */
1755 : : char *
1756 : 223 : valent_device_generate_id (void)
1757 : : {
1758 : 223 : char *id = g_uuid_string_random ();
1759 : :
1760 [ + + ]: 8251 : for (uint_fast8_t i = 0; id[i] != '\0'; i++)
1761 : : {
1762 [ + + ]: 8028 : if G_UNLIKELY (id[i] == '-')
1763 : 892 : id[i] = '_';
1764 : : }
1765 : :
1766 : 223 : return g_steal_pointer (&id);
1767 : : }
1768 : :
1769 : : /**
1770 : : * valent_device_validate_id:
1771 : : * @id: (nullable): a KDE Connect device ID
1772 : : *
1773 : : * Validate a KDE Connect device ID.
1774 : : *
1775 : : * A compliant device ID matches the pattern `/^[a-zA-Z0-9_]{32,38}$/`, being
1776 : : * alphanumeric with a length of 32-38 characters. Recommended practice is to
1777 : : * generate a UUIDv4 and remove the hyphens (`-`), or replace them with
1778 : : * underscores (`_`).
1779 : : *
1780 : : * This became a requirement in version 8 or the KDE Connect protocol.
1781 : : *
1782 : : * Returns: %TRUE if valid, or %FALSE
1783 : : *
1784 : : * Since: 1.0
1785 : : */
1786 : : gboolean
1787 : 153 : valent_device_validate_id (const char *id)
1788 : : {
1789 : 153 : size_t len = 0;
1790 : :
1791 [ + - + + ]: 153 : if G_UNLIKELY (id == NULL || *id == '\0')
1792 : : return FALSE;
1793 : :
1794 [ + + ]: 5494 : while (id[len] != '\0')
1795 : : {
1796 : 5342 : char c = id[len];
1797 : :
1798 [ + + + - ]: 5342 : if (!g_ascii_isalnum (c) && c != '_')
1799 : : return FALSE;
1800 : :
1801 [ + - ]: 5342 : if (++len > 38)
1802 : : return FALSE;
1803 : : }
1804 : :
1805 : 152 : return len >= 32;
1806 : : }
1807 : :
1808 : : /**
1809 : : * valent_device_validate_name:
1810 : : * @name: (nullable): a KDE Connect device name
1811 : : *
1812 : : * Validate a KDE Connect device name.
1813 : : *
1814 : : * A compliant device name matches the pattern `/^[^"',;:.!?()\[\]<>]{1,32}$/`,
1815 : : * containing none of `"',;:.!?()[]<>` with a length of 1-32 characters, with
1816 : : * at least one non-whitespace character.
1817 : : *
1818 : : * This became a requirement in version 8 or the KDE Connect protocol.
1819 : : *
1820 : : * Returns: %TRUE if valid, or %FALSE
1821 : : *
1822 : : * Since: 1.0
1823 : : */
1824 : : gboolean
1825 : 6 : valent_device_validate_name (const char *name)
1826 : : {
1827 : 6 : static const uint8_t forbidden_chars[256] = {
1828 : : ['"'] = 1, ['\''] = 1,
1829 : : [','] = 1, ['.'] = 1,
1830 : : [';'] = 1, [':'] = 1,
1831 : : ['!'] = 1, ['?'] = 1,
1832 : : ['('] = 1, [')'] = 1,
1833 : : ['['] = 1, [']'] = 1,
1834 : : ['<'] = 1, ['>'] = 1,
1835 : : };
1836 : 6 : size_t len = 0;
1837 : 6 : gboolean has_nonwhitespace = FALSE;
1838 : :
1839 [ + - + - ]: 6 : if G_UNLIKELY (name == NULL || *name == '\0')
1840 : : return FALSE;
1841 : :
1842 [ + + ]: 51 : while (name[len] != '\0')
1843 : : {
1844 : 46 : uint8_t c = (uint8_t)name[len];
1845 : :
1846 [ + + ]: 46 : if (forbidden_chars[c])
1847 : : return FALSE;
1848 : :
1849 [ + + ]: 45 : if (!has_nonwhitespace)
1850 : 8 : has_nonwhitespace = !g_ascii_isspace (c);
1851 : :
1852 [ + - ]: 45 : if (++len > 32)
1853 : : return FALSE;
1854 : : }
1855 : :
1856 : : return has_nonwhitespace;
1857 : : }
1858 : :
1859 : : /*< private >
1860 : : * valent_device_reload_plugins:
1861 : : * @device: a `ValentDevice`
1862 : : *
1863 : : * Reload all plugins.
1864 : : *
1865 : : * Check each available plugin and load or unload them if the required
1866 : : * capabilities have changed.
1867 : : */
1868 : : static void
1869 : 182 : valent_device_reload_plugins (ValentDevice *device)
1870 : : {
1871 : 182 : unsigned int n_plugins = 0;
1872 : :
1873 [ + - ]: 182 : g_assert (VALENT_IS_DEVICE (device));
1874 : :
1875 : 182 : n_plugins = g_list_model_get_n_items (G_LIST_MODEL (device->engine));
1876 : :
1877 [ + + ]: 1050 : for (unsigned int i = 0; i < n_plugins; i++)
1878 : : {
1879 : 868 : g_autoptr (PeasPluginInfo) info = NULL;
1880 : :
1881 : 868 : info = g_list_model_get_item (G_LIST_MODEL (device->engine), i);
1882 : :
1883 [ + + ]: 868 : if (valent_device_supports_plugin (device, info))
1884 : 362 : on_load_plugin (device->engine, info, device);
1885 : : else
1886 : 506 : on_unload_plugin (device->engine, info, device);
1887 : : }
1888 : 182 : }
1889 : :
1890 : : /*< private >
1891 : : * valent_device_update_plugins:
1892 : : * @device: a `ValentDevice`
1893 : : *
1894 : : * Update all plugins.
1895 : : *
1896 : : * Call [method@Valent.DevicePlugin.update_state] on each enabled plugin.
1897 : : */
1898 : : static void
1899 : 260 : valent_device_update_plugins (ValentDevice *device)
1900 : : {
1901 : 260 : ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
1902 : 260 : GHashTableIter iter;
1903 : 260 : ValentPlugin *plugin;
1904 : :
1905 [ + - ]: 260 : g_assert (VALENT_IS_DEVICE (device));
1906 : :
1907 : 260 : state = valent_device_get_state (device);
1908 : :
1909 : 260 : g_hash_table_iter_init (&iter, device->plugins);
1910 : :
1911 [ + + ]: 778 : while (g_hash_table_iter_next (&iter, NULL, (void **)&plugin))
1912 : : {
1913 [ - + ]: 518 : if (plugin->extension == NULL)
1914 : 0 : continue;
1915 : :
1916 : 518 : valent_device_plugin_update_state (VALENT_DEVICE_PLUGIN (plugin->extension),
1917 : : state);
1918 : : }
1919 : 260 : }
1920 : :
1921 : : /*< private >
1922 : : * valent_device_supports_plugin:
1923 : : * @device: a `ValentDevice`
1924 : : * @info: a `PeasPluginInfo`
1925 : : *
1926 : : * Check if @device supports the plugin described by @info.
1927 : : *
1928 : : * Returns: %TRUE if supported, or %FALSE if not
1929 : : */
1930 : : static gboolean
1931 : 1786 : valent_device_supports_plugin (ValentDevice *device,
1932 : : PeasPluginInfo *info)
1933 : : {
1934 : 1786 : const char **device_incoming;
1935 : 1786 : const char **device_outgoing;
1936 : 1786 : const char *in_str, *out_str;
1937 : :
1938 [ + - ]: 1786 : g_assert (VALENT_IS_DEVICE (device));
1939 [ - + ]: 1786 : g_assert (info != NULL);
1940 : :
1941 [ + + ]: 1786 : if (!peas_engine_provides_extension (device->engine,
1942 : : info,
1943 : : VALENT_TYPE_DEVICE_PLUGIN))
1944 : : return FALSE;
1945 : :
1946 : : /* Packet-less plugins aren't dependent on device capabilities */
1947 : 1462 : in_str = peas_plugin_info_get_external_data (info, "DevicePluginIncoming");
1948 : 1462 : out_str = peas_plugin_info_get_external_data (info, "DevicePluginOutgoing");
1949 : :
1950 [ + + ]: 1462 : if (in_str == NULL && out_str == NULL)
1951 : : return TRUE;
1952 : :
1953 : : /* Device hasn't supplied an identity packet yet */
1954 : 990 : device_incoming = (const char **)device->incoming_capabilities;
1955 : 990 : device_outgoing = (const char **)device->outgoing_capabilities;
1956 : :
1957 [ + + ]: 990 : if (device_incoming == NULL || device_outgoing == NULL)
1958 : : return FALSE;
1959 : :
1960 : : /* Check if outgoing from plugin matches incoming from device */
1961 [ + - ]: 668 : if (out_str != NULL)
1962 : : {
1963 : 2777 : g_auto (GStrv) plugin_outgoing = NULL;
1964 : :
1965 : 668 : plugin_outgoing = g_strsplit(out_str, ";", -1);
1966 : :
1967 [ + + ]: 1320 : for (int i = 0; plugin_outgoing[i]; i++)
1968 : : {
1969 [ + + ]: 997 : if (g_strv_contains (device_incoming, plugin_outgoing[i]))
1970 [ + - ]: 345 : return TRUE;
1971 : : }
1972 : : }
1973 : :
1974 : : /* Check if incoming from plugin matches outgoing from device */
1975 [ - + ]: 323 : if (in_str != NULL)
1976 : : {
1977 : 2109 : g_auto (GStrv) plugin_incoming = NULL;
1978 : :
1979 : 323 : plugin_incoming = g_strsplit(in_str, ";", -1);
1980 : :
1981 [ + + ]: 916 : for (int i = 0; plugin_incoming[i]; i++)
1982 : : {
1983 [ + + ]: 611 : if (g_strv_contains (device_outgoing, plugin_incoming[i]))
1984 [ + - ]: 18 : return TRUE;
1985 : : }
1986 : : }
1987 : :
1988 : : return FALSE;
1989 : : }
1990 : :
|