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