diff options
Diffstat (limited to 'lib/bluetooth')
-rw-r--r-- | lib/bluetooth/adapter.vala | 89 | ||||
-rw-r--r-- | lib/bluetooth/bluetooth.vala | 181 | ||||
-rw-r--r-- | lib/bluetooth/config.vala.in | 6 | ||||
-rw-r--r-- | lib/bluetooth/device.vala | 106 | ||||
-rw-r--r-- | lib/bluetooth/meson.build | 79 | ||||
-rw-r--r-- | lib/bluetooth/utils.vala | 21 | ||||
-rw-r--r-- | lib/bluetooth/version | 1 |
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 |