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