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