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