summaryrefslogtreecommitdiff
path: root/lib/bluetooth
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bluetooth')
-rw-r--r--lib/bluetooth/adapter.vala89
-rw-r--r--lib/bluetooth/bluetooth.vala181
-rw-r--r--lib/bluetooth/config.vala.in6
-rw-r--r--lib/bluetooth/device.vala106
-rw-r--r--lib/bluetooth/meson.build79
-rw-r--r--lib/bluetooth/utils.vala21
-rw-r--r--lib/bluetooth/version1
7 files changed, 483 insertions, 0 deletions
diff --git a/lib/bluetooth/adapter.vala b/lib/bluetooth/adapter.vala
new file mode 100644
index 0000000..0c9d00e
--- /dev/null
+++ b/lib/bluetooth/adapter.vala
@@ -0,0 +1,89 @@
+namespace AstalBluetooth {
+[DBus (name = "org.bluez.Adapter1")]
+internal interface IAdapter : DBusProxy {
+ public abstract void remove_device(ObjectPath device) throws Error;
+ public abstract void start_discovery() throws Error;
+ public abstract void stop_discovery() throws Error;
+
+ public abstract string[] uuids { owned get; }
+ public abstract bool discoverable { get; set; }
+ public abstract bool discovering { get; }
+ public abstract bool pairable { get; set; }
+ public abstract bool powered { get; set; }
+ public abstract string address { owned get; }
+ public abstract string alias { owned get; set; }
+ public abstract string modalias { owned get; }
+ public abstract string name { owned get; }
+ public abstract uint class { get; }
+ public abstract uint discoverable_timeout { get; set; }
+ public abstract uint pairable_timeout { get; set; }
+}
+
+public class Adapter : Object {
+ private IAdapter proxy;
+ public string object_path { owned get; construct set; }
+
+ internal Adapter(IAdapter proxy) {
+ this.proxy = proxy;
+ this.object_path = proxy.g_object_path;
+ proxy.g_properties_changed.connect((props) => {
+ var map = (HashTable<string, Variant>)props;
+ foreach (var key in map.get_keys()) {
+ var prop = kebab_case(key);
+ if (get_class().find_property(prop) != null) {
+ notify_property(prop);
+ }
+ }
+ });
+ }
+
+ public string[] uuids { owned get { return proxy.uuids; } }
+ public bool discovering { get { return proxy.discovering; } }
+ public string modalias { owned get { return proxy.modalias; } }
+ public string name { owned get { return proxy.name; } }
+ public uint class { get { return proxy.class; } }
+ public string address { owned get { return proxy.address; } }
+
+ public bool discoverable {
+ get { return proxy.discoverable; }
+ set { proxy.discoverable = value; }
+ }
+
+ public bool pairable {
+ get { return proxy.pairable; }
+ set { proxy.pairable = value; }
+ }
+
+ public bool powered {
+ get { return proxy.powered; }
+ set { proxy.powered = value; }
+ }
+
+ public string alias {
+ owned get { return proxy.alias; }
+ set { proxy.alias = value; }
+ }
+
+ public uint discoverable_timeout {
+ get { return proxy.discoverable_timeout; }
+ set { proxy.discoverable_timeout = value; }
+ }
+
+ public uint pairable_timeout {
+ get { return proxy.pairable_timeout; }
+ set { proxy.pairable_timeout = value; }
+ }
+
+ public void remove_device(Device device) {
+ try { proxy.remove_device((ObjectPath)device.object_path); } catch (Error err) { critical(err.message); }
+ }
+
+ public void start_discovery() {
+ try { proxy.start_discovery(); } catch (Error err) { critical(err.message); }
+ }
+
+ public void stop_discovery() {
+ try { proxy.stop_discovery(); } catch (Error err) { critical(err.message); }
+ }
+}
+}
diff --git a/lib/bluetooth/bluetooth.vala b/lib/bluetooth/bluetooth.vala
new file mode 100644
index 0000000..ce086ba
--- /dev/null
+++ b/lib/bluetooth/bluetooth.vala
@@ -0,0 +1,181 @@
+namespace AstalBluetooth {
+public Bluetooth get_default() {
+ return Bluetooth.get_default();
+}
+
+public class Bluetooth : Object {
+ private static Bluetooth _instance;
+
+ public static Bluetooth get_default() {
+ if (_instance == null)
+ _instance = new Bluetooth();
+
+ return _instance;
+ }
+
+ private DBusObjectManagerClient manager;
+
+ private HashTable<string, Adapter> _adapters =
+ new HashTable<string, Adapter>(str_hash, str_equal);
+
+ private HashTable<string, Device> _devices =
+ new HashTable<string, Device>(str_hash, str_equal);
+
+ public signal void device_added (Device device) {
+ notify_property("devices");
+ }
+
+ public signal void device_removed (Device device) {
+ notify_property("devices");
+ }
+
+ public signal void adapter_added (Adapter adapter) {
+ notify_property("adapters");
+ }
+
+ public signal void adapter_removed (Adapter adapter) {
+ notify_property("adapters");
+ }
+
+ public bool is_powered { get; private set; default = false; }
+ public bool is_connected { get; private set; default = false; }
+ public Adapter? adapter { get { return adapters.nth_data(0); } }
+
+ public List<weak Adapter> adapters {
+ owned get { return _adapters.get_values(); }
+ }
+
+ public List<weak Device> devices {
+ owned get { return _devices.get_values(); }
+ }
+
+ construct {
+ try {
+ manager = new DBusObjectManagerClient.for_bus_sync(
+ BusType.SYSTEM,
+ DBusObjectManagerClientFlags.NONE,
+ "org.bluez",
+ "/",
+ manager_proxy_get_type,
+ null
+ );
+
+ foreach (var object in manager.get_objects()) {
+ foreach (var iface in object.get_interfaces()) {
+ on_interface_added(object, iface);
+ }
+ }
+
+ manager.interface_added.connect(on_interface_added);
+ manager.interface_removed.connect(on_interface_removed);
+
+ manager.object_added.connect((object) => {
+ foreach (var iface in object.get_interfaces()) {
+ on_interface_added(object, iface);
+ }
+ });
+
+ manager.object_removed.connect((object) => {
+ foreach (var iface in object.get_interfaces()) {
+ on_interface_removed(object, iface);
+ }
+ });
+ } catch (Error err) {
+ critical(err.message);
+ }
+ }
+
+ public void toggle() {
+ adapter.powered = !adapter.powered;
+ }
+
+ [CCode (cname="astal_bluetooth_idevice_proxy_get_type")]
+ extern static GLib.Type get_idevice_proxy_type();
+
+ [CCode (cname="astal_bluetooth_iadapter_proxy_get_type")]
+ extern static GLib.Type get_iadapter_proxy_type();
+
+ private Type manager_proxy_get_type(DBusObjectManagerClient _, string object_path, string? interface_name) {
+ if (interface_name == null)
+ return typeof(DBusObjectProxy);
+
+ switch (interface_name) {
+ case "org.bluez.Device1":
+ return get_idevice_proxy_type();
+ case "org.bluez.Adapter1":
+ return get_iadapter_proxy_type();
+ default:
+ return typeof(DBusProxy);
+ }
+ }
+
+ private void on_interface_added(DBusObject object, DBusInterface iface) {
+ if (iface is IDevice) {
+ var device = new Device((IDevice)iface);
+ _devices.set(device.object_path, device);
+ device_added(device);
+ device.notify.connect(sync);
+ sync();
+ }
+
+ if (iface is IAdapter) {
+ var adapter = new Adapter((IAdapter)iface);
+ _adapters.set(adapter.object_path, adapter);
+ adapter_added(adapter);
+ adapter.notify.connect(sync);
+ sync();
+ }
+ }
+
+ private void on_interface_removed (DBusObject object, DBusInterface iface) {
+ if (iface is IDevice) {
+ unowned var device = (IDevice)iface;
+ device_removed(_devices.get(device.g_object_path));
+ _devices.remove(device.g_object_path);
+ }
+
+ if (iface is IAdapter) {
+ unowned var adapter = (IAdapter)iface;
+ adapter_removed(_adapters.get(adapter.g_object_path));
+ _adapters.remove(adapter.g_object_path);
+ }
+
+ sync();
+ }
+
+ private void sync() {
+ var powered = get_powered();
+ var connected = get_connected();
+
+ if (powered != is_powered || connected != is_connected) {
+ if (powered != is_powered) {
+ is_powered = powered;
+ }
+
+ if (connected != is_connected) {
+ is_connected = connected;
+ }
+ }
+ }
+
+ private bool get_powered() {
+ foreach (var adapter in adapters) {
+ if (adapter.powered) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private bool get_connected() {
+ foreach (var device in devices) {
+ if (device.connected) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+}
diff --git a/lib/bluetooth/config.vala.in b/lib/bluetooth/config.vala.in
new file mode 100644
index 0000000..9fce720
--- /dev/null
+++ b/lib/bluetooth/config.vala.in
@@ -0,0 +1,6 @@
+namespace AstalBluetooth {
+ public const int MAJOR_VERSION = @MAJOR_VERSION@;
+ public const int MINOR_VERSION = @MINOR_VERSION@;
+ public const int MICRO_VERSION = @MICRO_VERSION@;
+ public const string VERSION = "@VERSION@";
+}
diff --git a/lib/bluetooth/device.vala b/lib/bluetooth/device.vala
new file mode 100644
index 0000000..8fe086f
--- /dev/null
+++ b/lib/bluetooth/device.vala
@@ -0,0 +1,106 @@
+namespace AstalBluetooth {
+[DBus (name = "org.bluez.Device1")]
+internal interface IDevice : DBusProxy {
+ public abstract void cancel_pairing() throws Error;
+ public abstract async void connect() throws Error;
+ public abstract void connect_profile(string uuid) throws Error;
+ public abstract async void disconnect() throws Error;
+ public abstract void disconnect_profile(string uuid) throws Error;
+ public abstract void pair() throws Error;
+
+ public abstract string[] uuids { owned get; }
+ public abstract bool blocked { get; set; }
+ public abstract bool connected { get; }
+ public abstract bool legacy_pairing { get; }
+ public abstract bool paired { get; }
+ public abstract bool trusted { get; set; }
+ public abstract int16 rssi { 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 { get; }
+ public abstract uint32 class { get; }
+}
+
+public class Device : Object {
+ private IDevice proxy;
+ public string object_path { owned get; construct set; }
+
+ internal Device(IDevice proxy) {
+ this.proxy = proxy;
+ this.object_path = proxy.g_object_path;
+ proxy.g_properties_changed.connect((props) => {
+ var map = (HashTable<string, Variant>)props;
+ foreach (var key in map.get_keys()) {
+ var prop = kebab_case(key);
+ if (get_class().find_property(prop) != null) {
+ notify_property(prop);
+ }
+ }
+ });
+ }
+
+ public string[] uuids { owned get { return proxy.uuids; } }
+ public bool connected { get { return proxy.connected; } }
+ public bool legacy_pairing { get { return proxy.legacy_pairing; } }
+ public bool paired { get { return proxy.paired; } }
+ public int16 rssi { get { return proxy.rssi; } }
+ public ObjectPath adapter { owned get { return proxy.adapter; } }
+ public string address { owned get { return proxy.address; } }
+ public string icon { owned get { return proxy.icon; } }
+ public string modalias { owned get { return proxy.modalias; } }
+ public string name { owned get { return proxy.name; } }
+ public uint16 appearance { get { return proxy.appearance; } }
+ public uint32 class { get { return proxy.class; } }
+ public bool connecting { get; private set; }
+
+ public bool blocked {
+ get { return proxy.blocked; }
+ set { proxy.blocked = value; }
+ }
+
+ public bool trusted {
+ get { return proxy.trusted; }
+ set { proxy.trusted = value; }
+ }
+
+ public string alias {
+ owned get { return proxy.alias; }
+ set { proxy.alias = value; }
+ }
+
+ public void cancel_pairing() {
+ try { proxy.cancel_pairing(); } catch (Error err) { critical(err.message); }
+ }
+
+ public async void connect_device() {
+ try {
+ connecting = true;
+ yield proxy.connect();
+ } catch (Error err) {
+ critical(err.message);
+ } finally {
+ connecting = false;
+ }
+ }
+
+ public async void disconnect_device() {
+ try { yield proxy.disconnect(); } catch (Error err) { critical(err.message); }
+ }
+
+ public void connect_profile(string uuid) {
+ try { proxy.connect_profile(uuid); } catch (Error err) { critical(err.message); }
+ }
+
+ public void disconnect_profile(string uuid) {
+ try { proxy.disconnect_profile(uuid); } catch (Error err) { critical(err.message); }
+ }
+
+ public void pair() {
+ try { proxy.pair(); } catch (Error err) { critical(err.message); }
+ }
+}
+}
diff --git a/lib/bluetooth/meson.build b/lib/bluetooth/meson.build
new file mode 100644
index 0000000..934d380
--- /dev/null
+++ b/lib/bluetooth/meson.build
@@ -0,0 +1,79 @@
+project(
+ 'astal-bluetooth',
+ 'vala',
+ 'c',
+ version: run_command('cat', join_paths(meson.project_source_root(), 'version')).stdout().strip(),
+ meson_version: '>= 0.62.0',
+ default_options: [
+ 'warning_level=2',
+ 'werror=false',
+ 'c_std=gnu11',
+ ],
+)
+
+version_split = meson.project_version().split('.')
+api_version = version_split[0] + '.' + version_split[1]
+gir = 'AstalBluetooth-' + api_version + '.gir'
+typelib = 'AstalBluetooth-' + api_version + '.typelib'
+
+config = configure_file(
+ input: 'config.vala.in',
+ output: 'config.vala',
+ configuration: {
+ 'VERSION': meson.project_version(),
+ 'MAJOR_VERSION': version_split[0],
+ 'MINOR_VERSION': version_split[1],
+ 'MICRO_VERSION': version_split[2],
+ },
+)
+
+deps = [
+ dependency('glib-2.0'),
+ dependency('gobject-2.0'),
+ dependency('gio-2.0'),
+]
+
+sources = [
+ config,
+ 'utils.vala',
+ 'device.vala',
+ 'adapter.vala',
+ 'bluetooth.vala',
+]
+
+lib = library(
+ meson.project_name(),
+ sources,
+ dependencies: deps,
+ vala_header: meson.project_name() + '.h',
+ vala_vapi: meson.project_name() + '-' + api_version + '.vapi',
+ vala_gir: gir,
+ version: meson.project_version(),
+ install: true,
+ install_dir: [true, true, true, true],
+)
+
+import('pkgconfig').generate(
+ lib,
+ name: meson.project_name(),
+ filebase: meson.project_name() + '-' + api_version,
+ version: meson.project_version(),
+ subdirs: meson.project_name(),
+ requires: deps,
+ install_dir: get_option('libdir') / 'pkgconfig',
+)
+
+custom_target(
+ typelib,
+ command: [
+ find_program('g-ir-compiler'),
+ '--output', '@OUTPUT@',
+ '--shared-library', get_option('prefix') / get_option('libdir') / '@PLAINNAME@',
+ meson.current_build_dir() / gir,
+ ],
+ input: lib,
+ output: typelib,
+ depends: lib,
+ install: true,
+ install_dir: get_option('libdir') / 'girepository-1.0',
+)
diff --git a/lib/bluetooth/utils.vala b/lib/bluetooth/utils.vala
new file mode 100644
index 0000000..5dcaff6
--- /dev/null
+++ b/lib/bluetooth/utils.vala
@@ -0,0 +1,21 @@
+namespace AstalBluetooth {
+internal string kebab_case(string pascal_case) {
+ StringBuilder kebab_case = new StringBuilder();
+
+ for (int i = 0; i < pascal_case.length; i++) {
+ char c = pascal_case[i];
+
+ if (c >= 'A' && c <= 'Z') {
+ if (i != 0) {
+ kebab_case.append_c('-');
+ }
+
+ kebab_case.append_c((char)(c + 32));
+ } else {
+ kebab_case.append_c(c);
+ }
+ }
+
+ return kebab_case.str;
+}
+}
diff --git a/lib/bluetooth/version b/lib/bluetooth/version
new file mode 100644
index 0000000..6e8bf73
--- /dev/null
+++ b/lib/bluetooth/version
@@ -0,0 +1 @@
+0.1.0