From b4e1b6d6c67e2a7322dac7047e73939a99a7e3bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 31 Aug 2023 14:50:39 -0700 Subject: [PATCH 1/9] Add Bluetooth toggle --- data/quick-settings.gresource.xml | 4 ++ src/DBus/AccountsService.vala | 5 ++ src/PopoverWidget.vala | 109 ++++++++++++++++++++++++++++++ src/Widgets/SettingsToggle.vala | 8 ++- src/meson.build | 1 + 5 files changed, 125 insertions(+), 2 deletions(-) diff --git a/data/quick-settings.gresource.xml b/data/quick-settings.gresource.xml index d18da5e..b061185 100644 --- a/data/quick-settings.gresource.xml +++ b/data/quick-settings.gresource.xml @@ -8,5 +8,9 @@ icons/dark-mode.svg icons/quick-settings.svg + + icons/bluetooth/active.svg + icons/bluetooth/disabled.svg + icons/bluetooth/paired.svg diff --git a/src/DBus/AccountsService.vala b/src/DBus/AccountsService.vala index 7731a92..a3d089e 100644 --- a/src/DBus/AccountsService.vala +++ b/src/DBus/AccountsService.vala @@ -1,3 +1,8 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io) + */ + [DBus (name = "io.elementary.pantheon.AccountsService")] public interface QuickSettings.Pantheon.AccountsService : Object { public abstract int prefers_color_scheme { get; set; } diff --git a/src/PopoverWidget.vala b/src/PopoverWidget.vala index 512a443..c0aa1a0 100644 --- a/src/PopoverWidget.vala +++ b/src/PopoverWidget.vala @@ -9,6 +9,9 @@ public class QuickSettings.PopoverWidget : Gtk.Box { private Gtk.Popover? popover; private Hdy.Deck deck; + private SettingsToggle bluetooth_toggle; + + private DBusObjectManagerClient? bluetooth_manager = null; private Pantheon.AccountsService? pantheon_service = null; class construct { @@ -92,6 +95,38 @@ public class QuickSettings.PopoverWidget : Gtk.Box { } }); + setup_bluetooth.begin ((obj, res) => { + setup_bluetooth.end (res); + + if (bluetooth_manager == null) { + return; + } + + bluetooth_toggle = new SettingsToggle ( + new ThemedIcon ("quicksettings-bluetooth-active-symbolic"), + _("Bluetooth") + ) { + settings_uri = "settings://network/bluetooth" + }; + + toggle_box.add (bluetooth_toggle); + show_all (); + + bluetooth_manager.get_objects ().foreach ((object) => { + object.get_interfaces ().foreach ((iface) => on_interface_added (object, iface)); + }); + bluetooth_manager.interface_added.connect (on_interface_added); + bluetooth_manager.interface_removed.connect (on_interface_removed); + bluetooth_manager.object_added.connect ((object) => { + object.get_interfaces ().foreach ((iface) => on_interface_added (object, iface)); + }); + bluetooth_manager.object_removed.connect ((object) => { + object.get_interfaces ().foreach ((iface) => on_interface_removed (object, iface)); + }); + + update_bluetooth_status (); + }); + realize.connect (() => { popover = (Gtk.Popover) get_ancestor (typeof (Gtk.Popover)); popover.closed.connect (() => { @@ -160,4 +195,78 @@ public class QuickSettings.PopoverWidget : Gtk.Box { critical ("Unable to get Pantheon's AccountsService proxy, Dark mode toggle will not be available"); } } + + private async void setup_bluetooth () { + try { + bluetooth_manager = yield new GLib.DBusObjectManagerClient.for_bus.begin ( + BusType.SYSTEM, + NONE, + "org.bluez", + "/", + object_manager_get_proxy_type, + null + ); + } catch (Error e) { + critical (e.message); + } + } + + //TODO: Do not rely on this when it is possible to do it natively in Vala + [CCode (cname="quick_settings_bluez_adapter_proxy_get_type")] + extern static GLib.Type get_adapter_proxy_type (); + + private GLib.Type object_manager_get_proxy_type (DBusObjectManagerClient manager, string object_path, string? interface_name) { + if (interface_name == null) { + return typeof (GLib.DBusObjectProxy); + } + + switch (interface_name) { + case "org.bluez.Adapter1": + return get_adapter_proxy_type (); + default: + return typeof (GLib.DBusProxy); + } + } + + private void on_interface_added (GLib.DBusObject object, GLib.DBusInterface iface) { + if (iface is QuickSettings.BluezAdapter) { + unowned var adapter = (QuickSettings.BluezAdapter) iface; + + ((DBusProxy) adapter).g_properties_changed.connect ((changed, invalid) => { + var powered = changed.lookup_value ("Powered", new VariantType ("b")); + if (powered != null) { + update_bluetooth_status (); + } + }); + + update_bluetooth_status (); + } + } + + private void on_interface_removed (GLib.DBusObject object, GLib.DBusInterface iface) { + update_bluetooth_status (); + } + + private void update_bluetooth_status () { + var powered = false; + foreach (unowned var object in bluetooth_manager.get_objects ()) { + DBusInterface? iface = object.get_interface ("org.bluez.Adapter1"); + if (iface == null) { + continue; + } + + if (((QuickSettings.BluezAdapter) iface).powered) { + powered = true; + break; + } + } + + if (powered) { + bluetooth_toggle.active = true; + bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-active-symbolic"); + } else { + bluetooth_toggle.active = false; + bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-disabled-symbolic"); + } + } } diff --git a/src/Widgets/SettingsToggle.vala b/src/Widgets/SettingsToggle.vala index 108e89b..64d5548 100644 --- a/src/Widgets/SettingsToggle.vala +++ b/src/Widgets/SettingsToggle.vala @@ -5,7 +5,7 @@ public class QuickSettings.SettingsToggle : Gtk.Box { public bool active { get; set; } - public Icon icon { get; construct; } + public Icon icon { get; construct set; } public string label { get; construct; } public string settings_uri { get; set; default = "settings://"; } @@ -19,9 +19,11 @@ public class QuickSettings.SettingsToggle : Gtk.Box { } construct { + var image = new Gtk.Image.from_gicon (icon, MENU); + var button = new Gtk.ToggleButton () { halign = CENTER, - image = new Gtk.Image.from_gicon (icon, MENU) + image = image }; button.get_style_context ().add_class ("circular"); @@ -39,6 +41,8 @@ public class QuickSettings.SettingsToggle : Gtk.Box { button.bind_property ("active", this, "active", SYNC_CREATE | BIDIRECTIONAL); + bind_property ("icon", image, "gicon"); + middle_click_gesture = new Gtk.GestureMultiPress (button) { button = Gdk.BUTTON_MIDDLE }; diff --git a/src/meson.build b/src/meson.build index fdcbd18..e4ae74f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -11,6 +11,7 @@ sources = [ 'Indicator.vala', 'PopoverWidget.vala', 'DBus' / 'AccountsService.vala', + 'DBus' / 'BluezAdapter.vala', 'Views' / 'A11yView.vala', 'Widgets' / 'SettingsToggle.vala' ] From 83df2d716da86cc6fd8d50c86f307a43ed0455e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 31 Aug 2023 15:20:05 -0700 Subject: [PATCH 2/9] Set status --- src/PopoverWidget.vala | 58 +++++++++++++++++++++++++++++++++++++----- src/meson.build | 1 + 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/PopoverWidget.vala b/src/PopoverWidget.vala index c0aa1a0..a4ecd93 100644 --- a/src/PopoverWidget.vala +++ b/src/PopoverWidget.vala @@ -112,6 +112,10 @@ public class QuickSettings.PopoverWidget : Gtk.Box { toggle_box.add (bluetooth_toggle); show_all (); + bluetooth_toggle.notify["active"].connect (() => { + set_bluetooth_status.begin (bluetooth_toggle.active); + }); + bluetooth_manager.get_objects ().foreach ((object) => { object.get_interfaces ().foreach ((iface) => on_interface_added (object, iface)); }); @@ -124,7 +128,7 @@ public class QuickSettings.PopoverWidget : Gtk.Box { object.get_interfaces ().foreach ((iface) => on_interface_removed (object, iface)); }); - update_bluetooth_status (); + get_bluetooth_status (); }); realize.connect (() => { @@ -215,12 +219,18 @@ public class QuickSettings.PopoverWidget : Gtk.Box { [CCode (cname="quick_settings_bluez_adapter_proxy_get_type")] extern static GLib.Type get_adapter_proxy_type (); + //TODO: Do not rely on this when it is possible to do it natively in Vala + [CCode (cname="quick_settings_bluez_device_proxy_get_type")] + extern static GLib.Type get_device_proxy_type (); + private GLib.Type object_manager_get_proxy_type (DBusObjectManagerClient manager, string object_path, string? interface_name) { if (interface_name == null) { return typeof (GLib.DBusObjectProxy); } switch (interface_name) { + case "org.bluez.Device1": + return get_device_proxy_type (); case "org.bluez.Adapter1": return get_adapter_proxy_type (); default: @@ -235,19 +245,19 @@ public class QuickSettings.PopoverWidget : Gtk.Box { ((DBusProxy) adapter).g_properties_changed.connect ((changed, invalid) => { var powered = changed.lookup_value ("Powered", new VariantType ("b")); if (powered != null) { - update_bluetooth_status (); + get_bluetooth_status (); } }); - update_bluetooth_status (); + get_bluetooth_status (); } } private void on_interface_removed (GLib.DBusObject object, GLib.DBusInterface iface) { - update_bluetooth_status (); + get_bluetooth_status (); } - private void update_bluetooth_status () { + private void get_bluetooth_status () { var powered = false; foreach (unowned var object in bluetooth_manager.get_objects ()) { DBusInterface? iface = object.get_interface ("org.bluez.Adapter1"); @@ -261,12 +271,46 @@ public class QuickSettings.PopoverWidget : Gtk.Box { } } + if (bluetooth_toggle.active != powered) { + bluetooth_toggle.active = powered; + } + if (powered) { - bluetooth_toggle.active = true; bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-active-symbolic"); } else { - bluetooth_toggle.active = false; bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-disabled-symbolic"); } } + + private async void set_bluetooth_status (bool status) { + foreach (unowned var object in bluetooth_manager.get_objects ()) { + DBusInterface? iface = object.get_interface ("org.bluez.Adapter1"); + if (iface == null) { + continue; + } + + var adapter = (QuickSettings.BluezAdapter) iface; + if (adapter.powered != status) { + adapter.powered = status; + } + } + + if (!status) { + foreach (unowned var object in bluetooth_manager.get_objects ()) { + DBusInterface? iface = object.get_interface ("org.bluez.Device1"); + if (iface == null) { + continue; + } + + var device = (QuickSettings.BluezDevice) iface; + if (device.connected) { + try { + yield device.disconnect (); + } catch (Error e) { + critical (e.message); + } + } + } + } + } } diff --git a/src/meson.build b/src/meson.build index e4ae74f..120f8bd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,6 +12,7 @@ sources = [ 'PopoverWidget.vala', 'DBus' / 'AccountsService.vala', 'DBus' / 'BluezAdapter.vala', + 'DBus' / 'BluezDevice.vala', 'Views' / 'A11yView.vala', 'Widgets' / 'SettingsToggle.vala' ] From 7707d6abf0b1e8b03f327655cd280f5ae7e7a0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 31 Aug 2023 15:34:46 -0700 Subject: [PATCH 3/9] Show paired status --- src/PopoverWidget.vala | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/PopoverWidget.vala b/src/PopoverWidget.vala index a4ecd93..31f5b4a 100644 --- a/src/PopoverWidget.vala +++ b/src/PopoverWidget.vala @@ -248,9 +248,19 @@ public class QuickSettings.PopoverWidget : Gtk.Box { get_bluetooth_status (); } }); + } else if (iface is QuickSettings.BluezDevice) { + unowned var device = (QuickSettings.BluezDevice) iface; - get_bluetooth_status (); + ((DBusProxy) device).g_properties_changed.connect ((changed, invalid) => { + var connected = changed.lookup_value ("Connected", new VariantType ("b")); + var paired = changed.lookup_value ("Paired", new VariantType ("b")); + if (connected != null || paired != null) { + get_bluetooth_status (); + } + }); } + + get_bluetooth_status (); } private void on_interface_removed (GLib.DBusObject object, GLib.DBusInterface iface) { @@ -276,7 +286,24 @@ public class QuickSettings.PopoverWidget : Gtk.Box { } if (powered) { - bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-active-symbolic"); + var paired = false; + foreach (unowned var object in bluetooth_manager.get_objects ()) { + DBusInterface? iface = object.get_interface ("org.bluez.Device1"); + if (iface == null) { + continue; + } + + var device = (QuickSettings.BluezDevice) iface; + if (device.connected) { + paired = true; + } + } + + if (paired) { + bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-paired-symbolic"); + } else { + bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-active-symbolic"); + } } else { bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-disabled-symbolic"); } From 6f4887c04f581dfeaf6d0151d1e1430fa0882698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 31 Aug 2023 15:35:41 -0700 Subject: [PATCH 4/9] Add missing stuff --- data/icons/bluetooth/active.svg | 23 +++++++++++++++++++++++ data/icons/bluetooth/disabled.svg | 25 +++++++++++++++++++++++++ data/icons/bluetooth/paired.svg | 19 +++++++++++++++++++ src/DBus/BluezAdapter.vala | 9 +++++++++ src/DBus/BluezDevice.vala | 30 ++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 data/icons/bluetooth/active.svg create mode 100644 data/icons/bluetooth/disabled.svg create mode 100644 data/icons/bluetooth/paired.svg create mode 100644 src/DBus/BluezAdapter.vala create mode 100644 src/DBus/BluezDevice.vala diff --git a/data/icons/bluetooth/active.svg b/data/icons/bluetooth/active.svg new file mode 100644 index 0000000..fff32c8 --- /dev/null +++ b/data/icons/bluetooth/active.svg @@ -0,0 +1,23 @@ + + + + + + + diff --git a/data/icons/bluetooth/disabled.svg b/data/icons/bluetooth/disabled.svg new file mode 100644 index 0000000..4aa1339 --- /dev/null +++ b/data/icons/bluetooth/disabled.svg @@ -0,0 +1,25 @@ + + + + + + diff --git a/data/icons/bluetooth/paired.svg b/data/icons/bluetooth/paired.svg new file mode 100644 index 0000000..12d147a --- /dev/null +++ b/data/icons/bluetooth/paired.svg @@ -0,0 +1,19 @@ + + + + + + diff --git a/src/DBus/BluezAdapter.vala b/src/DBus/BluezAdapter.vala new file mode 100644 index 0000000..96ad51f --- /dev/null +++ b/src/DBus/BluezAdapter.vala @@ -0,0 +1,9 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io) + */ + +[DBus (name = "org.bluez.Adapter1")] +public interface QuickSettings.BluezAdapter : Object { + public abstract bool powered { get; set; } +} diff --git a/src/DBus/BluezDevice.vala b/src/DBus/BluezDevice.vala new file mode 100644 index 0000000..3dcf3ff --- /dev/null +++ b/src/DBus/BluezDevice.vala @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io) + */ + +[DBus (name = "org.bluez.Device1")] +public interface QuickSettings.BluezDevice : Object { + public abstract void cancel_pairing () throws Error; + public abstract async void connect () throws Error; + public abstract void connect_profile (string UUID) throws Error; //vala-lint=naming-convention + public abstract async void disconnect () throws Error; + public abstract void disconnect_profile (string UUID) throws Error; //vala-lint=naming-convention + public abstract void pair () throws Error; + + public abstract string[] UUIDs { owned get; } + public abstract bool blocked { owned get; set; } + public abstract bool connected { owned get; } + public abstract bool legacy_pairing { owned get; } + public abstract bool paired { owned get; } + public abstract bool trusted { owned get; set; } + public abstract int16 RSSI { owned get; } + public abstract ObjectPath adapter { owned get; } + public abstract string address { owned get; } + public abstract string alias { owned get; set; } + public abstract string icon { owned get; } + public abstract string modalias { owned get; } + public abstract string name { owned get; } + public abstract uint16 appearance { owned get; } + public abstract uint32 @class { owned get; } +} From 942ad6671356b0ba6a06fdc880ee5b8f153ca7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 31 Aug 2023 15:38:15 -0700 Subject: [PATCH 5/9] Update deps --- .github/workflows/main.yml | 2 +- README.md | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c332c4d..8c0f30d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: - name: Install Dependencies run: | apt update - apt install -y libglib2.0-dev libgtk-3-dev libwingpanel-dev meson valac + apt install -y libglib2.0-dev libgranite-dev libgtk-3-dev libhandy-1-dev libwingpanel-dev meson valac - name: Build env: DESTDIR: out diff --git a/README.md b/README.md index 9328e3f..d5f10a0 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ Prototype quick settings menu You'll need the following dependencies: +* libgtk-3-dev +* libgranite-dev >=6.0.0 +* libhandy-1-dev >=1.0 * libwingpanel-dev * meson * valac From e9e6bce7a49e5aca0a7d1748e2f681334e82feec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 31 Aug 2023 16:29:28 -0700 Subject: [PATCH 6/9] Isolate a shit load of the stuff --- src/PopoverWidget.vala | 158 +++---------------------------- src/Widgets/BluetoothToggle.vala | 141 +++++++++++++++++++++++++++ src/meson.build | 1 + 3 files changed, 157 insertions(+), 143 deletions(-) create mode 100644 src/Widgets/BluetoothToggle.vala diff --git a/src/PopoverWidget.vala b/src/PopoverWidget.vala index 31f5b4a..b00d682 100644 --- a/src/PopoverWidget.vala +++ b/src/PopoverWidget.vala @@ -9,9 +9,7 @@ public class QuickSettings.PopoverWidget : Gtk.Box { private Gtk.Popover? popover; private Hdy.Deck deck; - private SettingsToggle bluetooth_toggle; - private DBusObjectManagerClient? bluetooth_manager = null; private Pantheon.AccountsService? pantheon_service = null; class construct { @@ -96,39 +94,15 @@ public class QuickSettings.PopoverWidget : Gtk.Box { }); setup_bluetooth.begin ((obj, res) => { - setup_bluetooth.end (res); - + var bluetooth_manager = setup_bluetooth.end (res); if (bluetooth_manager == null) { return; } - bluetooth_toggle = new SettingsToggle ( - new ThemedIcon ("quicksettings-bluetooth-active-symbolic"), - _("Bluetooth") - ) { - settings_uri = "settings://network/bluetooth" - }; + var bluetooth_toggle = new BluetoothToggle (bluetooth_manager); toggle_box.add (bluetooth_toggle); show_all (); - - bluetooth_toggle.notify["active"].connect (() => { - set_bluetooth_status.begin (bluetooth_toggle.active); - }); - - bluetooth_manager.get_objects ().foreach ((object) => { - object.get_interfaces ().foreach ((iface) => on_interface_added (object, iface)); - }); - bluetooth_manager.interface_added.connect (on_interface_added); - bluetooth_manager.interface_removed.connect (on_interface_removed); - bluetooth_manager.object_added.connect ((object) => { - object.get_interfaces ().foreach ((iface) => on_interface_added (object, iface)); - }); - bluetooth_manager.object_removed.connect ((object) => { - object.get_interfaces ().foreach ((iface) => on_interface_removed (object, iface)); - }); - - get_bluetooth_status (); }); realize.connect (() => { @@ -200,21 +174,6 @@ public class QuickSettings.PopoverWidget : Gtk.Box { } } - private async void setup_bluetooth () { - try { - bluetooth_manager = yield new GLib.DBusObjectManagerClient.for_bus.begin ( - BusType.SYSTEM, - NONE, - "org.bluez", - "/", - object_manager_get_proxy_type, - null - ); - } catch (Error e) { - critical (e.message); - } - } - //TODO: Do not rely on this when it is possible to do it natively in Vala [CCode (cname="quick_settings_bluez_adapter_proxy_get_type")] extern static GLib.Type get_adapter_proxy_type (); @@ -238,106 +197,19 @@ public class QuickSettings.PopoverWidget : Gtk.Box { } } - private void on_interface_added (GLib.DBusObject object, GLib.DBusInterface iface) { - if (iface is QuickSettings.BluezAdapter) { - unowned var adapter = (QuickSettings.BluezAdapter) iface; - - ((DBusProxy) adapter).g_properties_changed.connect ((changed, invalid) => { - var powered = changed.lookup_value ("Powered", new VariantType ("b")); - if (powered != null) { - get_bluetooth_status (); - } - }); - } else if (iface is QuickSettings.BluezDevice) { - unowned var device = (QuickSettings.BluezDevice) iface; - - ((DBusProxy) device).g_properties_changed.connect ((changed, invalid) => { - var connected = changed.lookup_value ("Connected", new VariantType ("b")); - var paired = changed.lookup_value ("Paired", new VariantType ("b")); - if (connected != null || paired != null) { - get_bluetooth_status (); - } - }); - } - - get_bluetooth_status (); - } - - private void on_interface_removed (GLib.DBusObject object, GLib.DBusInterface iface) { - get_bluetooth_status (); - } - - private void get_bluetooth_status () { - var powered = false; - foreach (unowned var object in bluetooth_manager.get_objects ()) { - DBusInterface? iface = object.get_interface ("org.bluez.Adapter1"); - if (iface == null) { - continue; - } - - if (((QuickSettings.BluezAdapter) iface).powered) { - powered = true; - break; - } - } - - if (bluetooth_toggle.active != powered) { - bluetooth_toggle.active = powered; - } - - if (powered) { - var paired = false; - foreach (unowned var object in bluetooth_manager.get_objects ()) { - DBusInterface? iface = object.get_interface ("org.bluez.Device1"); - if (iface == null) { - continue; - } - - var device = (QuickSettings.BluezDevice) iface; - if (device.connected) { - paired = true; - } - } - - if (paired) { - bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-paired-symbolic"); - } else { - bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-active-symbolic"); - } - } else { - bluetooth_toggle.icon = new ThemedIcon ("quicksettings-bluetooth-disabled-symbolic"); - } - } - - private async void set_bluetooth_status (bool status) { - foreach (unowned var object in bluetooth_manager.get_objects ()) { - DBusInterface? iface = object.get_interface ("org.bluez.Adapter1"); - if (iface == null) { - continue; - } - - var adapter = (QuickSettings.BluezAdapter) iface; - if (adapter.powered != status) { - adapter.powered = status; - } - } - - if (!status) { - foreach (unowned var object in bluetooth_manager.get_objects ()) { - DBusInterface? iface = object.get_interface ("org.bluez.Device1"); - if (iface == null) { - continue; - } - - var device = (QuickSettings.BluezDevice) iface; - if (device.connected) { - try { - yield device.disconnect (); - } catch (Error e) { - critical (e.message); - } - } - } + private async DBusObjectManagerClient? setup_bluetooth () { + try { + return yield new GLib.DBusObjectManagerClient.for_bus.begin ( + BusType.SYSTEM, + NONE, + "org.bluez", + "/", + object_manager_get_proxy_type, + null + ); + } catch (Error e) { + critical (e.message); + return null; } } } diff --git a/src/Widgets/BluetoothToggle.vala b/src/Widgets/BluetoothToggle.vala new file mode 100644 index 0000000..7a507c1 --- /dev/null +++ b/src/Widgets/BluetoothToggle.vala @@ -0,0 +1,141 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io) + */ + +public class QuickSettings.BluetoothToggle: SettingsToggle { + public DBusObjectManagerClient bluetooth_manager { get; construct; } + + public BluetoothToggle (DBusObjectManagerClient bluetooth_manager) { + Object ( + bluetooth_manager: bluetooth_manager, + icon: new ThemedIcon ("quicksettings-bluetooth-active-symbolic"), + label: _("Bluetooth") + ); + } + + construct { + settings_uri = "settings://network/bluetooth"; + + notify["active"].connect (() => { + set_bluetooth_status.begin (active); + }); + + bluetooth_manager.get_objects ().foreach ((object) => { + object.get_interfaces ().foreach ((iface) => on_interface_added (object, iface)); + }); + bluetooth_manager.interface_added.connect (on_interface_added); + bluetooth_manager.interface_removed.connect (on_interface_removed); + bluetooth_manager.object_added.connect ((object) => { + object.get_interfaces ().foreach ((iface) => on_interface_added (object, iface)); + }); + bluetooth_manager.object_removed.connect ((object) => { + object.get_interfaces ().foreach ((iface) => on_interface_removed (object, iface)); + }); + + get_bluetooth_status (); + } + + private void on_interface_added (GLib.DBusObject object, GLib.DBusInterface iface) { + if (iface is QuickSettings.BluezAdapter) { + unowned var adapter = (QuickSettings.BluezAdapter) iface; + + ((DBusProxy) adapter).g_properties_changed.connect ((changed, invalid) => { + var powered = changed.lookup_value ("Powered", new VariantType ("b")); + if (powered != null) { + get_bluetooth_status (); + } + }); + } else if (iface is QuickSettings.BluezDevice) { + unowned var device = (QuickSettings.BluezDevice) iface; + + ((DBusProxy) device).g_properties_changed.connect ((changed, invalid) => { + var connected = changed.lookup_value ("Connected", new VariantType ("b")); + var paired = changed.lookup_value ("Paired", new VariantType ("b")); + if (connected != null || paired != null) { + get_bluetooth_status (); + } + }); + } + + get_bluetooth_status (); + } + + private void on_interface_removed (GLib.DBusObject object, GLib.DBusInterface iface) { + get_bluetooth_status (); + } + + private void get_bluetooth_status () { + var powered = false; + foreach (unowned var object in bluetooth_manager.get_objects ()) { + DBusInterface? iface = object.get_interface ("org.bluez.Adapter1"); + if (iface == null) { + continue; + } + + if (((QuickSettings.BluezAdapter) iface).powered) { + powered = true; + break; + } + } + + if (active != powered) { + active = powered; + } + + if (powered) { + var paired = false; + foreach (unowned var object in bluetooth_manager.get_objects ()) { + DBusInterface? iface = object.get_interface ("org.bluez.Device1"); + if (iface == null) { + continue; + } + + var device = (QuickSettings.BluezDevice) iface; + if (device.connected) { + paired = true; + } + } + + if (paired) { + icon = new ThemedIcon ("quicksettings-bluetooth-paired-symbolic"); + } else { + icon = new ThemedIcon ("quicksettings-bluetooth-active-symbolic"); + } + } else { + icon = new ThemedIcon ("quicksettings-bluetooth-disabled-symbolic"); + } + } + + private async void set_bluetooth_status (bool status) { + foreach (unowned var object in bluetooth_manager.get_objects ()) { + DBusInterface? iface = object.get_interface ("org.bluez.Adapter1"); + if (iface == null) { + continue; + } + + var adapter = (QuickSettings.BluezAdapter) iface; + if (adapter.powered != status) { + adapter.powered = status; + } + } + + if (!status) { + foreach (unowned var object in bluetooth_manager.get_objects ()) { + DBusInterface? iface = object.get_interface ("org.bluez.Device1"); + if (iface == null) { + continue; + } + + var device = (QuickSettings.BluezDevice) iface; + if (device.connected) { + try { + yield device.disconnect (); + } catch (Error e) { + critical (e.message); + } + } + } + } + } +} diff --git a/src/meson.build b/src/meson.build index 120f8bd..6c05ea8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,6 +14,7 @@ sources = [ 'DBus' / 'BluezAdapter.vala', 'DBus' / 'BluezDevice.vala', 'Views' / 'A11yView.vala', + 'Widgets' / 'BluetoothToggle.vala', 'Widgets' / 'SettingsToggle.vala' ] From e943c6b633562023918dba9c4e737999acf74792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 31 Aug 2023 16:31:48 -0700 Subject: [PATCH 7/9] simplify --- src/PopoverWidget.vala | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/PopoverWidget.vala b/src/PopoverWidget.vala index b00d682..6a6f7fa 100644 --- a/src/PopoverWidget.vala +++ b/src/PopoverWidget.vala @@ -95,14 +95,12 @@ public class QuickSettings.PopoverWidget : Gtk.Box { setup_bluetooth.begin ((obj, res) => { var bluetooth_manager = setup_bluetooth.end (res); - if (bluetooth_manager == null) { - return; - } - - var bluetooth_toggle = new BluetoothToggle (bluetooth_manager); + if (bluetooth_manager != null) { + var bluetooth_toggle = new BluetoothToggle (bluetooth_manager); - toggle_box.add (bluetooth_toggle); - show_all (); + toggle_box.add (bluetooth_toggle); + show_all (); + } }); realize.connect (() => { From e1d46275a1c6b4ef57ae844ee795362b41dc4494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Tue, 5 Sep 2023 11:20:14 -0700 Subject: [PATCH 8/9] Fix namespace --- data/quick-settings.gresource.xml | 6 +++--- src/Widgets/BluetoothToggle.vala | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/quick-settings.gresource.xml b/data/quick-settings.gresource.xml index 0df352e..52248a6 100644 --- a/data/quick-settings.gresource.xml +++ b/data/quick-settings.gresource.xml @@ -9,9 +9,9 @@ icons/dark-mode.svg icons/quick-settings.svg - icons/bluetooth/active.svg - icons/bluetooth/disabled.svg - icons/bluetooth/paired.svg + icons/bluetooth/active.svg + icons/bluetooth/disabled.svg + icons/bluetooth/paired.svg icons/rotation-allowed.svg icons/rotation-locked.svg diff --git a/src/Widgets/BluetoothToggle.vala b/src/Widgets/BluetoothToggle.vala index 7a507c1..0d14417 100644 --- a/src/Widgets/BluetoothToggle.vala +++ b/src/Widgets/BluetoothToggle.vala @@ -9,7 +9,7 @@ public class QuickSettings.BluetoothToggle: SettingsToggle { public BluetoothToggle (DBusObjectManagerClient bluetooth_manager) { Object ( bluetooth_manager: bluetooth_manager, - icon: new ThemedIcon ("quicksettings-bluetooth-active-symbolic"), + icon: new ThemedIcon ("quick-settings-bluetooth-active-symbolic"), label: _("Bluetooth") ); } @@ -98,12 +98,12 @@ public class QuickSettings.BluetoothToggle: SettingsToggle { } if (paired) { - icon = new ThemedIcon ("quicksettings-bluetooth-paired-symbolic"); + icon = new ThemedIcon ("quick-settings-bluetooth-paired-symbolic"); } else { - icon = new ThemedIcon ("quicksettings-bluetooth-active-symbolic"); + icon = new ThemedIcon ("quick-settings-bluetooth-active-symbolic"); } } else { - icon = new ThemedIcon ("quicksettings-bluetooth-disabled-symbolic"); + icon = new ThemedIcon ("quick-settings-bluetooth-disabled-symbolic"); } } From 289925d57ca0e0572cec29d68b07e1dcf95c1286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Tue, 5 Sep 2023 11:24:09 -0700 Subject: [PATCH 9/9] Combine Bluez DBus --- src/DBus/{BluezDevice.vala => Bluez.vala} | 7 ++++++- src/DBus/BluezAdapter.vala | 9 --------- src/Widgets/BluetoothToggle.vala | 16 ++++++++-------- src/meson.build | 3 +-- 4 files changed, 15 insertions(+), 20 deletions(-) rename src/DBus/{BluezDevice.vala => Bluez.vala} (87%) delete mode 100644 src/DBus/BluezAdapter.vala diff --git a/src/DBus/BluezDevice.vala b/src/DBus/Bluez.vala similarity index 87% rename from src/DBus/BluezDevice.vala rename to src/DBus/Bluez.vala index 3dcf3ff..5d3cd67 100644 --- a/src/DBus/BluezDevice.vala +++ b/src/DBus/Bluez.vala @@ -3,8 +3,13 @@ * SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io) */ +[DBus (name = "org.bluez.Adapter1")] +public interface QuickSettings.Bluez.Adapter : Object { + public abstract bool powered { get; set; } +} + [DBus (name = "org.bluez.Device1")] -public interface QuickSettings.BluezDevice : Object { +public interface QuickSettings.Bluez.Device : Object { public abstract void cancel_pairing () throws Error; public abstract async void connect () throws Error; public abstract void connect_profile (string UUID) throws Error; //vala-lint=naming-convention diff --git a/src/DBus/BluezAdapter.vala b/src/DBus/BluezAdapter.vala deleted file mode 100644 index 96ad51f..0000000 --- a/src/DBus/BluezAdapter.vala +++ /dev/null @@ -1,9 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0-or-later - * SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io) - */ - -[DBus (name = "org.bluez.Adapter1")] -public interface QuickSettings.BluezAdapter : Object { - public abstract bool powered { get; set; } -} diff --git a/src/Widgets/BluetoothToggle.vala b/src/Widgets/BluetoothToggle.vala index 0d14417..d23d163 100644 --- a/src/Widgets/BluetoothToggle.vala +++ b/src/Widgets/BluetoothToggle.vala @@ -37,8 +37,8 @@ public class QuickSettings.BluetoothToggle: SettingsToggle { } private void on_interface_added (GLib.DBusObject object, GLib.DBusInterface iface) { - if (iface is QuickSettings.BluezAdapter) { - unowned var adapter = (QuickSettings.BluezAdapter) iface; + if (iface is Bluez.Adapter) { + unowned var adapter = (Bluez.Adapter) iface; ((DBusProxy) adapter).g_properties_changed.connect ((changed, invalid) => { var powered = changed.lookup_value ("Powered", new VariantType ("b")); @@ -46,8 +46,8 @@ public class QuickSettings.BluetoothToggle: SettingsToggle { get_bluetooth_status (); } }); - } else if (iface is QuickSettings.BluezDevice) { - unowned var device = (QuickSettings.BluezDevice) iface; + } else if (iface is Bluez.Device) { + unowned var device = (Bluez.Device) iface; ((DBusProxy) device).g_properties_changed.connect ((changed, invalid) => { var connected = changed.lookup_value ("Connected", new VariantType ("b")); @@ -73,7 +73,7 @@ public class QuickSettings.BluetoothToggle: SettingsToggle { continue; } - if (((QuickSettings.BluezAdapter) iface).powered) { + if (((Bluez.Adapter) iface).powered) { powered = true; break; } @@ -91,7 +91,7 @@ public class QuickSettings.BluetoothToggle: SettingsToggle { continue; } - var device = (QuickSettings.BluezDevice) iface; + var device = (Bluez.Device) iface; if (device.connected) { paired = true; } @@ -114,7 +114,7 @@ public class QuickSettings.BluetoothToggle: SettingsToggle { continue; } - var adapter = (QuickSettings.BluezAdapter) iface; + var adapter = (Bluez.Adapter) iface; if (adapter.powered != status) { adapter.powered = status; } @@ -127,7 +127,7 @@ public class QuickSettings.BluetoothToggle: SettingsToggle { continue; } - var device = (QuickSettings.BluezDevice) iface; + var device = (Bluez.Device) iface; if (device.connected) { try { yield device.disconnect (); diff --git a/src/meson.build b/src/meson.build index 2b5014b..913f7e6 100644 --- a/src/meson.build +++ b/src/meson.build @@ -11,8 +11,7 @@ sources = [ 'Indicator.vala', 'PopoverWidget.vala', 'DBus' / 'AccountsService.vala', - 'DBus' / 'BluezAdapter.vala', - 'DBus' / 'BluezDevice.vala', + 'DBus' / 'Bluez.vala', 'DBus' / 'SensorProxy.vala', 'Views' / 'A11yView.vala', 'Widgets' / 'BluetoothToggle.vala',