[gnome-bluetooth/wip/hadess/more-precise-state: 2/2] lib: Export a more precise adapter power state




commit 6a4496e251892a627d6d660cf2080c097672b8b9
Author: Bastien Nocera <hadess hadess net>
Date:   Tue Aug 23 12:48:19 2022 +0200

    lib: Export a more precise adapter power state
    
    Unfortunately, powering on/off Bluetooth adapters has become longer,
    and less reliable over time, so front-ends need more information to
    be able to figure out what it happening.
    
    As a first pass, export whether the adapter is in the process of being
    turned on, or turned off, based on our own request.

 lib/bluetooth-client.c    | 65 ++++++++++++++++++++++++++++++++++++++++++++++-
 lib/bluetooth-enums.h     | 18 +++++++++++++
 lib/gnome-bluetooth.map   |  1 +
 tests/integration-test.py |  8 ++++++
 4 files changed, 91 insertions(+), 1 deletion(-)
---
diff --git a/lib/bluetooth-client.c b/lib/bluetooth-client.c
index 5dc2522a..a39317bb 100644
--- a/lib/bluetooth-client.c
+++ b/lib/bluetooth-client.c
@@ -54,11 +54,19 @@
 #define BLUEZ_ADAPTER_INTERFACE                "org.bluez.Adapter1"
 #define BLUEZ_DEVICE_INTERFACE         "org.bluez.Device1"
 
+/* Subset of BluetoothAdapterState */
+typedef enum {
+       POWER_STATE_REQUEST_NONE = 0,
+       POWER_STATE_REQUEST_ON,
+       POWER_STATE_REQUEST_OFF,
+} PowerStateRequest;
+
 struct _BluetoothClient {
        GObject parent;
 
        GListStore *list_store;
        Adapter1 *default_adapter;
+       PowerStateRequest power_req;
        GDBusObjectManager *manager;
        GCancellable *cancellable;
        guint num_adapters;
@@ -75,6 +83,7 @@ enum {
        PROP_NUM_ADAPTERS,
        PROP_DEFAULT_ADAPTER,
        PROP_DEFAULT_ADAPTER_POWERED,
+       PROP_DEFAULT_ADAPTER_STATE,
        PROP_DEFAULT_ADAPTER_SETUP_MODE,
        PROP_DEFAULT_ADAPTER_NAME,
        PROP_DEFAULT_ADAPTER_ADDRESS
@@ -409,15 +418,31 @@ adapter_set_powered_cb (GDBusProxy *proxy,
                        GAsyncResult *res,
                        gpointer      user_data)
 {
+       BluetoothClient *client;
        g_autoptr(GError) error = NULL;
        g_autoptr(GVariant) ret = NULL;
 
        ret = g_dbus_proxy_call_finish (proxy, res, &error);
        if (!ret) {
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                       return;
                g_debug ("Error setting property 'Powered' on %s: %s (%s, %d)",
                         g_dbus_proxy_get_object_path (proxy),
                         error->message, g_quark_to_string (error->domain), error->code);
        }
+
+       client = user_data;
+       if (client->default_adapter) {
+               gboolean powered;
+
+               powered = adapter1_get_powered (client->default_adapter);
+               if ((powered && client->power_req == POWER_STATE_REQUEST_ON) ||
+                   (!powered && client->power_req == POWER_STATE_REQUEST_OFF)) {
+                       /* Only reset if we don't have a conflicting state in progress */
+                       client->power_req = POWER_STATE_REQUEST_NONE;
+               }
+       }
+       g_object_notify (G_OBJECT (client), "default-adapter-state");
 }
 
 static void
@@ -433,6 +458,12 @@ adapter_set_powered (BluetoothClient *client,
                return;
        }
 
+       if ((powered && client->power_req == POWER_STATE_REQUEST_ON) ||
+           (!powered && client->power_req == POWER_STATE_REQUEST_OFF)) {
+               g_debug ("Default adapter is already being powered %s", powered ? "on" : "off");
+               return;
+       }
+
        if (powered == adapter1_get_powered (client->default_adapter)) {
                g_debug ("Default adapter is already %spowered", powered ? "" : "un");
                return;
@@ -442,6 +473,9 @@ adapter_set_powered (BluetoothClient *client,
                 powered ? "up" : "down",
                 g_dbus_proxy_get_object_path (G_DBUS_PROXY (client->default_adapter)));
        variant = g_variant_new_boolean (powered);
+       client->power_req = powered ?
+               POWER_STATE_REQUEST_ON : POWER_STATE_REQUEST_OFF;
+       g_object_notify (G_OBJECT (client), "default-adapter-state");
        g_dbus_proxy_call (G_DBUS_PROXY (client->default_adapter),
                           "org.freedesktop.DBus.Properties.Set",
                           g_variant_new ("(ssv)", "org.bluez.Adapter1", "Powered", variant),
@@ -554,6 +588,7 @@ adapter_notify_cb (Adapter1       *adapter,
                g_object_notify (G_OBJECT (client), "default-adapter-setup-mode");
        } else if (g_strcmp0 (property, "powered") == 0) {
                g_object_notify (G_OBJECT (client), "default-adapter-powered");
+               g_object_notify (G_OBJECT (client), "default-adapter-state");
        }
 }
 
@@ -570,6 +605,7 @@ notify_default_adapter_props (BluetoothClient *client)
        g_object_notify (G_OBJECT (client), "default-adapter");
        g_object_notify (G_OBJECT (client), "default-adapter-address");
        g_object_notify (G_OBJECT (client), "default-adapter-powered");
+       g_object_notify (G_OBJECT (client), "default-adapter-state");
        g_object_notify (G_OBJECT (client), "default-adapter-setup-mode");
        g_object_notify (G_OBJECT (client), "default-adapter-name");
 }
@@ -603,7 +639,6 @@ default_adapter_changed (GDBusObjectManager       *manager,
                g_list_store_remove_all (client->list_store);
                g_debug ("Disabling discovery on old default adapter");
                _bluetooth_client_set_default_adapter_discovering (client, FALSE);
-               g_clear_object (&client->default_adapter);
        }
 
        client->default_adapter = ADAPTER1 (g_object_ref (G_OBJECT (adapter)));
@@ -1210,6 +1245,19 @@ _bluetooth_client_set_default_adapter_discovering (BluetoothClient *client,
        }
 }
 
+static BluetoothAdapterState
+adapter_get_state (BluetoothClient *client)
+{
+       if (!client->default_adapter)
+               return BLUETOOTH_ADAPTER_STATE_ABSENT;
+       if (client->power_req == POWER_STATE_REQUEST_NONE)
+               return adapter1_get_powered (client->default_adapter) ?
+                       BLUETOOTH_ADAPTER_STATE_ON : BLUETOOTH_ADAPTER_STATE_OFF;
+       return client->power_req == POWER_STATE_REQUEST_ON ?
+               BLUETOOTH_ADAPTER_STATE_TURNING_ON :
+               BLUETOOTH_ADAPTER_STATE_TURNING_OFF;
+}
+
 static void
 bluetooth_client_get_property (GObject        *object,
                               guint           property_id,
@@ -1230,6 +1278,9 @@ bluetooth_client_get_property (GObject        *object,
                g_value_set_boolean (value, client->default_adapter ?
                                     adapter1_get_powered (client->default_adapter) : FALSE);
                break;
+       case PROP_DEFAULT_ADAPTER_STATE:
+               g_value_set_enum (value, adapter_get_state (client));
+               break;
        case PROP_DEFAULT_ADAPTER_NAME:
                g_value_set_string (value, client->default_adapter ?
                                    adapter1_get_alias (client->default_adapter) : NULL);
@@ -1363,6 +1414,18 @@ static void bluetooth_client_class_init(BluetoothClientClass *klass)
                                         g_param_spec_boolean ("default-adapter-powered", NULL,
                                                              "Whether the default adapter is powered",
                                                               FALSE, G_PARAM_READWRITE));
+       /**
+        * BluetoothClient:default-adapter-state:
+        *
+        * The #BluetoothAdapterState of the default Bluetooth adapter. More precise than
+        * #BluetoothClient:default-adapter-powered.
+        */
+       g_object_class_install_property (object_class, PROP_DEFAULT_ADAPTER_STATE,
+                                        g_param_spec_enum ("default-adapter-state", NULL,
+                                                           "State of the default adapter",
+                                                           BLUETOOTH_TYPE_ADAPTER_STATE,
+                                                           BLUETOOTH_ADAPTER_STATE_ABSENT,
+                                                           G_PARAM_READABLE));
        /**
         * BluetoothClient:default-adapter-setup-mode:
         *
diff --git a/lib/bluetooth-enums.h b/lib/bluetooth-enums.h
index 9bbe2dad..6c84f5f2 100644
--- a/lib/bluetooth-enums.h
+++ b/lib/bluetooth-enums.h
@@ -112,3 +112,21 @@ typedef enum {
        BLUETOOTH_BATTERY_TYPE_PERCENTAGE,
        BLUETOOTH_BATTERY_TYPE_COARSE
 } BluetoothBatteryType;
+
+/**
+ * BluetoothAdapterState:
+ * @BLUETOOTH_ADAPTER_STATE_ON: Bluetooth adapter is on.
+ * @BLUETOOTH_ADAPTER_STATE_TURNING_ON: Bluetooth adapter is being turned on.
+ * @BLUETOOTH_ADAPTER_STATE_TURNING_OFF: Bluetooth adapter is being turned off.
+ * @BLUETOOTH_ADAPTER_STATE_OFF: Bluetooth adapter is off.
+ * @BLUETOOTH_ADAPTER_STATE_ABSENT: Bluetooth adapter is missing.
+ *
+ * A more precise power state for a Bluetooth adapter.
+ **/
+typedef enum {
+       BLUETOOTH_ADAPTER_STATE_ON,
+       BLUETOOTH_ADAPTER_STATE_TURNING_ON,
+       BLUETOOTH_ADAPTER_STATE_TURNING_OFF,
+       BLUETOOTH_ADAPTER_STATE_OFF,
+       BLUETOOTH_ADAPTER_STATE_ABSENT,
+} BluetoothAdapterState;
diff --git a/lib/gnome-bluetooth.map b/lib/gnome-bluetooth.map
index 26295cba..7febdee1 100644
--- a/lib/gnome-bluetooth.map
+++ b/lib/gnome-bluetooth.map
@@ -22,6 +22,7 @@ global:
   bluetooth_device_get_object_path;
   bluetooth_device_to_string;
   bluetooth_battery_type_get_type;
+  bluetooth_adapter_state_get_type;
   bluetooth_agent_get_type;
   bluetooth_agent_error_get_type;
   bluetooth_agent_new;
diff --git a/tests/integration-test.py b/tests/integration-test.py
index e7ebf7ef..83550c3a 100755
--- a/tests/integration-test.py
+++ b/tests/integration-test.py
@@ -265,26 +265,34 @@ class OopTests(dbusmock.DBusTestCase):
         self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), False)
         self.wait_for_mainloop()
         self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), False)
+        self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.OFF)
         self.client.props.default_adapter_powered = True
+        self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.TURNING_ON)
         self.wait_for_condition(lambda: dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered') == True)
         self.assertEqual(self.client.props.num_adapters, 1)
         self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), True)
         self.wait_for_mainloop()
         self.assertEqual(self.client.props.default_adapter_powered, True)
+        self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.ON)
 
         self.client.props.default_adapter_powered = False
         self.wait_for_condition(lambda: dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered') == False)
         self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), False)
+        self.assertEqual(self.client.props.default_adapter_state, 
GnomeBluetoothPriv.AdapterState.TURNING_OFF)
         self.wait_for_mainloop()
         self.assertEqual(self.client.props.default_adapter_powered, False)
+        self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.OFF)
 
         dbusmock_bluez.UpdateProperties('org.bluez.Adapter1', {
                 'Powered': True,
         })
+        # NOTE: this should be "turning on" when we have bluez API to keep track of it
+        self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.OFF)
         self.wait_for_condition(lambda: dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered') == True)
         self.assertEqual(dbusprops_bluez.Get('org.bluez.Adapter1', 'Powered'), True)
         self.wait_for_mainloop()
         self.assertEqual(self.client.props.default_adapter_powered, True)
+        self.assertEqual(self.client.props.default_adapter_state, GnomeBluetoothPriv.AdapterState.ON)
 
     def _pair_cb(self, client, result, user_data=None):
         success, path = client.setup_device_finish(result)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]