LCOV - code coverage report
Current view: top level - src/libvalent/device - valent-device-plugin.c (source / functions) Coverage Total Hit
Test: Code Coverage Lines: 84.6 % 117 99
Test Date: 2024-12-06 20:21:07 Functions: 93.3 % 15 14
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 46.2 % 158 73

             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-plugin"
       5                 :             : 
       6                 :             : #include "config.h"
       7                 :             : 
       8                 :             : #include <gio/gio.h>
       9                 :             : #include <json-glib/json-glib.h>
      10                 :             : #include <libpeas.h>
      11                 :             : #include <libvalent-core.h>
      12                 :             : 
      13                 :             : #include "valent-device.h"
      14                 :             : #include "valent-device-plugin.h"
      15                 :             : #include "valent-packet.h"
      16                 :             : 
      17                 :             : /**
      18                 :             :  * ValentDevicePlugin:
      19                 :             :  *
      20                 :             :  * An abstract base class for device plugins.
      21                 :             :  *
      22                 :             :  * `ValentDevicePlugin` is a base class for plugins that operate in the scope of
      23                 :             :  * a single device. This usually means communicating with other devices, however
      24                 :             :  * plugins aren't required to be packet based.
      25                 :             :  *
      26                 :             :  * ## Plugin Requirements
      27                 :             :  *
      28                 :             :  * Device plugins essentially have two sets of dependent conditions for being
      29                 :             :  * enabled. Plugins become available (i.e. can be enabled) when any of the
      30                 :             :  * following are true:
      31                 :             :  *
      32                 :             :  * - any of the device's outgoing capabilities match any of the plugin's
      33                 :             :  *   incoming capabilities
      34                 :             :  * - any of the device's incoming capabilities match any of the plugin's
      35                 :             :  *   outgoing capabilities
      36                 :             :  * - the plugin doesn't list any capabilities (eg. a non-packet based plugin)
      37                 :             :  *
      38                 :             :  * When a plugin becomes available it may be enabled, disabled and configured.
      39                 :             :  *
      40                 :             :  * ## Plugin Actions
      41                 :             :  *
      42                 :             :  * `ValentDevicePlugin` implements the [iface@Gio.ActionGroup] and
      43                 :             :  * [iface@Gio.ActionMap] interfaces, providing a simple way for plugins to
      44                 :             :  * expose functions and states. Each [iface@Gio.Action] added to the action map
      45                 :             :  * will be included in the device action group with the plugin's module name as
      46                 :             :  * a prefix (eg. `share.uri`).
      47                 :             :  *
      48                 :             :  * If the [class@Valent.DeviceManager] is exported on D-Bus, the actions will be
      49                 :             :  * exported along with the [class@Valent.Device].
      50                 :             :  *
      51                 :             :  * ## Implementation Notes
      52                 :             :  *
      53                 :             :  * Implementations that define `X-DevicePluginIncoming` in the `.plugin` file
      54                 :             :  * must override [vfunc@Valent.DevicePlugin.handle_packet] to handle incoming
      55                 :             :  * packets. Implementations that depend on the device state, especially those
      56                 :             :  * that define `X-DevicePluginOutgoing` in the `.plugin` file, should override
      57                 :             :  * [vfunc@Valent.DevicePlugin.update_state].
      58                 :             :  *
      59                 :             :  * ## `.plugin` File
      60                 :             :  *
      61                 :             :  * Implementations may define the following extra fields in the `.plugin` file:
      62                 :             :  *
      63                 :             :  * - `X-DevicePluginIncoming`
      64                 :             :  *
      65                 :             :  *     A list of packet types (eg. `kdeconnect.ping`) separated by semi-colons
      66                 :             :  *     indicating the packets that the plugin can handle.
      67                 :             :  *
      68                 :             :  * - `X-DevicePluginOutgoing`
      69                 :             :  *
      70                 :             :  *     A list of packet types (eg. `kdeconnect.share.request`) separated by
      71                 :             :  *     semi-colons indicating the packets that the plugin may send.
      72                 :             :  *
      73                 :             :  * - `X-DevicePluginSettings`
      74                 :             :  *
      75                 :             :  *     A [class@Gio.Settings] schema ID for the plugin's settings. See
      76                 :             :  *     [method@Valent.Context.get_plugin_settings] for more information.
      77                 :             :  *
      78                 :             :  * Since: 1.0
      79                 :             :  */
      80                 :             : 
      81                 :             : typedef struct
      82                 :             : {
      83                 :             :   gpointer  reserved[1];
      84                 :             : } ValentDevicePluginPrivate;
      85                 :             : 
      86   [ +  +  +  - ]:        4478 : G_DEFINE_ABSTRACT_TYPE (ValentDevicePlugin, valent_device_plugin, VALENT_TYPE_EXTENSION)
      87                 :             : 
      88                 :             : /* LCOV_EXCL_START */
      89                 :             : static void
      90                 :             : valent_device_plugin_real_handle_packet (ValentDevicePlugin *plugin,
      91                 :             :                                          const char         *type,
      92                 :             :                                          JsonNode           *packet)
      93                 :             : {
      94                 :             :   g_assert (VALENT_IS_DEVICE_PLUGIN (plugin));
      95                 :             :   g_assert (type != NULL && *type != '\0');
      96                 :             :   g_assert (VALENT_IS_PACKET (packet));
      97                 :             : 
      98                 :             :   g_critical ("%s: expected handler for \"%s\" packet",
      99                 :             :               G_OBJECT_TYPE_NAME (plugin),
     100                 :             :               type);
     101                 :             : }
     102                 :             : 
     103                 :             : static void
     104                 :             : valent_device_plugin_real_update_state (ValentDevicePlugin *plugin,
     105                 :             :                                         ValentDeviceState   state)
     106                 :             : {
     107                 :             :   g_assert (VALENT_IS_DEVICE_PLUGIN (plugin));
     108                 :             : }
     109                 :             : /* LCOV_EXCL_STOP */
     110                 :             : 
     111                 :             : static void
     112                 :         105 : valent_device_send_packet_cb (ValentDevice *device,
     113                 :             :                               GAsyncResult *result,
     114                 :             :                               gpointer      user_data)
     115                 :             : {
     116                 :         210 :   g_autoptr (GError) error = NULL;
     117                 :             : 
     118         [ +  + ]:         105 :   if (!valent_device_send_packet_finish (device, result, &error))
     119                 :             :     {
     120         [ -  + ]:           5 :       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
     121                 :           0 :         g_critical ("%s(): %s", G_STRFUNC, error->message);
     122         [ -  + ]:           5 :       else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_CONNECTED))
     123                 :           0 :         g_warning ("%s(): %s", G_STRFUNC, error->message);
     124         [ +  - ]:           5 :       else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
     125                 :           5 :         g_debug ("%s(): %s", G_STRFUNC, error->message);
     126                 :             :     }
     127                 :         105 : }
     128                 :             : 
     129                 :             : /*
     130                 :             :  * GObject
     131                 :             :  */
     132                 :             : static void
     133                 :          58 : valent_device_plugin_class_init (ValentDevicePluginClass *klass)
     134                 :             : {
     135                 :          58 :   klass->handle_packet = valent_device_plugin_real_handle_packet;
     136                 :          58 :   klass->update_state = valent_device_plugin_real_update_state;
     137                 :             : }
     138                 :             : 
     139                 :             : static void
     140                 :         212 : valent_device_plugin_init (ValentDevicePlugin *self)
     141                 :             : {
     142                 :         212 : }
     143                 :             : 
     144                 :             : /**
     145                 :             :  * valent_device_plugin_queue_packet:
     146                 :             :  * @plugin: a `ValentDevicePlugin`
     147                 :             :  * @packet: a KDE Connect packet
     148                 :             :  *
     149                 :             :  * Queue a KDE Connect packet to be sent to the device this plugin is bound to.
     150                 :             :  *
     151                 :             :  * For notification of success, you may call [method@Valent.Resource.get_source]
     152                 :             :  * and then [method@Valent.Device.send_packet], but note that there can be no
     153                 :             :  * guarantee the remote device has received the packet.
     154                 :             :  *
     155                 :             :  * Since: 1.0
     156                 :             :  */
     157                 :             : void
     158                 :         105 : valent_device_plugin_queue_packet (ValentDevicePlugin *plugin,
     159                 :             :                                    JsonNode           *packet)
     160                 :             : {
     161                 :         105 :   ValentDevice *device = NULL;
     162                 :         210 :   g_autoptr (GCancellable) destroy = NULL;
     163                 :             : 
     164         [ +  - ]:         105 :   g_return_if_fail (VALENT_IS_DEVICE_PLUGIN (plugin));
     165         [ -  + ]:         105 :   g_return_if_fail (VALENT_IS_PACKET (packet));
     166                 :             : 
     167         [ +  - ]:         105 :   if ((device = valent_resource_get_source (VALENT_RESOURCE (plugin))) == NULL)
     168                 :             :     return;
     169                 :             : 
     170                 :         105 :   destroy = valent_object_ref_cancellable (VALENT_OBJECT (plugin));
     171         [ +  - ]:         105 :   valent_device_send_packet (device,
     172                 :             :                              packet,
     173                 :             :                              destroy,
     174                 :             :                              (GAsyncReadyCallback)valent_device_send_packet_cb,
     175                 :             :                              NULL);
     176                 :             : }
     177                 :             : 
     178                 :             : /**
     179                 :             :  * valent_device_plugin_show_notification:
     180                 :             :  * @plugin: a `ValentDevicePlugin`
     181                 :             :  * @id: an id for the notification
     182                 :             :  * @notification: a `GNotification`
     183                 :             :  *
     184                 :             :  * A convenience for showing a local notification.
     185                 :             :  *
     186                 :             :  * @id will be automatically prepended with the device ID and plugin module to
     187                 :             :  * prevent conflicting with other devices and plugins.
     188                 :             :  *
     189                 :             :  * Call [method@Valent.DevicePlugin.hide_notification] to make the same
     190                 :             :  * transformation on @id and withdraw the notification.
     191                 :             :  *
     192                 :             :  * Since: 1.0
     193                 :             :  */
     194                 :             : void
     195                 :          39 : valent_device_plugin_show_notification (ValentDevicePlugin *plugin,
     196                 :             :                                         const char         *id,
     197                 :             :                                         GNotification      *notification)
     198                 :             : {
     199                 :          39 :   GApplication *application = g_application_get_default ();
     200                 :          39 :   g_autoptr (ValentDevice) device = NULL;
     201   [ -  -  -  + ]:          39 :   g_autoptr (PeasPluginInfo) plugin_info = NULL;
     202   [ -  -  -  + ]:          39 :   g_autofree char *notification_id = NULL;
     203                 :             : 
     204         [ +  - ]:          39 :   g_return_if_fail (VALENT_IS_DEVICE_PLUGIN (plugin));
     205         [ -  + ]:          39 :   g_return_if_fail (id != NULL);
     206   [ +  -  +  -  :          39 :   g_return_if_fail (G_IS_NOTIFICATION (notification));
             -  +  -  - ]
     207                 :             : 
     208         [ -  + ]:          39 :   if G_UNLIKELY (application == NULL)
     209                 :             :     return;
     210                 :             : 
     211                 :           0 :   g_object_get (plugin,
     212                 :             :                 "plugin-info", &plugin_info,
     213                 :             :                 "source",      &device,
     214                 :             :                 NULL);
     215                 :           0 :   notification_id = g_strdup_printf ("%s::%s::%s",
     216                 :             :                                      valent_device_get_id (device),
     217                 :             :                                      peas_plugin_info_get_module_name (plugin_info),
     218                 :             :                                      id);
     219                 :           0 :   g_application_send_notification (application, notification_id, notification);
     220                 :             : }
     221                 :             : 
     222                 :             : /**
     223                 :             :  * valent_device_plugin_hide_notification:
     224                 :             :  * @plugin: a `ValentDevicePlugin`
     225                 :             :  * @id: an id for the notification
     226                 :             :  *
     227                 :             :  * A convenience for withdrawing a notification.
     228                 :             :  *
     229                 :             :  * This method will withdraw a notification shown with
     230                 :             :  * [method@Valent.DevicePlugin.show_notification].
     231                 :             :  *
     232                 :             :  * Since: 1.0
     233                 :             :  */
     234                 :             : void
     235                 :          17 : valent_device_plugin_hide_notification (ValentDevicePlugin *plugin,
     236                 :             :                                         const char         *id)
     237                 :             : {
     238                 :          17 :   GApplication *application = g_application_get_default ();
     239                 :          17 :   g_autoptr (ValentDevice) device = NULL;
     240   [ -  -  -  + ]:          17 :   g_autoptr (PeasPluginInfo) plugin_info = NULL;
     241   [ -  -  -  + ]:          17 :   g_autofree char *notification_id = NULL;
     242                 :             : 
     243         [ +  - ]:          17 :   g_return_if_fail (VALENT_IS_DEVICE_PLUGIN (plugin));
     244         [ -  + ]:          17 :   g_return_if_fail (id != NULL);
     245                 :             : 
     246         [ -  + ]:          17 :   if G_UNLIKELY (application == NULL)
     247                 :             :     return;
     248                 :             : 
     249                 :           0 :   g_object_get (plugin,
     250                 :             :                 "plugin-info", &plugin_info,
     251                 :             :                 "source",      &device,
     252                 :             :                 NULL);
     253                 :           0 :   notification_id = g_strdup_printf ("%s::%s::%s",
     254                 :             :                                      valent_device_get_id (device),
     255                 :             :                                      peas_plugin_info_get_module_name (plugin_info),
     256                 :             :                                      id);
     257                 :           0 :   g_application_withdraw_notification (application, notification_id);
     258                 :             : }
     259                 :             : 
     260                 :             : /**
     261                 :             :  * valent_device_plugin_handle_packet: (virtual handle_packet)
     262                 :             :  * @plugin: a `ValentDevicePlugin`
     263                 :             :  * @type: a KDE Connect packet type
     264                 :             :  * @packet: a KDE Connect packet
     265                 :             :  *
     266                 :             :  * Handle a packet from the device the plugin is bound to.
     267                 :             :  *
     268                 :             :  * This is called when the device receives a packet type included in the
     269                 :             :  * `X-DevicePluginIncoming` field of the `.plugin` file.
     270                 :             :  *
     271                 :             :  * This is optional for implementations which do not register any incoming
     272                 :             :  * capabilities, such as plugins that do not provide packet-based functionality.
     273                 :             :  *
     274                 :             :  * Since: 1.0
     275                 :             :  */
     276                 :             : void
     277                 :         117 : valent_device_plugin_handle_packet (ValentDevicePlugin *plugin,
     278                 :             :                                     const char         *type,
     279                 :             :                                     JsonNode           *packet)
     280                 :             : {
     281                 :         117 :   VALENT_ENTRY;
     282                 :             : 
     283         [ +  - ]:         117 :   g_return_if_fail (VALENT_IS_DEVICE_PLUGIN (plugin));
     284   [ +  -  -  + ]:         117 :   g_return_if_fail (type != NULL && *type != '\0');
     285         [ -  + ]:         117 :   g_return_if_fail (VALENT_IS_PACKET (packet));
     286                 :             : 
     287                 :         117 :   VALENT_DEVICE_PLUGIN_GET_CLASS (plugin)->handle_packet (plugin, type, packet);
     288                 :             : 
     289                 :         117 :   VALENT_EXIT;
     290                 :             : }
     291                 :             : 
     292                 :             : /**
     293                 :             :  * valent_device_plugin_update_state: (virtual update_state)
     294                 :             :  * @plugin: a `ValentDevicePlugin`
     295                 :             :  * @state: a `ValentDeviceState`
     296                 :             :  *
     297                 :             :  * Update the plugin based on the new state of the device.
     298                 :             :  *
     299                 :             :  * This function is called when the connected or paired state of the device
     300                 :             :  * changes. This may be used to configure actions, event handlers that may
     301                 :             :  * trigger outgoing packets and exchange connect-time data with the device.
     302                 :             :  *
     303                 :             :  * This is optional for all implementations as plugins aren't required to be
     304                 :             :  * dependent on the device state.
     305                 :             :  *
     306                 :             :  * Since: 1.0
     307                 :             :  */
     308                 :             : void
     309                 :         592 : valent_device_plugin_update_state (ValentDevicePlugin *plugin,
     310                 :             :                                    ValentDeviceState   state)
     311                 :             : {
     312                 :         592 :   VALENT_ENTRY;
     313                 :             : 
     314         [ +  - ]:         592 :   g_return_if_fail (VALENT_IS_DEVICE_PLUGIN (plugin));
     315                 :             : 
     316                 :         592 :   VALENT_DEVICE_PLUGIN_GET_CLASS (plugin)->update_state (plugin, state);
     317                 :             : 
     318                 :         592 :   VALENT_EXIT;
     319                 :             : }
     320                 :             : 
     321                 :             : static int
     322                 :         159 : _g_menu_find_action (GMenuModel *menu,
     323                 :             :                      const char *action)
     324                 :             : {
     325                 :         159 :   int i, n_items;
     326                 :             : 
     327   [ +  -  +  -  :         159 :   g_assert (G_IS_MENU_MODEL (menu));
             +  -  -  + ]
     328         [ -  + ]:         159 :   g_assert (action != NULL);
     329                 :             : 
     330                 :         159 :   n_items = g_menu_model_get_n_items (menu);
     331                 :             : 
     332         [ +  + ]:         365 :   for (i = 0; i < n_items; i++)
     333                 :             :     {
     334                 :          55 :       g_autofree char *item_str = NULL;
     335                 :             : 
     336                 :          55 :       g_menu_model_get_item_attribute (menu, i, "action", "s", &item_str);
     337                 :             : 
     338         [ +  + ]:          55 :       if (g_strcmp0 (item_str, action) == 0)
     339                 :           8 :         return i;
     340                 :             :     }
     341                 :             : 
     342                 :             :   return -1;
     343                 :             : }
     344                 :             : 
     345                 :             : /**
     346                 :             :  * valent_device_plugin_set_menu_action:
     347                 :             :  * @plugin: a `ValentDevicePlugin`
     348                 :             :  * @action: a `GAction` name
     349                 :             :  * @label: (nullable): a label for the action
     350                 :             :  * @icon_name: (nullable): an icon for the action
     351                 :             :  *
     352                 :             :  * Set or remove a device menu action by [iface@Gio.Action] name.
     353                 :             :  *
     354                 :             :  * If @label and @icon are %NULL, @action will be removed from the menu.
     355                 :             :  *
     356                 :             :  * Since: 1.0
     357                 :             :  */
     358                 :             : void
     359                 :         142 : valent_device_plugin_set_menu_action (ValentDevicePlugin *plugin,
     360                 :             :                                       const char         *action,
     361                 :             :                                       const char         *label,
     362                 :             :                                       const char         *icon_name)
     363                 :             : {
     364                 :         284 :   g_autoptr (GMenuItem) item = NULL;
     365                 :             : 
     366         [ +  - ]:         142 :   g_return_if_fail (VALENT_IS_DEVICE_PLUGIN (plugin));
     367   [ +  -  -  + ]:         142 :   g_return_if_fail (action != NULL && *action != '\0');
     368   [ +  -  +  -  :         142 :   g_return_if_fail ((label == NULL && icon_name == NULL) ||
                   -  + ]
     369                 :             :                     (label != NULL && *label != '\0'));
     370                 :             : 
     371         [ +  - ]:         142 :   if (label != NULL)
     372                 :             :     {
     373                 :         284 :       g_autoptr (GIcon) icon = NULL;
     374                 :             : 
     375         [ +  - ]:         142 :       if (icon_name != NULL)
     376                 :         142 :         icon = g_themed_icon_new (icon_name);
     377                 :             : 
     378                 :         142 :       item = g_menu_item_new (label, action);
     379                 :         142 :       g_menu_item_set_icon (item, icon);
     380         [ +  - ]:         142 :       g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled");
     381                 :             :     }
     382                 :             : 
     383         [ +  - ]:         142 :   valent_device_plugin_set_menu_item (plugin, action, item);
     384                 :             : }
     385                 :             : 
     386                 :             : /**
     387                 :             :  * valent_device_plugin_set_menu_item:
     388                 :             :  * @plugin: a `ValentDevicePlugin`
     389                 :             :  * @action: a `GAction` name
     390                 :             :  * @item: (nullable): a `GMenuItem`
     391                 :             :  *
     392                 :             :  * Set or remove a device [class@Gio.MenuItem] by [iface@Gio.Action] name.
     393                 :             :  *
     394                 :             :  * If @item is %NULL, @action will be removed from the menu.
     395                 :             :  *
     396                 :             :  * Since: 1.0
     397                 :             :  */
     398                 :             : void
     399                 :         415 : valent_device_plugin_set_menu_item (ValentDevicePlugin *plugin,
     400                 :             :                                     const char         *action,
     401                 :             :                                     GMenuItem          *item)
     402                 :             : {
     403                 :         415 :   ValentDevice *device = NULL;
     404                 :         415 :   GMenuModel *menu;
     405                 :         415 :   int index_ = -1;
     406                 :             : 
     407         [ +  - ]:         415 :   g_return_if_fail (VALENT_IS_DEVICE_PLUGIN (plugin));
     408   [ +  -  -  + ]:         415 :   g_return_if_fail (action != NULL && *action != '\0');
     409   [ +  +  +  -  :         415 :   g_return_if_fail (item == NULL || G_IS_MENU_ITEM (item));
             -  +  -  - ]
     410                 :             : 
     411                 :             :   /* NOTE: this method may be called by plugins in their `dispose()` */
     412         [ +  + ]:         415 :   if ((device = valent_resource_get_source (VALENT_RESOURCE (plugin))) == NULL)
     413                 :             :     return;
     414                 :             : 
     415                 :         159 :   menu = valent_device_get_menu (device);
     416                 :         159 :   index_ = _g_menu_find_action (menu, action);
     417                 :             : 
     418         [ +  + ]:         159 :   if (index_ > -1)
     419                 :           8 :     g_menu_remove (G_MENU (menu), index_);
     420                 :             : 
     421         [ +  + ]:         159 :   if (item != NULL)
     422                 :             :     {
     423         [ -  + ]:         143 :       if (index_ > -1)
     424                 :           0 :         g_menu_insert_item (G_MENU (menu), index_, item);
     425                 :             :       else
     426                 :         143 :         g_menu_append_item (G_MENU (menu), item);
     427                 :             :     }
     428                 :             : }
     429                 :             : 
     430                 :             : /**
     431                 :             :  * valent_notification_set_device_action:
     432                 :             :  * @notification: a `GNotification`
     433                 :             :  * @device: a `ValentDevice`
     434                 :             :  * @action: the device action name
     435                 :             :  * @target: (nullable): the action target
     436                 :             :  *
     437                 :             :  * Set the default action for @notification. @action is wrapped in the special
     438                 :             :  * `device` action for @device, which allows it to be activated from the `app`
     439                 :             :  * action scope.
     440                 :             :  *
     441                 :             :  * Since: 1.0
     442                 :             :  */
     443                 :             : void
     444                 :           0 : valent_notification_set_device_action (GNotification *notification,
     445                 :             :                                        ValentDevice  *device,
     446                 :             :                                        const char    *action,
     447                 :             :                                        GVariant      *target)
     448                 :             : {
     449                 :           0 :   GVariantBuilder builder;
     450                 :             : 
     451   [ #  #  #  #  :           0 :   g_return_if_fail (G_IS_NOTIFICATION (notification));
             #  #  #  # ]
     452         [ #  # ]:           0 :   g_return_if_fail (VALENT_IS_DEVICE (device));
     453   [ #  #  #  # ]:           0 :   g_return_if_fail (action != NULL && *action != '\0');
     454                 :             : 
     455                 :           0 :   g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
     456                 :             : 
     457         [ #  # ]:           0 :   if (target != NULL)
     458                 :           0 :     g_variant_builder_add (&builder, "v", target);
     459                 :             : 
     460                 :           0 :   g_notification_set_default_action_and_target (notification,
     461                 :             :                                                 "app.device",
     462                 :             :                                                 "(ssav)",
     463                 :             :                                                 valent_device_get_id (device),
     464                 :             :                                                 action,
     465                 :             :                                                 &builder);
     466                 :             : }
     467                 :             : 
     468                 :             : /**
     469                 :             :  * valent_notification_add_device_button:
     470                 :             :  * @notification: a `GNotification`
     471                 :             :  * @device: a `ValentDevice`
     472                 :             :  * @label: the button label
     473                 :             :  * @action: the device action name
     474                 :             :  * @target: (nullable): the action target
     475                 :             :  *
     476                 :             :  * Add an action button to @notification. @action is wrapped in the special
     477                 :             :  * `device` action for @device, which allows it to be activated from the `app`
     478                 :             :  * action scope.
     479                 :             :  *
     480                 :             :  * Since: 1.0
     481                 :             :  */
     482                 :             : void
     483                 :          31 : valent_notification_add_device_button (GNotification *notification,
     484                 :             :                                        ValentDevice  *device,
     485                 :             :                                        const char    *label,
     486                 :             :                                        const char    *action,
     487                 :             :                                        GVariant      *target)
     488                 :             : {
     489                 :          31 :   GVariantBuilder builder;
     490                 :             : 
     491   [ +  -  +  -  :          31 :   g_return_if_fail (G_IS_NOTIFICATION (notification));
             -  +  -  - ]
     492         [ -  + ]:          31 :   g_return_if_fail (VALENT_IS_DEVICE (device));
     493   [ +  -  -  + ]:          31 :   g_return_if_fail (label != NULL && *label != '\0');
     494   [ +  -  -  + ]:          31 :   g_return_if_fail (action != NULL && *action != '\0');
     495                 :             : 
     496                 :          31 :   g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
     497                 :             : 
     498         [ +  + ]:          31 :   if (target != NULL)
     499                 :          26 :     g_variant_builder_add (&builder, "v", target);
     500                 :             : 
     501                 :          31 :   g_notification_add_button_with_target (notification,
     502                 :             :                                          label,
     503                 :             :                                          "app.device",
     504                 :             :                                          "(ssav)",
     505                 :             :                                          valent_device_get_id (device),
     506                 :             :                                          action,
     507                 :             :                                          &builder);
     508                 :             : }
     509                 :             : 
        

Generated by: LCOV version 2.0-1