LCOV - code coverage report
Current view: top level - src/libvalent/device - valent-device.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 91.2 % 646 589
Test Date: 2024-12-02 22:49:30 Functions: 96.4 % 56 54
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 66.5 % 400 266

             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   [ +  +  +  - ]:        6937 : 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                 :          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                 :         201 : device_plugin_free (gpointer data)
     195                 :             : {
     196                 :         201 :   ValentPlugin *plugin = data;
     197                 :             : 
     198                 :             :   /* `::action-removed` needs to be emitted before the plugin is freed */
     199         [ +  - ]:         201 :   if (plugin->extension != NULL)
     200                 :         201 :     valent_object_destroy (VALENT_OBJECT (plugin->extension));
     201                 :             : 
     202                 :         201 :   g_clear_pointer (&plugin, valent_plugin_free);
     203                 :         201 : }
     204                 :             : 
     205                 :             : static void
     206                 :         348 : on_plugin_action_added (GActionGroup *action_group,
     207                 :             :                         const char   *action_name,
     208                 :             :                         ValentPlugin *plugin)
     209                 :             : {
     210                 :         348 :   ValentDevice *self = VALENT_DEVICE (plugin->parent);
     211                 :         696 :   g_autofree char *full_name = NULL;
     212                 :         348 :   GAction *action;
     213                 :             : 
     214                 :         348 :   full_name = g_strdup_printf ("%s.%s",
     215                 :         348 :                                peas_plugin_info_get_module_name (plugin->info),
     216                 :             :                                action_name);
     217                 :         348 :   action = g_action_map_lookup_action (G_ACTION_MAP (action_group),
     218                 :             :                                        action_name);
     219                 :             : 
     220                 :         348 :   g_hash_table_replace (self->actions,
     221         [ -  + ]:         696 :                         g_strdup (full_name),
     222                 :             :                         g_object_ref (action));
     223                 :         348 :   g_action_group_action_added (G_ACTION_GROUP (plugin->parent), full_name);
     224                 :         348 : }
     225                 :             : 
     226                 :             : static void
     227                 :         598 : on_plugin_action_enabled_changed (GActionGroup *action_group,
     228                 :             :                                   const char   *action_name,
     229                 :             :                                   gboolean      enabled,
     230                 :             :                                   ValentPlugin *plugin)
     231                 :             : {
     232                 :        1196 :   g_autofree char *full_name = NULL;
     233                 :             : 
     234                 :         598 :   full_name = g_strdup_printf ("%s.%s",
     235                 :         598 :                                peas_plugin_info_get_module_name (plugin->info),
     236                 :             :                                action_name);
     237                 :         598 :   g_action_group_action_enabled_changed (G_ACTION_GROUP (plugin->parent),
     238                 :             :                                          full_name,
     239                 :             :                                          enabled);
     240                 :         598 : }
     241                 :             : 
     242                 :             : static void
     243                 :         334 : on_plugin_action_removed (GActionGroup *action_group,
     244                 :             :                           const char   *action_name,
     245                 :             :                           ValentPlugin *plugin)
     246                 :             : {
     247                 :         334 :   ValentDevice *self = VALENT_DEVICE (plugin->parent);
     248                 :         668 :   g_autofree char *full_name = NULL;
     249                 :             : 
     250                 :         334 :   full_name = g_strdup_printf ("%s.%s",
     251                 :         334 :                                peas_plugin_info_get_module_name (plugin->info),
     252                 :             :                                action_name);
     253                 :             : 
     254                 :         334 :   g_action_group_action_removed (G_ACTION_GROUP (plugin->parent), full_name);
     255                 :         334 :   g_hash_table_remove (self->actions, full_name);
     256                 :         334 : }
     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                 :         210 : valent_device_enable_plugin (ValentDevice *device,
     276                 :             :                              ValentPlugin *plugin)
     277                 :             : {
     278                 :         420 :   g_auto (GStrv) actions = NULL;
     279                 :         210 :   const char *incoming = NULL;
     280                 :             : 
     281         [ +  - ]:         210 :   g_assert (VALENT_IS_DEVICE (device));
     282         [ -  + ]:         210 :   g_assert (plugin != NULL);
     283                 :             : 
     284                 :             :   /* Instantiate the plugin */
     285                 :         210 :   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         [ -  + ]:         210 :   g_return_if_fail (G_IS_OBJECT (plugin->extension));
     292                 :             : 
     293                 :             :   /* Register packet handlers */
     294                 :         210 :   incoming = peas_plugin_info_get_external_data (plugin->info,
     295                 :             :                                                  "DevicePluginIncoming");
     296                 :             : 
     297         [ +  + ]:         210 :   if (incoming != NULL)
     298                 :             :     {
     299                 :         313 :       g_auto (GStrv) capabilities = NULL;
     300                 :             : 
     301                 :         103 :       capabilities = g_strsplit (incoming, ";", -1);
     302                 :             : 
     303         [ +  + ]:         296 :       for (unsigned int i = 0; capabilities[i] != NULL; i++)
     304                 :             :         {
     305                 :         193 :           GPtrArray *handlers = NULL;
     306                 :         193 :           const char *type = capabilities[i];
     307                 :             : 
     308         [ +  + ]:         193 :           if ((handlers = g_hash_table_lookup (device->handlers, type)) == NULL)
     309                 :             :             {
     310                 :         185 :               handlers = g_ptr_array_new ();
     311         [ -  + ]:         370 :               g_hash_table_insert (device->handlers, g_strdup (type), handlers);
     312                 :             :             }
     313                 :             : 
     314                 :         193 :           g_ptr_array_add (handlers, plugin->extension);
     315                 :             :         }
     316                 :             :     }
     317                 :             : 
     318                 :             :   /* Register plugin actions */
     319                 :         210 :   actions = g_action_group_list_actions (G_ACTION_GROUP (plugin->extension));
     320                 :             : 
     321         [ +  + ]:         558 :   for (unsigned int i = 0; actions[i] != NULL; i++)
     322                 :             :     {
     323                 :         348 :       on_plugin_action_added (G_ACTION_GROUP (plugin->extension),
     324                 :             :                               actions[i],
     325                 :             :                               plugin);
     326                 :             :     }
     327                 :             : 
     328                 :         210 :   g_signal_connect (plugin->extension,
     329                 :             :                     "action-added",
     330                 :             :                     G_CALLBACK (on_plugin_action_added),
     331                 :             :                     plugin);
     332                 :         210 :   g_signal_connect (plugin->extension,
     333                 :             :                     "action-enabled-changed",
     334                 :             :                     G_CALLBACK (on_plugin_action_enabled_changed),
     335                 :             :                     plugin);
     336                 :         210 :   g_signal_connect (plugin->extension,
     337                 :             :                     "action-removed",
     338                 :             :                     G_CALLBACK (on_plugin_action_removed),
     339                 :             :                     plugin);
     340                 :         210 :   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         [ +  - ]:         210 :   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                 :         208 : valent_device_reset_pair (gpointer object)
     418                 :             : {
     419                 :         208 :   ValentDevice *device = VALENT_DEVICE (object);
     420                 :         208 :   GApplication *application = g_application_get_default ();
     421                 :             : 
     422         [ +  - ]:         208 :   g_assert (VALENT_IS_DEVICE (device));
     423                 :             : 
     424         [ -  + ]:         208 :   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         [ +  + ]:         208 :   g_clear_handle_id (&device->incoming_pair, g_source_remove);
     433         [ +  + ]:         208 :   g_clear_handle_id (&device->outgoing_pair, g_source_remove);
     434                 :             : 
     435                 :         208 :   g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_STATE]);
     436                 :             : 
     437                 :         208 :   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                 :         179 : valent_device_handle_identity (ValentDevice *device,
     607                 :             :                                JsonNode     *packet)
     608                 :             : {
     609                 :         179 :   const char *device_id;
     610                 :         179 :   const char *device_name;
     611                 :         179 :   const char *device_type;
     612                 :             : 
     613                 :         179 :   VALENT_ENTRY;
     614                 :             : 
     615         [ +  - ]:         179 :   g_assert (VALENT_IS_DEVICE (device));
     616         [ -  + ]:         179 :   g_assert (VALENT_IS_PACKET (packet));
     617                 :             : 
     618                 :         179 :   valent_object_lock (VALENT_OBJECT (device));
     619                 :             : 
     620                 :             :   /* Device ID, which MUST exist and MUST match the construct-time value */
     621         [ +  - ]:         179 :   if (!valent_packet_get_string (packet, "deviceId", &device_id) ||
     622         [ -  + ]:         179 :       !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         [ -  + ]:         179 :   if (!valent_packet_get_string (packet, "deviceName", &device_name))
     633                 :           0 :     device_name = "Unnamed";
     634                 :             : 
     635         [ +  + ]:         179 :   if (g_set_str (&device->name, device_name))
     636                 :         101 :     g_object_notify_by_pspec (G_OBJECT (device), properties [PROP_NAME]);
     637                 :             : 
     638                 :             :   /* Device Type */
     639         [ -  + ]:         179 :   if (!valent_packet_get_string (packet, "deviceType", &device_type))
     640                 :           0 :     device_type = DEVICE_TYPE_DESKTOP;
     641                 :             : 
     642         [ +  + ]:         179 :   if (g_set_str (&device->type, device_type))
     643                 :             :     {
     644                 :         101 :       const char *device_icon = "computer-symbolic";
     645                 :             : 
     646         [ -  + ]:         101 :       if (g_str_equal (device_type, DEVICE_TYPE_DESKTOP))
     647                 :             :         device_icon = "computer-symbolic";
     648         [ +  - ]:         101 :       else if (g_str_equal (device_type, DEVICE_TYPE_LAPTOP))
     649                 :             :         device_icon = "laptop-symbolic";
     650         [ -  + ]:         101 :       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                 :         101 :         device_icon = "tv-symbolic";
     656                 :             : 
     657         [ +  - ]:         101 :       if (g_set_str (&device->icon_name, device_icon))
     658                 :         101 :         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         [ +  + ]:         179 :   g_clear_pointer (&device->incoming_capabilities, g_strfreev);
     664                 :         179 :   device->incoming_capabilities = valent_packet_dup_strv (packet,
     665                 :             :                                                           "incomingCapabilities");
     666                 :             : 
     667         [ +  + ]:         179 :   g_clear_pointer (&device->outgoing_capabilities, g_strfreev);
     668                 :         179 :   device->outgoing_capabilities = valent_packet_dup_strv (packet,
     669                 :             :                                                           "outgoingCapabilities");
     670                 :             : 
     671                 :         179 :   valent_object_unlock (VALENT_OBJECT (device));
     672                 :             : 
     673                 :             :   /* Recheck plugins and load or unload if capabilities have changed */
     674                 :         179 :   valent_device_reload_plugins (device);
     675                 :             : 
     676                 :         179 :   VALENT_EXIT;
     677                 :             : }
     678                 :             : 
     679                 :             : static void
     680                 :         124 : valent_device_handle_packet (ValentDevice *device,
     681                 :             :                              JsonNode     *packet)
     682                 :             : {
     683                 :         124 :   GPtrArray *handlers = NULL;
     684                 :         124 :   const char *type;
     685                 :             : 
     686         [ +  - ]:         124 :   g_assert (VALENT_IS_DEVICE (device));
     687         [ -  + ]:         124 :   g_assert (VALENT_IS_PACKET (packet));
     688                 :             : 
     689                 :         124 :   VALENT_JSON (packet, device->name);
     690                 :             : 
     691                 :         124 :   type = valent_packet_get_type (packet);
     692                 :             : 
     693         [ +  + ]:         124 :   if G_UNLIKELY (strcmp (type, "kdeconnect.pair") == 0)
     694                 :             :     {
     695                 :           5 :       valent_device_handle_pair (device, packet);
     696                 :             :     }
     697         [ +  + ]:         119 :   else if G_UNLIKELY (!device->paired)
     698                 :             :     {
     699                 :           1 :       valent_device_send_pair (device, FALSE);
     700                 :             :     }
     701         [ +  - ]:         118 :   else if ((handlers = g_hash_table_lookup (device->handlers, type)) != NULL)
     702                 :             :     {
     703         [ +  + ]:         236 :       for (unsigned int i = 0, len = handlers->len; i < len; i++)
     704                 :             :         {
     705                 :         118 :           ValentDevicePlugin *handler = g_ptr_array_index (handlers, i);
     706                 :             : 
     707                 :         118 :           valent_device_plugin_handle_packet (handler, type, packet);
     708                 :             :         }
     709                 :             :     }
     710                 :             :   else
     711                 :             :     {
     712                 :         124 :       VALENT_NOTE ("%s: Unsupported packet \"%s\"", device->name, type);
     713                 :             :     }
     714                 :         124 : }
     715                 :             : 
     716                 :             : /*
     717                 :             :  * ValentEngine callbacks
     718                 :             :  */
     719                 :             : static void
     720                 :         906 : on_load_plugin (PeasEngine     *engine,
     721                 :             :                 PeasPluginInfo *info,
     722                 :             :                 ValentDevice   *self)
     723                 :             : {
     724                 :         906 :   ValentPlugin *plugin;
     725                 :             : 
     726         [ +  - ]:         906 :   g_assert (PEAS_IS_ENGINE (engine));
     727         [ -  + ]:         906 :   g_assert (info != NULL);
     728         [ -  + ]:         906 :   g_assert (VALENT_IS_DEVICE (self));
     729                 :             : 
     730         [ +  + ]:         906 :   if (!valent_device_supports_plugin (self, info))
     731                 :             :     return;
     732                 :             : 
     733         [ +  + ]:         465 :   if (g_hash_table_contains (self->plugins, info))
     734                 :             :     return;
     735                 :             : 
     736                 :         208 :   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                 :         208 :   plugin = valent_plugin_new (self, self->context, info,
     742                 :             :                               G_CALLBACK (on_plugin_enabled_changed));
     743                 :         208 :   g_hash_table_insert (self->plugins, info, plugin);
     744                 :             : 
     745         [ +  - ]:         208 :   if (valent_plugin_get_enabled (plugin))
     746                 :         208 :     valent_device_enable_plugin (self, plugin);
     747                 :             : 
     748                 :         208 :   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PLUGINS]);
     749                 :             : }
     750                 :             : 
     751                 :             : static void
     752                 :         510 : on_unload_plugin (PeasEngine     *engine,
     753                 :             :                   PeasPluginInfo *info,
     754                 :             :                   ValentDevice   *device)
     755                 :             : {
     756         [ +  - ]:         510 :   g_assert (PEAS_IS_ENGINE (engine));
     757         [ -  + ]:         510 :   g_assert (info != NULL);
     758         [ -  + ]:         510 :   g_assert (VALENT_IS_DEVICE (device));
     759                 :             : 
     760         [ +  + ]:         510 :   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                 :         100 : valent_device_destroy (ValentObject *object)
     819                 :             : {
     820                 :         100 :   ValentDevice *self = VALENT_DEVICE (object);
     821                 :             : 
     822                 :             :   /* State */
     823                 :         100 :   valent_device_reset_pair (self);
     824                 :         100 :   valent_device_set_channel (self, NULL);
     825                 :             : 
     826                 :             :   /* Plugins */
     827                 :         100 :   g_signal_handlers_disconnect_by_data (self->engine, self);
     828                 :         100 :   g_hash_table_remove_all (self->plugins);
     829                 :         100 :   g_hash_table_remove_all (self->actions);
     830                 :         100 :   g_hash_table_remove_all (self->handlers);
     831                 :             : 
     832                 :         100 :   VALENT_OBJECT_CLASS (valent_device_parent_class)->destroy (object);
     833                 :         100 : }
     834                 :             : 
     835                 :             : /*
     836                 :             :  * GObject
     837                 :             :  */
     838                 :             : static void
     839                 :         104 : valent_device_constructed (GObject *object)
     840                 :             : {
     841                 :         104 :   ValentDevice *self = VALENT_DEVICE (object);
     842                 :         208 :   g_autofree char *path = NULL;
     843                 :         104 :   unsigned int n_plugins = 0;
     844                 :             : 
     845                 :             :   /* We must at least have a device ID */
     846         [ +  - ]:         104 :   g_assert (self->id != NULL);
     847                 :             : 
     848                 :             :   /* Context */
     849         [ +  + ]:         104 :   if (self->context == NULL)
     850                 :          98 :     self->context = valent_context_new (NULL, "device", self->id);
     851                 :             : 
     852                 :             :   /* GSettings*/
     853                 :         104 :   path = g_strdup_printf ("/ca/andyholmes/valent/device/%s/", self->id);
     854                 :         104 :   self->settings = g_settings_new_with_path ("ca.andyholmes.Valent.Device", path);
     855                 :         104 :   self->paired = g_settings_get_boolean (self->settings, "paired");
     856                 :             : 
     857                 :             :   /* Load plugins and watch for changes */
     858                 :         104 :   n_plugins = g_list_model_get_n_items (G_LIST_MODEL (self->engine));
     859                 :             : 
     860         [ +  + ]:         649 :   for (unsigned int i = 0; i < n_plugins; i++)
     861                 :             :     {
     862                 :         545 :       g_autoptr (PeasPluginInfo) info = NULL;
     863                 :             : 
     864                 :         545 :       info = g_list_model_get_item (G_LIST_MODEL (self->engine), i);
     865                 :             : 
     866         [ +  + ]:         545 :       if (peas_plugin_info_is_loaded (info))
     867                 :         544 :         on_load_plugin (self->engine, info, self);
     868                 :             :     }
     869                 :             : 
     870                 :         104 :   g_signal_connect_object (self->engine,
     871                 :             :                            "load-plugin",
     872                 :             :                            G_CALLBACK (on_load_plugin),
     873                 :             :                            self,
     874                 :             :                            G_CONNECT_AFTER);
     875                 :             : 
     876                 :         104 :   g_signal_connect_object (self->engine,
     877                 :             :                            "unload-plugin",
     878                 :             :                            G_CALLBACK (on_unload_plugin),
     879                 :             :                            self,
     880                 :             :                            0);
     881                 :             : 
     882                 :         104 :   G_OBJECT_CLASS (valent_device_parent_class)->constructed (object);
     883                 :         104 : }
     884                 :             : 
     885                 :             : static void
     886                 :         100 : valent_device_finalize (GObject *object)
     887                 :             : {
     888                 :         100 :   ValentDevice *self = VALENT_DEVICE (object);
     889                 :             : 
     890         [ +  - ]:         100 :   g_clear_object (&self->context);
     891         [ +  - ]:         100 :   g_clear_object (&self->settings);
     892                 :             : 
     893                 :             :   /* Properties */
     894         [ +  + ]:         100 :   g_clear_pointer (&self->icon_name, g_free);
     895         [ +  - ]:         100 :   g_clear_pointer (&self->id, g_free);
     896         [ +  + ]:         100 :   g_clear_pointer (&self->name, g_free);
     897         [ +  + ]:         100 :   g_clear_pointer (&self->type, g_free);
     898         [ +  + ]:         100 :   g_clear_pointer (&self->incoming_capabilities, g_strfreev);
     899         [ +  + ]:         100 :   g_clear_pointer (&self->outgoing_capabilities, g_strfreev);
     900                 :             : 
     901                 :             :   /* State */
     902         [ -  + ]:         100 :   g_clear_object (&self->channel);
     903                 :             : 
     904                 :             :   /* Plugins */
     905         [ +  - ]:         100 :   g_clear_pointer (&self->plugins, g_hash_table_unref);
     906         [ +  - ]:         100 :   g_clear_pointer (&self->actions, g_hash_table_unref);
     907         [ +  - ]:         100 :   g_clear_pointer (&self->handlers, g_hash_table_unref);
     908         [ +  - ]:         100 :   g_clear_object (&self->menu);
     909                 :             : 
     910                 :         100 :   G_OBJECT_CLASS (valent_device_parent_class)->finalize (object);
     911                 :         100 : }
     912                 :             : 
     913                 :             : static void
     914                 :          24 : valent_device_get_property (GObject    *object,
     915                 :             :                             guint       prop_id,
     916                 :             :                             GValue     *value,
     917                 :             :                             GParamSpec *pspec)
     918                 :             : {
     919                 :          24 :   ValentDevice *self = VALENT_DEVICE (object);
     920                 :             : 
     921   [ +  +  +  +  :          24 :   switch (prop_id)
                +  +  - ]
     922                 :             :     {
     923                 :           1 :     case PROP_CONTEXT:
     924                 :           1 :       g_value_set_object (value, self->context);
     925                 :           1 :       break;
     926                 :             : 
     927                 :           4 :     case PROP_ICON_NAME:
     928                 :           4 :       g_value_set_string (value, self->icon_name);
     929                 :           4 :       break;
     930                 :             : 
     931                 :           5 :     case PROP_ID:
     932                 :           5 :       g_value_set_string (value, self->id);
     933                 :           5 :       break;
     934                 :             : 
     935                 :          10 :     case PROP_NAME:
     936                 :          10 :       g_value_set_string (value, self->name);
     937                 :          10 :       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                 :          24 : }
     951                 :             : 
     952                 :             : static void
     953                 :         208 : valent_device_set_property (GObject      *object,
     954                 :             :                             guint         prop_id,
     955                 :             :                             const GValue *value,
     956                 :             :                             GParamSpec   *pspec)
     957                 :             : {
     958                 :         208 :   ValentDevice *self = VALENT_DEVICE (object);
     959                 :             : 
     960      [ +  +  - ]:         208 :   switch (prop_id)
     961                 :             :     {
     962                 :         104 :     case PROP_CONTEXT:
     963                 :         104 :       self->context = g_value_dup_object (value);
     964                 :         104 :       break;
     965                 :             : 
     966                 :         104 :     case PROP_ID:
     967                 :         104 :       self->id = g_value_dup_string (value);
     968                 :         104 :       break;
     969                 :             : 
     970                 :           0 :     default:
     971                 :           0 :       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     972                 :             :     }
     973                 :         208 : }
     974                 :             : 
     975                 :             : static void
     976                 :         104 : valent_device_init (ValentDevice *self)
     977                 :             : {
     978                 :         104 :   GSimpleAction *action = NULL;
     979                 :             : 
     980                 :             :   /* Plugins */
     981                 :         104 :   self->engine = valent_get_plugin_engine ();
     982                 :         104 :   self->plugins = g_hash_table_new_full (NULL, NULL, NULL, device_plugin_free);
     983                 :         104 :   self->handlers = g_hash_table_new_full (g_str_hash,
     984                 :             :                                           g_str_equal,
     985                 :             :                                           g_free,
     986                 :             :                                           (GDestroyNotify)g_ptr_array_unref);
     987                 :         104 :   self->actions = g_hash_table_new_full (g_str_hash,
     988                 :             :                                          g_str_equal,
     989                 :             :                                          g_free,
     990                 :             :                                          g_object_unref);
     991                 :         104 :   self->menu = g_menu_new ();
     992                 :             : 
     993                 :             :   /* Stock Actions */
     994                 :         104 :   action = g_simple_action_new ("pair", NULL);
     995                 :         104 :   g_signal_connect_object (action,
     996                 :             :                            "activate",
     997                 :             :                            G_CALLBACK (pair_action),
     998                 :             :                            self, 0);
     999                 :         104 :   g_hash_table_replace (self->actions,
    1000                 :         104 :                         g_strdup ("pair"),
    1001                 :             :                         g_steal_pointer (&action));
    1002                 :             : 
    1003                 :         104 :   action = g_simple_action_new ("unpair", NULL);
    1004                 :         104 :   g_signal_connect_object (action,
    1005                 :             :                            "activate",
    1006                 :             :                            G_CALLBACK (unpair_action),
    1007                 :             :                            self, 0);
    1008                 :         104 :   g_hash_table_replace (self->actions,
    1009                 :         104 :                         g_strdup ("unpair"),
    1010                 :             :                         g_steal_pointer (&action));
    1011                 :         104 : }
    1012                 :             : 
    1013                 :             : static void
    1014                 :          32 : valent_device_class_init (ValentDeviceClass *klass)
    1015                 :             : {
    1016                 :          32 :   GObjectClass *object_class = G_OBJECT_CLASS (klass);
    1017                 :          32 :   ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
    1018                 :             : 
    1019                 :          32 :   object_class->constructed = valent_device_constructed;
    1020                 :          32 :   object_class->finalize = valent_device_finalize;
    1021                 :          32 :   object_class->get_property = valent_device_get_property;
    1022                 :          32 :   object_class->set_property = valent_device_set_property;
    1023                 :             : 
    1024                 :          32 :   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                 :          64 :   properties [PROP_CONTEXT] =
    1034                 :          32 :     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                 :          64 :   properties [PROP_ICON_NAME] =
    1049                 :          32 :     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                 :          64 :   properties [PROP_ID] =
    1067                 :          32 :     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                 :          64 :   properties [PROP_NAME] =
    1082                 :          32 :     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                 :          64 :   properties [PROP_PLUGINS] =
    1096                 :          32 :     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                 :          64 :   properties [PROP_STATE] =
    1110                 :          32 :     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                 :          32 :   g_object_class_install_properties (object_class, N_PROPERTIES, properties);
    1118                 :          32 : }
    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                 :         101 : valent_device_new_full (JsonNode      *identity,
    1151                 :             :                         ValentContext *context)
    1152                 :             : {
    1153                 :         101 :   ValentDevice *ret;
    1154                 :         101 :   const char *id;
    1155                 :             : 
    1156         [ +  - ]:         101 :   g_return_val_if_fail (VALENT_IS_PACKET (identity), NULL);
    1157                 :             : 
    1158         [ -  + ]:         101 :   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                 :         101 :   ret = g_object_new (VALENT_TYPE_DEVICE,
    1165                 :             :                       "id",      id,
    1166                 :             :                       "context", context,
    1167                 :             :                       NULL);
    1168                 :         101 :   valent_device_handle_identity (ret, identity);
    1169                 :             : 
    1170                 :         101 :   return ret;
    1171                 :             : }
    1172                 :             : 
    1173                 :             : static void
    1174                 :         143 : valent_device_send_packet_cb (ValentChannel *channel,
    1175                 :             :                               GAsyncResult  *result,
    1176                 :             :                               gpointer       user_data)
    1177                 :             : {
    1178                 :         143 :   g_autoptr (GTask) task = G_TASK (user_data);
    1179                 :         143 :   ValentDevice *device = g_task_get_source_object (task);
    1180   [ +  -  +  - ]:         143 :   g_autoptr (GError) error = NULL;
    1181                 :             : 
    1182         [ +  - ]:         143 :   g_assert (VALENT_IS_DEVICE (device));
    1183                 :             : 
    1184         [ +  + ]:         143 :   if (valent_channel_write_packet_finish (channel, result, &error))
    1185         [ -  + ]:         137 :     return g_task_return_boolean (task, TRUE);
    1186                 :             : 
    1187                 :           6 :   VALENT_NOTE ("%s: %s", device->name, error->message);
    1188                 :           6 :   g_task_return_error (task, g_steal_pointer (&error));
    1189                 :             : 
    1190                 :           6 :   valent_object_lock (VALENT_OBJECT (device));
    1191         [ +  + ]:           6 :   if (device->channel == channel)
    1192                 :           2 :     valent_device_set_channel (device, NULL);
    1193         [ -  + ]:           6 :   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                 :         145 : valent_device_send_packet (ValentDevice        *device,
    1216                 :             :                            JsonNode            *packet,
    1217                 :             :                            GCancellable        *cancellable,
    1218                 :             :                            GAsyncReadyCallback  callback,
    1219                 :             :                            gpointer             user_data)
    1220                 :             : {
    1221                 :         143 :   g_autoptr (GTask) task = NULL;
    1222                 :             : 
    1223         [ +  - ]:         145 :   g_return_if_fail (VALENT_IS_DEVICE (device));
    1224         [ -  + ]:         145 :   g_return_if_fail (VALENT_IS_PACKET (packet));
    1225                 :             : 
    1226                 :         145 :   valent_object_lock (VALENT_OBJECT (device));
    1227                 :             : 
    1228         [ +  + ]:         145 :   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         [ +  + ]:         144 :   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                 :         143 :   task = g_task_new (device, cancellable, callback, user_data);
    1253         [ +  - ]:         143 :   g_task_set_source_tag (task, valent_device_send_packet);
    1254                 :             : 
    1255                 :         143 :   VALENT_JSON (packet, device->name);
    1256                 :         143 :   valent_channel_write_packet (device->channel,
    1257                 :             :                                packet,
    1258                 :             :                                cancellable,
    1259                 :             :                                (GAsyncReadyCallback)valent_device_send_packet_cb,
    1260                 :             :                                g_steal_pointer (&task));
    1261                 :             : 
    1262                 :         143 :   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                 :         125 : valent_device_send_packet_finish (ValentDevice  *device,
    1279                 :             :                                   GAsyncResult  *result,
    1280                 :             :                                   GError       **error)
    1281                 :             : {
    1282         [ +  - ]:         125 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), FALSE);
    1283         [ -  + ]:         125 :   g_return_val_if_fail (g_task_is_valid (result, device), FALSE);
    1284   [ +  -  -  + ]:         125 :   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
    1285                 :             : 
    1286                 :         125 :   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                 :         201 : read_packet_cb (ValentChannel *channel,
    1316                 :             :                 GAsyncResult  *result,
    1317                 :             :                 ValentDevice  *device)
    1318                 :             : {
    1319                 :         402 :   g_autoptr (GError) error = NULL;
    1320         [ +  + ]:         201 :   g_autoptr (JsonNode) packet = NULL;
    1321                 :             : 
    1322         [ +  - ]:         201 :   g_assert (VALENT_IS_CHANNEL (channel));
    1323         [ -  + ]:         201 :   g_assert (VALENT_IS_DEVICE (device));
    1324                 :             : 
    1325                 :         201 :   packet = valent_channel_read_packet_finish (channel, result, &error);
    1326                 :             : 
    1327                 :             :   /* On success, queue another read before handling the packet */
    1328         [ +  + ]:         201 :   if (packet != NULL)
    1329                 :             :     {
    1330                 :         124 :       valent_channel_read_packet (channel,
    1331                 :             :                                   NULL,
    1332                 :             :                                   (GAsyncReadyCallback)read_packet_cb,
    1333                 :             :                                   g_object_ref (device));
    1334                 :             : 
    1335                 :         124 :       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                 :          77 :       VALENT_NOTE ("%s: %s", device->name, error->message);
    1342                 :             : 
    1343                 :          77 :       valent_object_lock (VALENT_OBJECT (device));
    1344         [ +  + ]:          77 :       if (device->channel == channel)
    1345                 :          72 :         valent_device_set_channel (device, NULL);
    1346                 :          77 :       valent_object_unlock (VALENT_OBJECT (device));
    1347                 :             :     }
    1348                 :             : 
    1349         [ +  + ]:         201 :   g_object_unref (device);
    1350                 :         201 : }
    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                 :         257 : valent_device_set_channel (ValentDevice  *device,
    1363                 :             :                            ValentChannel *channel)
    1364                 :             : {
    1365                 :         257 :   gboolean was_connected;
    1366                 :         257 :   gboolean is_connected;
    1367                 :             : 
    1368         [ +  - ]:         257 :   g_return_if_fail (VALENT_IS_DEVICE (device));
    1369   [ +  +  -  + ]:         257 :   g_return_if_fail (channel == NULL || VALENT_IS_CHANNEL (channel));
    1370                 :             : 
    1371                 :         257 :   valent_object_lock (VALENT_OBJECT (device));
    1372                 :             : 
    1373         [ +  + ]:         257 :   if (device->channel == channel)
    1374                 :             :     {
    1375                 :         102 :       valent_object_unlock (VALENT_OBJECT (device));
    1376                 :         102 :       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         [ +  + ]:         155 :   if ((was_connected = (device->channel != NULL)))
    1382                 :             :     {
    1383                 :          77 :       valent_channel_close_async (device->channel, NULL, NULL, NULL);
    1384         [ +  - ]:          77 :       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         [ +  + ]:         155 :   if ((is_connected = g_set_object (&device->channel, channel)))
    1390                 :             :     {
    1391                 :          78 :       JsonNode *peer_identity;
    1392                 :             : 
    1393                 :             :       /* Handle the peer identity packet */
    1394                 :          78 :       peer_identity = valent_channel_get_peer_identity (channel);
    1395                 :          78 :       valent_device_handle_identity (device, peer_identity);
    1396                 :             : 
    1397                 :             :       /* Start receiving packets */
    1398                 :          78 :       valent_channel_read_packet (channel,
    1399                 :             :                                   NULL,
    1400                 :             :                                   (GAsyncReadyCallback)read_packet_cb,
    1401                 :             :                                   g_object_ref (device));
    1402                 :             :     }
    1403                 :             : 
    1404                 :         155 :   valent_object_unlock (VALENT_OBJECT (device));
    1405                 :             : 
    1406                 :             :   /* If the state changed, update the plugins and notify */
    1407         [ +  - ]:         155 :   if (is_connected == was_connected)
    1408                 :             :     return;
    1409                 :             : 
    1410                 :         155 :   valent_device_update_plugins (device);
    1411                 :         155 :   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                 :         109 : valent_device_get_context (ValentDevice *device)
    1426                 :             : {
    1427         [ +  - ]:         109 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
    1428                 :             : 
    1429                 :         109 :   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                 :          65 : valent_device_get_id (ValentDevice *device)
    1462                 :             : {
    1463         [ +  - ]:          65 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
    1464         [ -  + ]:          65 :   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                 :         105 : valent_device_set_paired (ValentDevice *device,
    1520                 :             :                           gboolean      paired)
    1521                 :             : {
    1522         [ +  - ]:         105 :   g_assert (VALENT_IS_DEVICE (device));
    1523                 :             : 
    1524                 :         105 :   valent_object_lock (VALENT_OBJECT (device));
    1525                 :             : 
    1526                 :             :   /* If nothing's changed, only reset pending pair timeouts */
    1527         [ +  + ]:         105 :   if (device->paired == paired)
    1528                 :             :     {
    1529                 :          68 :       valent_device_reset_pair (device);
    1530                 :          68 :       valent_object_unlock (VALENT_OBJECT (device));
    1531                 :          68 :       return;
    1532                 :             :     }
    1533                 :             : 
    1534                 :             :   /* FIXME: If we're connected store/clear connection data */
    1535   [ +  +  +  + ]:          37 :   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                 :          37 :   device->paired = paired;
    1541                 :          37 :   g_settings_set_boolean (device->settings, "paired", device->paired);
    1542                 :             : 
    1543                 :          37 :   valent_object_unlock (VALENT_OBJECT (device));
    1544                 :             : 
    1545                 :             :   /* Update plugins and notify */
    1546                 :          37 :   valent_device_update_plugins (device);
    1547                 :          37 :   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                 :          99 : valent_device_get_plugins (ValentDevice *device)
    1562                 :             : {
    1563                 :         198 :   g_autoptr (GStrvBuilder) builder = NULL;
    1564                 :          99 :   GHashTableIter iter;
    1565                 :          99 :   PeasPluginInfo *info;
    1566                 :             : 
    1567         [ +  - ]:          99 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), NULL);
    1568                 :             : 
    1569                 :          99 :   builder = g_strv_builder_new ();
    1570                 :          99 :   g_hash_table_iter_init (&iter, device->plugins);
    1571                 :             : 
    1572         [ +  + ]:         289 :   while (g_hash_table_iter_next (&iter, (void **)&info, NULL))
    1573                 :         190 :     g_strv_builder_add (builder, peas_plugin_info_get_module_name (info));
    1574                 :             : 
    1575                 :          99 :   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                 :         486 : valent_device_get_state (ValentDevice *device)
    1590                 :             : {
    1591                 :         486 :   ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
    1592                 :             : 
    1593         [ +  - ]:         486 :   g_return_val_if_fail (VALENT_IS_DEVICE (device), state);
    1594                 :             : 
    1595                 :         486 :   valent_object_lock (VALENT_OBJECT (device));
    1596                 :             : 
    1597         [ +  + ]:         486 :   if (device->channel != NULL)
    1598                 :         126 :     state |= VALENT_DEVICE_STATE_CONNECTED;
    1599                 :             : 
    1600         [ +  + ]:         486 :   if (device->paired)
    1601                 :         356 :     state |= VALENT_DEVICE_STATE_PAIRED;
    1602                 :             : 
    1603         [ +  + ]:         486 :   if (device->incoming_pair > 0)
    1604                 :           3 :     state |= VALENT_DEVICE_STATE_PAIR_INCOMING;
    1605                 :             : 
    1606         [ +  + ]:         486 :   if (device->outgoing_pair > 0)
    1607                 :           2 :     state |= VALENT_DEVICE_STATE_PAIR_OUTGOING;
    1608                 :             : 
    1609                 :         486 :   valent_object_unlock (VALENT_OBJECT (device));
    1610                 :             : 
    1611                 :         486 :   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                 :         179 : valent_device_reload_plugins (ValentDevice *device)
    1713                 :             : {
    1714                 :         179 :   unsigned int n_plugins = 0;
    1715                 :             : 
    1716         [ +  - ]:         179 :   g_assert (VALENT_IS_DEVICE (device));
    1717                 :             : 
    1718                 :         179 :   n_plugins = g_list_model_get_n_items (G_LIST_MODEL (device->engine));
    1719                 :             : 
    1720         [ +  + ]:        1038 :   for (unsigned int i = 0; i < n_plugins; i++)
    1721                 :             :     {
    1722                 :         859 :       g_autoptr (PeasPluginInfo) info = NULL;
    1723                 :             : 
    1724                 :         859 :       info = g_list_model_get_item (G_LIST_MODEL (device->engine), i);
    1725                 :             : 
    1726         [ +  + ]:         859 :       if (valent_device_supports_plugin (device, info))
    1727                 :         356 :         on_load_plugin (device->engine, info, device);
    1728                 :             :       else
    1729                 :         503 :         on_unload_plugin (device->engine, info, device);
    1730                 :             :     }
    1731                 :         179 : }
    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                 :         192 : valent_device_update_plugins (ValentDevice *device)
    1743                 :             : {
    1744                 :         192 :   ValentDeviceState state = VALENT_DEVICE_STATE_NONE;
    1745                 :         192 :   GHashTableIter iter;
    1746                 :         192 :   ValentPlugin *plugin;
    1747                 :             : 
    1748         [ +  - ]:         192 :   g_assert (VALENT_IS_DEVICE (device));
    1749                 :             : 
    1750                 :         192 :   state = valent_device_get_state (device);
    1751                 :             : 
    1752                 :         192 :   g_hash_table_iter_init (&iter, device->plugins);
    1753                 :             : 
    1754         [ +  + ]:         574 :   while (g_hash_table_iter_next (&iter, NULL, (void **)&plugin))
    1755                 :             :     {
    1756         [ -  + ]:         382 :       if (plugin->extension == NULL)
    1757                 :           0 :         continue;
    1758                 :             : 
    1759                 :         382 :       valent_device_plugin_update_state (VALENT_DEVICE_PLUGIN (plugin->extension),
    1760                 :             :                                          state);
    1761                 :             :     }
    1762                 :         192 : }
    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                 :        1765 : valent_device_supports_plugin (ValentDevice   *device,
    1775                 :             :                                PeasPluginInfo *info)
    1776                 :             : {
    1777                 :        1765 :   const char **device_incoming;
    1778                 :        1765 :   const char **device_outgoing;
    1779                 :        1765 :   const char *in_str, *out_str;
    1780                 :             : 
    1781         [ +  - ]:        1765 :   g_assert (VALENT_IS_DEVICE (device));
    1782         [ -  + ]:        1765 :   g_assert (info != NULL);
    1783                 :             : 
    1784         [ +  + ]:        1765 :   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                 :        1446 :   in_str = peas_plugin_info_get_external_data (info, "DevicePluginIncoming");
    1791                 :        1446 :   out_str = peas_plugin_info_get_external_data (info, "DevicePluginOutgoing");
    1792                 :             : 
    1793         [ +  + ]:        1446 :   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         [ +  - ]:         662 :   if (out_str != NULL)
    1805                 :             :     {
    1806                 :        2750 :       g_auto (GStrv) plugin_outgoing = NULL;
    1807                 :             : 
    1808                 :         662 :       plugin_outgoing = g_strsplit(out_str, ";", -1);
    1809                 :             : 
    1810         [ +  + ]:        1314 :       for (int i = 0; plugin_outgoing[i]; i++)
    1811                 :             :         {
    1812         [ +  + ]:         991 :           if (g_strv_contains (device_incoming, plugin_outgoing[i]))
    1813         [ +  - ]:         339 :             return TRUE;
    1814                 :             :         }
    1815                 :             :     }
    1816                 :             : 
    1817                 :             :   /* Check if incoming from plugin matches outgoing from device */
    1818         [ -  + ]:         323 :   if (in_str != NULL)
    1819                 :             :     {
    1820                 :        2088 :       g_auto (GStrv) plugin_incoming = NULL;
    1821                 :             : 
    1822                 :         323 :       plugin_incoming = g_strsplit(in_str, ";", -1);
    1823                 :             : 
    1824         [ +  + ]:         916 :       for (int i = 0; plugin_incoming[i]; i++)
    1825                 :             :         {
    1826         [ +  + ]:         611 :           if (g_strv_contains (device_outgoing, plugin_incoming[i]))
    1827         [ +  - ]:          18 :             return TRUE;
    1828                 :             :         }
    1829                 :             :     }
    1830                 :             : 
    1831                 :             :   return FALSE;
    1832                 :             : }
    1833                 :             : 
        

Generated by: LCOV version 2.0-1