Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions data/icons/bluetooth/active.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions data/icons/bluetooth/disabled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions data/icons/bluetooth/paired.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions data/quick-settings.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
<gresource prefix="org/elementary/wingpanel/icons">
<file alias="scalable/status/dark-mode-symbolic.svg" compressed="true" preprocess="xml-stripblanks">icons/dark-mode.svg</file>
<file alias="scalable/status/quick-settings-symbolic.svg" compressed="true" preprocess="xml-stripblanks">icons/quick-settings.svg</file>

<file alias="scalable/status/quick-settings-bluetooth-active-symbolic.svg" compressed="true" preprocess="xml-stripblanks">icons/bluetooth/active.svg</file>
<file alias="scalable/status/quick-settings-bluetooth-disabled-symbolic.svg" compressed="true" preprocess="xml-stripblanks">icons/bluetooth/disabled.svg</file>
<file alias="scalable/status/quick-settings-bluetooth-paired-symbolic.svg" compressed="true" preprocess="xml-stripblanks">icons/bluetooth/paired.svg</file>

<file alias="scalable/status/quick-settings-rotation-allowed-symbolic.svg" compressed="true" preprocess="xml-stripblanks">icons/rotation-allowed.svg</file>
<file alias="scalable/status/quick-settings-rotation-locked-symbolic.svg" compressed="true" preprocess="xml-stripblanks">icons/rotation-locked.svg</file>
</gresource>
Expand Down
35 changes: 35 additions & 0 deletions src/DBus/Bluez.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SPDX-License-Identifier: GPL-3.0-or-later
* 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.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
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; }
}
49 changes: 49 additions & 0 deletions src/PopoverWidget.vala
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ public class QuickSettings.PopoverWidget : Gtk.Box {
}
});

setup_bluetooth.begin ((obj, res) => {
var bluetooth_manager = setup_bluetooth.end (res);
if (bluetooth_manager != null) {
var bluetooth_toggle = new BluetoothToggle (bluetooth_manager);

toggle_box.add (bluetooth_toggle);
show_all ();
}
});

setup_sensor_proxy.begin ((obj, res) => {
var sensor_proxy = setup_sensor_proxy.end (res);
if (sensor_proxy.has_accelerometer) {
Expand Down Expand Up @@ -144,6 +154,45 @@ public class QuickSettings.PopoverWidget : Gtk.Box {
}
}

//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 ();

//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:
return typeof (GLib.DBusProxy);
}
}

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;
}
}

private async SensorProxy? setup_sensor_proxy () {
try {
return yield Bus.get_proxy (BusType.SYSTEM, "net.hadess.SensorProxy", "/net/hadess/SensorProxy");
Expand Down
141 changes: 141 additions & 0 deletions src/Widgets/BluetoothToggle.vala
Original file line number Diff line number Diff line change
@@ -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 ("quick-settings-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 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"));
if (powered != null) {
get_bluetooth_status ();
}
});
} 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"));
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 (((Bluez.Adapter) 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 = (Bluez.Device) iface;
if (device.connected) {
paired = true;
}
}

if (paired) {
icon = new ThemedIcon ("quick-settings-bluetooth-paired-symbolic");
} else {
icon = new ThemedIcon ("quick-settings-bluetooth-active-symbolic");
}
} else {
icon = new ThemedIcon ("quick-settings-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 = (Bluez.Adapter) 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 = (Bluez.Device) iface;
if (device.connected) {
try {
yield device.disconnect ();
} catch (Error e) {
critical (e.message);
}
}
}
}
}
}
2 changes: 2 additions & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ sources = [
'Indicator.vala',
'PopoverWidget.vala',
'DBus' / 'AccountsService.vala',
'DBus' / 'Bluez.vala',
'DBus' / 'SensorProxy.vala',
'Views' / 'A11yView.vala',
'Widgets' / 'BluetoothToggle.vala',
'Widgets' / 'DarkModeToggle.vala',
'Widgets' / 'RotationToggle.vala',
'Widgets' / 'SettingsToggle.vala'
Expand Down