summaryrefslogtreecommitdiff
path: root/csrc/services/bluetooth-service.c
diff options
context:
space:
mode:
authorHannes Schulze <haschu0103@outlook.de>2025-06-19 23:17:58 +0200
committerHannes Schulze <haschu0103@outlook.de>2025-06-19 23:17:58 +0200
commit1af3d14f3f995645a0de08a54b244c19840709e9 (patch)
tree3c4098863d8a8fd004a2acc8912a23b6036ab4dd /csrc/services/bluetooth-service.c
parent40efcf60a5f39674b5137f11de7bcfe37fb121b5 (diff)
init hs project
Diffstat (limited to 'csrc/services/bluetooth-service.c')
-rw-r--r--csrc/services/bluetooth-service.c949
1 files changed, 949 insertions, 0 deletions
diff --git a/csrc/services/bluetooth-service.c b/csrc/services/bluetooth-service.c
new file mode 100644
index 0000000..eb13968
--- /dev/null
+++ b/csrc/services/bluetooth-service.c
@@ -0,0 +1,949 @@
+#include "services/bluetooth-service.h"
+#include "dbus/bluez.h"
+#include <gtk/gtk.h>
+
+//
+// FoobarBluetoothDeviceState:
+//
+// The current connection state of a remote bluetooth device.
+//
+
+G_DEFINE_ENUM_TYPE(
+ FoobarBluetoothDeviceState,
+ foobar_bluetooth_device_state,
+ G_DEFINE_ENUM_VALUE( FOOBAR_BLUETOOTH_DEVICE_STATE_DISCONNECTED, "disconnected" ),
+ G_DEFINE_ENUM_VALUE( FOOBAR_BLUETOOTH_DEVICE_STATE_CONNECTING, "connecting" ),
+ G_DEFINE_ENUM_VALUE( FOOBAR_BLUETOOTH_DEVICE_STATE_CONNECTED, "connected" ) )
+
+//
+// FoobarBluetoothDevice:
+//
+// Represents an external bluetooth device the client can connect to.
+//
+
+struct _FoobarBluetoothDevice
+{
+ GObject parent_instance;
+ FoobarBluetoothService* service;
+ FoobarBluetoothDeviceState state;
+ FoobarBluezDevice* device;
+ GCancellable* connect_cancellable;
+ gulong notify_handler_id;
+};
+
+enum
+{
+ DEVICE_PROP_NAME = 1,
+ DEVICE_PROP_STATE,
+ N_DEVICE_PROPS,
+};
+
+static GParamSpec* device_props[N_DEVICE_PROPS] = { 0 };
+
+static void foobar_bluetooth_device_class_init ( FoobarBluetoothDeviceClass* klass );
+static void foobar_bluetooth_device_init ( FoobarBluetoothDevice* self );
+static void foobar_bluetooth_device_get_property ( GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec );
+static void foobar_bluetooth_device_finalize ( GObject* object );
+static FoobarBluetoothDevice* foobar_bluetooth_device_new ( FoobarBluetoothService* service,
+ FoobarBluezDevice* device );
+static void foobar_bluetooth_device_handle_notify( GObject* object,
+ GParamSpec* pspec,
+ gpointer userdata );
+static void foobar_bluetooth_device_connect_cb ( GObject* object,
+ GAsyncResult* result,
+ gpointer userdata );
+static void foobar_bluetooth_device_disconnect_cb( GObject* object,
+ GAsyncResult* result,
+ gpointer userdata );
+static void foobar_bluetooth_device_update_state ( FoobarBluetoothDevice* self );
+
+G_DEFINE_FINAL_TYPE( FoobarBluetoothDevice, foobar_bluetooth_device, G_TYPE_OBJECT )
+
+//
+// FoobarBluetoothService:
+//
+// Service managing the state of the bluetooth adapter. This is implemented using the Bluez DBus API.
+//
+
+struct _FoobarBluetoothService
+{
+ GObject parent_instance;
+ GDBusObjectManager* object_manager;
+ GListStore* devices;
+ GtkFilterListModel* filtered_devices;
+ GtkSortListModel* sorted_devices;
+ GtkFilterListModel* connected_devices;
+ GPtrArray* adapters; // First adapter is the default one.
+ FoobarBluezAdapter* default_adapter;
+ gulong interface_added_handler_id;
+ gulong interface_removed_handler_id;
+ gulong object_added_handler_id;
+ gulong object_removed_handler_id;
+ gulong adapter_notify_handler_id;
+};
+
+enum
+{
+ PROP_DEVICES = 1,
+ PROP_CONNECTED_DEVICES,
+ PROP_IS_AVAILABLE,
+ PROP_IS_ENABLED,
+ PROP_IS_SCANNING,
+ N_PROPS,
+};
+
+static GParamSpec* props[N_PROPS] = { 0 };
+
+static void foobar_bluetooth_service_class_init ( FoobarBluetoothServiceClass* klass );
+static void foobar_bluetooth_service_init ( FoobarBluetoothService* self );
+static void foobar_bluetooth_service_get_property ( GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec );
+static void foobar_bluetooth_service_set_property ( GObject* object,
+ guint prop_id,
+ GValue const* value,
+ GParamSpec* pspec );
+static void foobar_bluetooth_service_finalize ( GObject* object );
+static void foobar_bluetooth_service_handle_interface_added ( GDBusObjectManager* manager,
+ GDBusObject* object,
+ GDBusInterface* interface,
+ gpointer userdata );
+static void foobar_bluetooth_service_handle_interface_removed( GDBusObjectManager* manager,
+ GDBusObject* object,
+ GDBusInterface* interface,
+ gpointer userdata );
+static void foobar_bluetooth_service_handle_object_added ( GDBusObjectManager* manager,
+ GDBusObject* object,
+ gpointer userdata );
+static void foobar_bluetooth_service_handle_object_removed ( GDBusObjectManager* manager,
+ GDBusObject* object,
+ gpointer userdata );
+static void foobar_bluetooth_service_handle_adapter_notify ( GObject* object,
+ GParamSpec* pspec,
+ gpointer userdata );
+static void foobar_bluetooth_service_update_default_adapter ( FoobarBluetoothService* self );
+static GType foobar_bluetooth_service_dbus_proxy_type_cb ( GDBusObjectManagerClient* manager,
+ gchar const* object_path,
+ gchar const* interface_name,
+ gpointer userdata );
+static gboolean foobar_bluetooth_service_filter_func ( gpointer item,
+ gpointer userdata );
+static gboolean foobar_bluetooth_service_connected_filter_func ( gpointer item,
+ gpointer userdata );
+static gint foobar_bluetooth_service_sort_func ( gconstpointer item_a,
+ gconstpointer item_b,
+ gpointer userdata );
+
+G_DEFINE_FINAL_TYPE( FoobarBluetoothService, foobar_bluetooth_service, G_TYPE_OBJECT )
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Device
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Static initialization for devices.
+//
+void foobar_bluetooth_device_class_init( FoobarBluetoothDeviceClass* klass )
+{
+ GObjectClass* object_klass = G_OBJECT_CLASS( klass );
+ object_klass->get_property = foobar_bluetooth_device_get_property;
+ object_klass->finalize = foobar_bluetooth_device_finalize;
+
+ device_props[DEVICE_PROP_NAME] = g_param_spec_string(
+ "name",
+ "Name",
+ "Human-readable name for the device.",
+ NULL,
+ G_PARAM_READABLE );
+ device_props[DEVICE_PROP_STATE] = g_param_spec_enum(
+ "state",
+ "State",
+ "Current connection state of the device.",
+ FOOBAR_TYPE_BLUETOOTH_DEVICE_STATE,
+ 0,
+ G_PARAM_READABLE );
+ g_object_class_install_properties( object_klass, N_DEVICE_PROPS, device_props );
+}
+
+//
+// Instance initialization for devices.
+//
+void foobar_bluetooth_device_init( FoobarBluetoothDevice* self )
+{
+ (void)self;
+}
+
+//
+// Property getter implementation, mapping a property id to a method.
+//
+void foobar_bluetooth_device_get_property(
+ GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec )
+{
+ FoobarBluetoothDevice* self = (FoobarBluetoothDevice*)object;
+
+ switch ( prop_id )
+ {
+ case DEVICE_PROP_NAME:
+ g_value_set_string( value, foobar_bluetooth_device_get_name( self ) );
+ break;
+ case DEVICE_PROP_STATE:
+ g_value_set_enum( value, foobar_bluetooth_device_get_state( self ) );
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
+ break;
+ }
+}
+
+//
+// Instance cleanup for devices.
+//
+void foobar_bluetooth_device_finalize( GObject* object )
+{
+ FoobarBluetoothDevice* self = (FoobarBluetoothDevice*)object;
+
+ g_clear_signal_handler( &self->notify_handler_id, self->device );
+ g_clear_object( &self->device );
+ g_clear_object( &self->connect_cancellable );
+
+ G_OBJECT_CLASS( foobar_bluetooth_device_parent_class )->finalize( object );
+}
+
+//
+// Create a new device object, wrapping a DBus device instance.
+//
+FoobarBluetoothDevice* foobar_bluetooth_device_new(
+ FoobarBluetoothService* service,
+ FoobarBluezDevice* device )
+{
+ FoobarBluetoothDevice* self = g_object_new( FOOBAR_TYPE_BLUETOOTH_DEVICE, NULL );
+
+ self->service = service;
+ self->device = g_object_ref( device );
+ foobar_bluetooth_device_update_state( self );
+
+ self->notify_handler_id = g_signal_connect(
+ self->device, "notify", G_CALLBACK( foobar_bluetooth_device_handle_notify ), self );
+
+ return self;
+}
+
+//
+// Get the name/alias for the device.
+//
+gchar const* foobar_bluetooth_device_get_name( FoobarBluetoothDevice* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_BLUETOOTH_DEVICE( self ), NULL );
+ return foobar_bluez_device_get_alias( self->device );
+}
+
+//
+// Get the current connection state for the device.
+//
+FoobarBluetoothDeviceState foobar_bluetooth_device_get_state( FoobarBluetoothDevice* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_BLUETOOTH_DEVICE( self ), 0 );
+ return self->state;
+}
+
+//
+// Toggle whether the device is connected:
+//
+// - If the device is currently disconnected, it is connected.
+// - If the device is currently being connected, that process is aborted.
+// - If the device is currently connected, it is disconnected.
+//
+void foobar_bluetooth_device_toggle_connection( FoobarBluetoothDevice* self )
+{
+ g_return_if_fail( FOOBAR_IS_BLUETOOTH_DEVICE( self ) );
+
+ switch ( self->state )
+ {
+ case FOOBAR_BLUETOOTH_DEVICE_STATE_DISCONNECTED:
+ self->connect_cancellable = g_cancellable_new( );
+ foobar_bluetooth_device_update_state( self );
+ foobar_bluez_device_call_connect(
+ self->device,
+ self->connect_cancellable,
+ foobar_bluetooth_device_connect_cb,
+ g_object_ref( self ) );
+ break;
+ case FOOBAR_BLUETOOTH_DEVICE_STATE_CONNECTING:
+ g_cancellable_cancel( self->connect_cancellable );
+ break;
+ case FOOBAR_BLUETOOTH_DEVICE_STATE_CONNECTED:
+ foobar_bluez_device_call_disconnect(
+ self->device,
+ NULL,
+ foobar_bluetooth_device_disconnect_cb,
+ g_object_ref( self ) );
+ break;
+ default:
+ g_warn_if_reached( );
+ }
+}
+
+//
+// Called by the underlying device when one of its properties changes.
+//
+void foobar_bluetooth_device_handle_notify(
+ GObject* object,
+ GParamSpec* pspec,
+ gpointer userdata )
+{
+ (void)object;
+ FoobarBluetoothDevice* self = (FoobarBluetoothDevice*)userdata;
+
+ gchar const* property = g_param_spec_get_name( pspec );
+ if ( !g_strcmp0( property, "alias" ) )
+ {
+ g_object_notify_by_pspec( G_OBJECT( self ), device_props[DEVICE_PROP_NAME] );
+ }
+ else if ( !g_strcmp0( property, "name" ) || !g_strcmp0( property, "adapter" ) )
+ {
+ if ( self->service )
+ {
+ gtk_filter_changed(
+ gtk_filter_list_model_get_filter( self->service->filtered_devices ),
+ GTK_FILTER_CHANGE_DIFFERENT );
+ }
+ }
+ else if ( !g_strcmp0( property, "connected" ) )
+ {
+ foobar_bluetooth_device_update_state( self );
+ }
+}
+
+//
+// Called when a request to connect the device is completed.
+//
+// This is mainly used to update the device's state.
+//
+void foobar_bluetooth_device_connect_cb(
+ GObject* object,
+ GAsyncResult* result,
+ gpointer userdata )
+{
+ (void)object;
+ g_autoptr( FoobarBluetoothDevice ) self = (FoobarBluetoothDevice*)userdata;
+
+ g_autoptr( GError ) error = NULL;
+ if ( !foobar_bluez_device_call_connect_finish( self->device, result, &error ) )
+ {
+ g_warning( "Unable to connect bluetooth device: %s", error->message );
+ }
+
+ g_clear_object( &self->connect_cancellable );
+ foobar_bluetooth_device_update_state( self );
+}
+
+//
+// Called when a request to disconnect the device is completed.
+//
+void foobar_bluetooth_device_disconnect_cb(
+ GObject* object,
+ GAsyncResult* result,
+ gpointer userdata )
+{
+ (void)object;
+ g_autoptr( FoobarBluetoothDevice ) self = (FoobarBluetoothDevice*)userdata;
+
+ g_autoptr( GError ) error = NULL;
+ if ( !foobar_bluez_device_call_disconnect_finish( self->device, result, &error ) )
+ {
+ g_warning( "Unable to disconnect bluetooth device: %s", error->message );
+ }
+}
+
+//
+// Update the current device state.
+//
+// The connect_cancellable member variable is used to indicate an ongoing connection attempt. Otherwise, the value of
+// is_connected is used.
+//
+void foobar_bluetooth_device_update_state( FoobarBluetoothDevice* self )
+{
+ FoobarBluetoothDeviceState new_state = foobar_bluez_device_get_connected( self->device )
+ ? FOOBAR_BLUETOOTH_DEVICE_STATE_CONNECTED
+ : FOOBAR_BLUETOOTH_DEVICE_STATE_DISCONNECTED;
+ if ( self->connect_cancellable )
+ {
+ new_state = FOOBAR_BLUETOOTH_DEVICE_STATE_CONNECTING;
+ }
+
+ if ( self->state != new_state )
+ {
+ self->state = new_state;
+ g_object_notify_by_pspec( G_OBJECT( self ), device_props[DEVICE_PROP_STATE] );
+
+ if ( self->service )
+ {
+ gtk_filter_changed(
+ gtk_filter_list_model_get_filter( self->service->connected_devices ),
+ GTK_FILTER_CHANGE_DIFFERENT );
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Service Implementation
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Static initialization for the bluetooth service.
+//
+void foobar_bluetooth_service_class_init( FoobarBluetoothServiceClass* klass )
+{
+ GObjectClass* object_klass = G_OBJECT_CLASS( klass );
+ object_klass->get_property = foobar_bluetooth_service_get_property;
+ object_klass->set_property = foobar_bluetooth_service_set_property;
+ object_klass->finalize = foobar_bluetooth_service_finalize;
+
+ props[PROP_DEVICES] = g_param_spec_object(
+ "devices",
+ "Devices",
+ "Sorted list of available bluetooth devices.",
+ G_TYPE_LIST_MODEL,
+ G_PARAM_READABLE );
+ props[PROP_CONNECTED_DEVICES] = g_param_spec_object(
+ "connected-devices",
+ "Connected Devices",
+ "Sorted list of currently connected bluetooth devices.",
+ G_TYPE_LIST_MODEL,
+ G_PARAM_READABLE );
+ props[PROP_IS_AVAILABLE] = g_param_spec_boolean(
+ "is-available",
+ "Is Available",
+ "Indicates whether the system has a bluetooth adapter.",
+ FALSE,
+ G_PARAM_READABLE );
+ props[PROP_IS_ENABLED] = g_param_spec_boolean(
+ "is-enabled",
+ "Is Enabled",
+ "Indicates whether the bluetooth adapter is currently enabled.",
+ FALSE,
+ G_PARAM_READWRITE );
+ props[PROP_IS_SCANNING] = g_param_spec_boolean(
+ "is-scanning",
+ "Is Scanning",
+ "Indicates whether the bluetooth adapter is currently scanning for devices.",
+ FALSE,
+ G_PARAM_READWRITE );
+ g_object_class_install_properties( object_klass, N_PROPS, props );
+}
+
+//
+// Instance initialization for the bluetooth service.
+//
+void foobar_bluetooth_service_init( FoobarBluetoothService* self )
+{
+ self->devices = g_list_store_new( FOOBAR_TYPE_BLUETOOTH_DEVICE );
+
+ GtkCustomFilter* filter = gtk_custom_filter_new( foobar_bluetooth_service_filter_func, self, NULL );
+ self->filtered_devices = gtk_filter_list_model_new(
+ G_LIST_MODEL( g_object_ref( self->devices ) ),
+ GTK_FILTER( filter ) );
+
+ GtkCustomSorter* sorter = gtk_custom_sorter_new( foobar_bluetooth_service_sort_func, NULL, NULL );
+ self->sorted_devices = gtk_sort_list_model_new(
+ G_LIST_MODEL( g_object_ref( self->filtered_devices ) ),
+ GTK_SORTER( sorter ) );
+
+ GtkCustomFilter* connected_filter = gtk_custom_filter_new( foobar_bluetooth_service_connected_filter_func, NULL, NULL );
+ self->connected_devices = gtk_filter_list_model_new(
+ G_LIST_MODEL( g_object_ref( self->sorted_devices ) ),
+ GTK_FILTER( connected_filter ) );
+
+ self->adapters = g_ptr_array_new_with_free_func( g_object_unref );
+
+ g_autoptr( GError ) error = NULL;
+ self->object_manager = g_dbus_object_manager_client_new_for_bus_sync(
+ G_BUS_TYPE_SYSTEM,
+ G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START,
+ "org.bluez",
+ "/",
+ foobar_bluetooth_service_dbus_proxy_type_cb,
+ NULL,
+ NULL,
+ NULL,
+ &error );
+
+ if ( self->object_manager )
+ {
+ self->interface_added_handler_id = g_signal_connect(
+ self->object_manager,
+ "interface-added",
+ G_CALLBACK( foobar_bluetooth_service_handle_interface_added ),
+ self );
+ self->interface_removed_handler_id = g_signal_connect(
+ self->object_manager,
+ "interface-removed",
+ G_CALLBACK( foobar_bluetooth_service_handle_interface_removed ),
+ self );
+ self->object_added_handler_id = g_signal_connect(
+ self->object_manager,
+ "object-added",
+ G_CALLBACK( foobar_bluetooth_service_handle_object_added ),
+ self );
+ self->object_removed_handler_id = g_signal_connect(
+ self->object_manager,
+ "object-removed",
+ G_CALLBACK( foobar_bluetooth_service_handle_object_removed ),
+ self );
+
+ g_autolist( GDBusObject ) objects = g_dbus_object_manager_get_objects( self->object_manager );
+ for ( GList* it = objects; it; it = it->next )
+ {
+ foobar_bluetooth_service_handle_object_added( self->object_manager, it->data, self );
+ }
+ }
+ else
+ {
+ g_warning( "Unable to create a DBus object manager for communication with Bluez: %s", error->message );
+ }
+}
+
+//
+// Property getter implementation, mapping a property id to a method.
+//
+void foobar_bluetooth_service_get_property(
+ GObject* object,
+ guint prop_id,
+ GValue* value,
+ GParamSpec* pspec )
+{
+ FoobarBluetoothService* self = (FoobarBluetoothService*)object;
+
+ switch ( prop_id )
+ {
+ case PROP_DEVICES:
+ g_value_set_object( value, foobar_bluetooth_service_get_devices( self ) );
+ break;
+ case PROP_CONNECTED_DEVICES:
+ g_value_set_object( value, foobar_bluetooth_service_get_connected_devices( self ) );
+ break;
+ case PROP_IS_AVAILABLE:
+ g_value_set_boolean( value, foobar_bluetooth_service_is_available( self ) );
+ break;
+ case PROP_IS_ENABLED:
+ g_value_set_boolean( value, foobar_bluetooth_service_is_enabled( self ) );
+ break;
+ case PROP_IS_SCANNING:
+ g_value_set_boolean( value, foobar_bluetooth_service_is_scanning( self ) );
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
+ break;
+ }
+}
+
+//
+// Property setter implementation, mapping a property id to a method.
+//
+void foobar_bluetooth_service_set_property(
+ GObject* object,
+ guint prop_id,
+ GValue const* value,
+ GParamSpec* pspec )
+{
+ FoobarBluetoothService* self = (FoobarBluetoothService*)object;
+
+ switch ( prop_id )
+ {
+ case PROP_IS_ENABLED:
+ foobar_bluetooth_service_set_enabled( self, g_value_get_boolean( value ) );
+ break;
+ case PROP_IS_SCANNING:
+ foobar_bluetooth_service_set_scanning( self, g_value_get_boolean( value ) );
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID( object, prop_id, pspec );
+ break;
+ }
+}
+
+//
+// Instance cleanup for the bluetooth service.
+//
+void foobar_bluetooth_service_finalize( GObject* object )
+{
+ FoobarBluetoothService* self = (FoobarBluetoothService*)object;
+
+ for ( guint i = 0; i < g_list_model_get_n_items( G_LIST_MODEL( self->devices ) ); ++i )
+ {
+ FoobarBluetoothDevice* device = g_list_model_get_item( G_LIST_MODEL( self->devices ), i );
+ device->service = NULL;
+ }
+
+ g_clear_signal_handler( &self->adapter_notify_handler_id, self->default_adapter );
+ g_clear_signal_handler( &self->interface_added_handler_id, self->object_manager );
+ g_clear_signal_handler( &self->interface_removed_handler_id, self->object_manager );
+ g_clear_signal_handler( &self->object_added_handler_id, self->object_manager );
+ g_clear_signal_handler( &self->object_removed_handler_id, self->object_manager );
+ g_clear_object( &self->object_manager );
+ g_clear_object( &self->connected_devices );
+ g_clear_object( &self->sorted_devices );
+ g_clear_object( &self->filtered_devices );
+ g_clear_object( &self->devices );
+ g_clear_object( &self->default_adapter );
+ g_clear_pointer( &self->adapters, g_ptr_array_unref );
+
+ G_OBJECT_CLASS( foobar_bluetooth_service_parent_class )->finalize( object );
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Public API
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Create a new bluetooth service instance.
+//
+FoobarBluetoothService* foobar_bluetooth_service_new( void )
+{
+ return g_object_new( FOOBAR_TYPE_BLUETOOTH_SERVICE, NULL );
+}
+
+//
+// Get the sorted list of available bluetooth devices.
+//
+GListModel* foobar_bluetooth_service_get_devices( FoobarBluetoothService* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_BLUETOOTH_SERVICE( self ), NULL );
+ return G_LIST_MODEL( self->sorted_devices );
+}
+
+//
+// Get the sorted list of currently connected bluetooth devices.
+//
+GListModel* foobar_bluetooth_service_get_connected_devices( FoobarBluetoothService* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_BLUETOOTH_SERVICE( self ), NULL );
+ return G_LIST_MODEL( self->connected_devices );
+}
+
+//
+// Check whether the system has a bluetooth adapter.
+//
+gboolean foobar_bluetooth_service_is_available( FoobarBluetoothService* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_BLUETOOTH_SERVICE( self ), FALSE );
+ return self->default_adapter != NULL;
+}
+
+//
+// Check whether the bluetooth adapter is currently enabled.
+//
+gboolean foobar_bluetooth_service_is_enabled( FoobarBluetoothService* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_BLUETOOTH_SERVICE( self ), FALSE );
+ return self->default_adapter ? foobar_bluez_adapter_get_powered( self->default_adapter ) : FALSE;
+}
+
+//
+// Check whether the bluetooth adapter is currently scanning for devices.
+//
+gboolean foobar_bluetooth_service_is_scanning( FoobarBluetoothService* self )
+{
+ g_return_val_if_fail( FOOBAR_IS_BLUETOOTH_SERVICE( self ), FALSE );
+ return self->default_adapter ? foobar_bluez_adapter_get_discovering( self->default_adapter ) : FALSE;
+}
+
+//
+// Update whether the bluetooth adapter is currently enabled.
+//
+void foobar_bluetooth_service_set_enabled(
+ FoobarBluetoothService* self,
+ gboolean value )
+{
+ g_return_if_fail( FOOBAR_IS_BLUETOOTH_SERVICE( self ) );
+
+ if (self->default_adapter)
+ {
+ foobar_bluez_adapter_set_powered( self->default_adapter, value );
+ }
+}
+
+//
+// Update whether the bluetooth adapter is currently scanning for devices.
+//
+void foobar_bluetooth_service_set_scanning(
+ FoobarBluetoothService* self,
+ gboolean value )
+{
+ g_return_if_fail( FOOBAR_IS_BLUETOOTH_SERVICE( self ) );
+
+ g_autoptr( GError ) error = NULL;
+
+ if (self->default_adapter && foobar_bluetooth_service_is_scanning( self ) != value)
+ {
+ if (value)
+ {
+ // Make this device discoverable while discovery is active.
+
+ GVariantBuilder filter_builder;
+ g_variant_builder_init( &filter_builder, G_VARIANT_TYPE_VARDICT );
+ g_variant_builder_add( &filter_builder, "{sv}", "Discoverable", g_variant_new_boolean( TRUE ) );
+ GVariant* filter = g_variant_builder_end( &filter_builder );
+ if ( !foobar_bluez_adapter_call_set_discovery_filter_sync( self->default_adapter, filter, NULL, &error ) )
+ {
+ g_warning( "Failed to set discovery filter: %s", error->message );
+ return;
+ }
+ if ( !foobar_bluez_adapter_call_start_discovery_sync( self->default_adapter, NULL, &error ) )
+ {
+ g_warning( "Failed to start discovery: %s", error->message );
+ }
+ }
+ else
+ {
+ if ( !foobar_bluez_adapter_call_stop_discovery_sync( self->default_adapter, NULL, &error ) )
+ {
+ g_warning( "Failed to stop discovery: %s", error->message );
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Signal Handlers
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Called when an interface was added to an existing DBus object (either adapter or device).
+//
+void foobar_bluetooth_service_handle_interface_added(
+ GDBusObjectManager* manager,
+ GDBusObject* object,
+ GDBusInterface* interface,
+ gpointer userdata )
+{
+ (void)manager;
+ (void)object;
+ FoobarBluetoothService* self = (FoobarBluetoothService*)userdata;
+
+ if ( FOOBAR_IS_BLUEZ_DEVICE( interface ) )
+ {
+ g_autoptr( FoobarBluetoothDevice ) device = foobar_bluetooth_device_new(
+ self,
+ FOOBAR_BLUEZ_DEVICE( interface ) );
+ g_list_store_append( self->devices, device );
+ }
+ else if ( FOOBAR_IS_BLUEZ_ADAPTER( interface ) )
+ {
+ g_ptr_array_add( self->adapters, g_object_ref( interface ) );
+ foobar_bluetooth_service_update_default_adapter( self );
+ }
+}
+
+//
+// Called when an interface was removed from an existing DBus object (either adapter or device).
+//
+void foobar_bluetooth_service_handle_interface_removed(
+ GDBusObjectManager* manager,
+ GDBusObject* object,
+ GDBusInterface* interface,
+ gpointer userdata )
+{
+ (void)manager;
+ FoobarBluetoothService* self = (FoobarBluetoothService*)userdata;
+
+ gchar const* removed_path = g_dbus_object_get_object_path( object );
+ if ( FOOBAR_IS_BLUEZ_DEVICE( interface ) )
+ {
+ for ( guint i = 0; i < g_list_model_get_n_items( G_LIST_MODEL( self->devices ) ); ++i )
+ {
+ FoobarBluetoothDevice* device = g_list_model_get_item( G_LIST_MODEL( self->devices ), i );
+ gchar const* path = g_dbus_proxy_get_object_path( G_DBUS_PROXY( device->device ) );
+ if ( !g_strcmp0( path, removed_path ) )
+ {
+ device->service = NULL;
+ g_list_store_remove( self->devices, i );
+ break;
+ }
+ }
+ }
+ else if ( FOOBAR_IS_BLUEZ_ADAPTER( interface ) )
+ {
+ for ( guint i = 0; i < self->adapters->len; ++i )
+ {
+ FoobarBluezAdapter* adapter = g_ptr_array_index( self->adapters, i );
+ gchar const* path = g_dbus_proxy_get_object_path( G_DBUS_PROXY( adapter ) );
+ if ( !g_strcmp0( path, removed_path ) )
+ {
+ g_ptr_array_remove_index( self->adapters, i );
+ break;
+ }
+ }
+ foobar_bluetooth_service_update_default_adapter( self );
+ }
+}
+
+//
+// Called when a DBus object was added (either adapter or device).
+//
+void foobar_bluetooth_service_handle_object_added(
+ GDBusObjectManager* manager,
+ GDBusObject* object,
+ gpointer userdata )
+{
+ g_autolist( GDBusInterface ) interfaces = g_dbus_object_get_interfaces( object );
+ for ( GList* it = interfaces; it; it = it->next )
+ {
+ foobar_bluetooth_service_handle_interface_added( manager, object, it->data, userdata );
+ }
+}
+
+//
+// Called when a DBus object was removed (either adapter or device).
+//
+void foobar_bluetooth_service_handle_object_removed(
+ GDBusObjectManager* manager,
+ GDBusObject* object,
+ gpointer userdata )
+{
+ g_autolist( GDBusInterface ) interfaces = g_dbus_object_get_interfaces( object );
+ for ( GList* it = interfaces; it; it = it->next )
+ {
+ foobar_bluetooth_service_handle_interface_removed( manager, object, it->data, userdata );
+ }
+}
+
+//
+// Called when a property of the default adapter has changed.
+//
+void foobar_bluetooth_service_handle_adapter_notify(
+ GObject* object,
+ GParamSpec* pspec,
+ gpointer userdata )
+{
+ (void)object;
+ FoobarBluetoothService* self = (FoobarBluetoothService*)userdata;
+
+ gchar const* property = g_param_spec_get_name( pspec );
+ if ( !g_strcmp0( property, "powered" ) )
+ {
+ g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_ENABLED] );
+ }
+ else if ( !g_strcmp0( property, "discovering" ) )
+ {
+ g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_SCANNING] );
+ }
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+// Helper Methods
+// ---------------------------------------------------------------------------------------------------------------------
+
+//
+// Update the default_adapter to match the first item in the adapters array.
+//
+void foobar_bluetooth_service_update_default_adapter( FoobarBluetoothService* self )
+{
+ FoobarBluezAdapter* new_adapter = ( self->adapters->len > 0 ) ? g_ptr_array_index( self->adapters, 0 ) : NULL;
+
+ if ( self->default_adapter != new_adapter )
+ {
+ g_clear_signal_handler( &self->adapter_notify_handler_id, self->default_adapter );
+ g_clear_object( &self->default_adapter );
+
+ if ( new_adapter )
+ {
+ self->default_adapter = g_object_ref( new_adapter );
+ self->adapter_notify_handler_id = g_signal_connect(
+ self->default_adapter,
+ "notify",
+ G_CALLBACK( foobar_bluetooth_service_handle_adapter_notify ),
+ self );
+ }
+
+ gtk_filter_changed( gtk_filter_list_model_get_filter( self->filtered_devices ), GTK_FILTER_CHANGE_DIFFERENT );
+ g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_AVAILABLE] );
+ g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_ENABLED] );
+ g_object_notify_by_pspec( G_OBJECT( self ), props[PROP_IS_SCANNING] );
+ }
+}
+
+//
+// Get the type for creating a DBus proxy object.
+//
+// This is used because the DBus object manager should give us instances of the FoobarBluezAdapterProxy and
+// FoobarBluezDeviceProxy types.
+//
+GType foobar_bluetooth_service_dbus_proxy_type_cb(
+ GDBusObjectManagerClient* manager,
+ gchar const* object_path,
+ gchar const* interface_name,
+ gpointer userdata )
+{
+ if ( !interface_name )
+ {
+ return G_TYPE_DBUS_OBJECT_PROXY;
+ }
+
+ if ( !g_strcmp0( interface_name, "org.bluez.Device1" ) )
+ {
+ return FOOBAR_TYPE_BLUEZ_DEVICE_PROXY;
+ }
+
+ if ( !g_strcmp0( interface_name, "org.bluez.Adapter1" ) )
+ {
+ return FOOBAR_TYPE_BLUEZ_ADAPTER_PROXY;
+ }
+
+ return G_TYPE_DBUS_PROXY;
+}
+
+//
+// Filtering callback for the list of devices, only showing devices from the default adapter.
+//
+gboolean foobar_bluetooth_service_filter_func(
+ gpointer item,
+ gpointer userdata )
+{
+ FoobarBluetoothService* self = (FoobarBluetoothService*)userdata;
+ FoobarBluetoothDevice* device = item;
+
+ gchar const* device_adapter = foobar_bluez_device_get_adapter( device->device );
+ gchar const* default_adapter = self->default_adapter
+ ? g_dbus_proxy_get_object_path( G_DBUS_PROXY( self->default_adapter ) )
+ : NULL;
+ gchar const* name = foobar_bluez_device_get_name( device->device );
+ return name != NULL && !g_strcmp0( device_adapter, default_adapter );
+}
+
+//
+// Filtering callback for the list of connected devices.
+//
+gboolean foobar_bluetooth_service_connected_filter_func(
+ gpointer item,
+ gpointer userdata )
+{
+ (void)userdata;
+
+ FoobarBluetoothDevice* device = item;
+ return foobar_bluetooth_device_get_state( device ) == FOOBAR_BLUETOOTH_DEVICE_STATE_CONNECTED;
+}
+
+//
+// Sorting callback for the list of devices.
+//
+// Devices are sorted by their names.
+//
+gint foobar_bluetooth_service_sort_func(
+ gconstpointer item_a,
+ gconstpointer item_b,
+ gpointer userdata )
+{
+ (void)userdata;
+
+ FoobarBluetoothDevice* device_a = (FoobarBluetoothDevice*)item_a;
+ FoobarBluetoothDevice* device_b = (FoobarBluetoothDevice*)item_b;
+ return g_strcmp0( foobar_bluetooth_device_get_name( device_a ), foobar_bluetooth_device_get_name( device_b ) );
+}