From: Vibhav Pant vibhavp@gmail.com
Enable GUID_BLUETOOTHLE_DEVICE_INTERFACE for remote devices that have GATT services, and store them on the PE driver. --- dlls/winebth.sys/dbus.c | 112 ++++++++++++++++++++++++- dlls/winebth.sys/unixlib.c | 9 +++ dlls/winebth.sys/unixlib.h | 7 ++ dlls/winebth.sys/winebluetooth.c | 10 +++ dlls/winebth.sys/winebth.c | 135 ++++++++++++++++++++++++++++++- dlls/winebth.sys/winebth_priv.h | 20 +++++ include/Makefile.in | 1 + include/bthledef.h | 21 +++++ 8 files changed, 309 insertions(+), 6 deletions(-) create mode 100644 include/bthledef.h
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 3c0c21460fd..d20a70852f0 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -119,6 +119,7 @@ const int bluez_timeout = -1; #define BLUEZ_INTERFACE_DEVICE "org.bluez.Device1" #define BLUEZ_INTERFACE_AGENT_MANAGER "org.bluez.AgentManager1" #define BLUEZ_INTERFACE_AGENT "org.bluez.Agent1" +#define BLUEZ_INTERFACE_GATT_SERVICE "org.bluez.GattService1"
#define DO_FUNC( f ) typeof( f ) (*p_##f) DBUS_FUNCS; @@ -306,6 +307,19 @@ static void parse_mac_address( const char *addr_str, BYTE dest[6] ) dest[i] = addr[i]; }
+static BOOL parse_uuid( GUID *guid, const char *str ) +{ + int ret; + if (strlen( str ) != 36) + return FALSE; + ret = sscanf( str, "%08x-%hx-%hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", &guid->Data1, &guid->Data2, &guid->Data3, + &guid->Data4[0], &guid->Data4[1], &guid->Data4[2], &guid->Data4[3], &guid->Data4[4], &guid->Data4[5], + &guid->Data4[6], &guid->Data4[7] ); + if (ret != 11) + return FALSE; + return TRUE; +} + static void bluez_dbus_wait_for_reply_callback( DBusPendingCall *pending_call, void *wait ) { sem_post( wait ); @@ -840,6 +854,8 @@ struct bluez_watcher_ctx struct list initial_radio_list; /* struct bluez_init_entry */ struct list initial_device_list; + /* struct bluez_init_entry */ + struct list initial_gatt_service_list;
/* struct bluez_watcher_event */ struct list event_list; @@ -850,6 +866,7 @@ struct bluez_init_entry union { struct winebluetooth_watcher_event_radio_added radio; struct winebluetooth_watcher_event_device_added device; + struct winebluetooth_watcher_event_gatt_service_added service; } object; struct list entry; }; @@ -1897,6 +1914,10 @@ static void bluez_watcher_free( struct bluez_watcher_ctx *watcher ) break; case BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED: break; + case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED: + unix_name_free( (struct unix_name *)event1->event.gatt_service_added.device.handle ); + unix_name_free( (struct unix_name *)event1->event.gatt_service_added.service.handle ); + break; } free( event1 ); } @@ -1924,6 +1945,7 @@ NTSTATUS bluez_watcher_init( void *connection, void **ctx ) watcher_ctx->init_device_list_call = call; list_init( &watcher_ctx->initial_radio_list ); list_init( &watcher_ctx->initial_device_list ); + list_init( &watcher_ctx->initial_gatt_service_list ); list_init( &watcher_ctx->event_list );
/* The bluez_dbus_loop thread will free up the watcher when the disconnect message is processed (i.e, @@ -1979,7 +2001,7 @@ void bluez_watcher_close( void *connection, void *ctx ) }
static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct list *adapter_list, - struct list *device_list ) + struct list *device_list, struct list *gatt_service_list ) { DBusMessageIter dict, paths_iter, iface_iter, prop_iter; const char *path; @@ -2087,10 +2109,82 @@ static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct lis TRACE( "Found BlueZ org.bluez.Device1 object %s: %p\n", debugstr_a( path ), device_name ); break; } + else if (!strcmp( iface, BLUEZ_INTERFACE_GATT_SERVICE )) + { + struct unix_name *service_name, *device_name = NULL; + struct bluez_init_entry *init_device; + DBusMessageIter variant; + const char *prop_name; + + init_device = calloc( 1, sizeof( *init_device ) ); + if (!init_device) + { + status = STATUS_NO_MEMORY; + goto done; + } + + service_name = unix_name_get_or_create( path ); + if (!service_name) + { + ERR( "Failed to allocate memory for service path %s\n", debugstr_a( path ) ); + break; + } + init_device->object.service.service.handle = (UINT_PTR)service_name; + + while ((prop_name = bluez_next_dict_entry( &prop_iter, &variant ))) + { + if (!strcmp( prop_name, "Device" ) + && p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_OBJECT_PATH ) + { + const char *device_path; + + p_dbus_message_iter_get_basic( &variant, &device_path ); + device_name = unix_name_get_or_create( device_path ); + if (!device_name) + { + unix_name_free( service_name ); + free( init_device ); + ERR( "Failed to allocate memory for device path %s\n", debugstr_a( device_path )); + status = STATUS_NO_MEMORY; + goto done; + } + init_device->object.service.device.handle = (UINT_PTR)device_name; + } + else if (!strcmp( prop_name, "Handle" ) + && p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_UINT16) + p_dbus_message_iter_get_basic( &variant, &init_device->object.service.attr_handle ); + else if (!strcmp( prop_name, "Primary" ) + && p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_BOOLEAN) + { + dbus_bool_t primary; + p_dbus_message_iter_get_basic( &variant, &primary ); + init_device->object.service.is_primary = !!primary; + } + else if (!strcmp( prop_name, "UUID" ) + && p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_STRING) + { + const char *uuid_str; + p_dbus_message_iter_get_basic( &variant, &uuid_str ); + if (!parse_uuid( &init_device->object.service.uuid, uuid_str )) + ERR("Failed to parse UUID %s for GATT service %s\n", debugstr_a( uuid_str ), path ); + } + } + if (!device_name) + { + unix_name_free( service_name ); + free( init_device ); + ERR( "Could not find the associated device for the GATT service %s\n", debugstr_a( path ) ); + break; + } + list_add_tail( gatt_service_list, &init_device->entry ); + TRACE( "Found BlueZ org.bluez.GattService1 object %s %p\n", debugstr_a( path ), service_name ); + break; + } } }
- TRACE( "Initial device list: radios: %d, devices: %d\n", list_count( adapter_list ), list_count( device_list ) ); + TRACE( "Initial device list: radios: %d, devices: %d, GATT services: %d\n", list_count( adapter_list ), + list_count( device_list ), list_count( gatt_service_list ) ); done: return status; } @@ -2119,6 +2213,17 @@ static BOOL bluez_watcher_event_queue_ready( struct bluez_watcher_ctx *ctx, stru free( device ); return TRUE; } + if (!list_empty( &ctx->initial_gatt_service_list )) + { + struct bluez_init_entry *service; + + service = LIST_ENTRY( list_head( &ctx->initial_gatt_service_list ), struct bluez_init_entry, entry ); + event->event_type = BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED; + event->event_data.gatt_service_added = service->object.service; + list_remove( &service->entry ); + free( service ); + return TRUE; + } if (!list_empty( &ctx->event_list )) { struct bluez_watcher_event *watcher_event = @@ -2215,7 +2320,8 @@ NTSTATUS bluez_dbus_loop( void *c, void *watcher, void *auth_agent, return STATUS_NO_MEMORY; } status = bluez_build_initial_device_lists( reply, &watcher_ctx->initial_radio_list, - &watcher_ctx->initial_device_list ); + &watcher_ctx->initial_device_list, + &watcher_ctx->initial_gatt_service_list ); p_dbus_message_unref( reply ); if (status != STATUS_SUCCESS) { diff --git a/dlls/winebth.sys/unixlib.c b/dlls/winebth.sys/unixlib.c index 543c6d2ff1a..5e020464ca4 100644 --- a/dlls/winebth.sys/unixlib.c +++ b/dlls/winebth.sys/unixlib.c @@ -248,6 +248,13 @@ static NTSTATUS bluetooth_device_start_pairing( void *args ) return bluez_device_start_pairing( dbus_connection, bluetooth_watcher, params->device, params->irp ); }
+static NTSTATUS bluetooth_gatt_service_free( void *args ) +{ + struct bluetooth_gatt_service_free_params *params = args; + unix_name_free( params->service ); + return STATUS_SUCCESS; +} + static NTSTATUS bluetooth_get_event( void *args ) { struct bluetooth_get_event_params *params = args; @@ -275,6 +282,8 @@ const unixlib_entry_t __wine_unix_call_funcs[] = { bluetooth_auth_agent_enable_incoming, bluetooth_auth_send_response,
+ bluetooth_gatt_service_free, + bluetooth_get_event, };
diff --git a/dlls/winebth.sys/unixlib.h b/dlls/winebth.sys/unixlib.h index 273023f5b16..42783ccf400 100644 --- a/dlls/winebth.sys/unixlib.h +++ b/dlls/winebth.sys/unixlib.h @@ -54,6 +54,11 @@ struct bluetooth_device_free_params unix_name_t device; };
+struct bluetooth_gatt_service_free_params +{ + unix_name_t service; +}; + struct bluetooth_device_disconnect_params { unix_name_t device; @@ -130,6 +135,8 @@ enum bluetoothapis_funcs unix_bluetooth_auth_agent_enable_incoming, unix_bluetooth_auth_send_response,
+ unix_bluetooth_gatt_service_free, + unix_bluetooth_get_event,
unix_funcs_count diff --git a/dlls/winebth.sys/winebluetooth.c b/dlls/winebth.sys/winebluetooth.c index 448fd3f4dda..add7020a0b3 100644 --- a/dlls/winebth.sys/winebluetooth.c +++ b/dlls/winebth.sys/winebluetooth.c @@ -188,6 +188,16 @@ NTSTATUS winebluetooth_device_start_pairing( winebluetooth_device_t device, IRP return UNIX_BLUETOOTH_CALL( bluetooth_device_start_pairing, &args ); }
+void winebluetooth_gatt_service_free( winebluetooth_gatt_service_t service ) +{ + struct bluetooth_gatt_service_free_params args = {0}; + + TRACE( "(%p)\n", (void *)service.handle ); + + args.service = service.handle; + UNIX_BLUETOOTH_CALL( bluetooth_gatt_service_free, &args ); +} + NTSTATUS winebluetooth_get_event( struct winebluetooth_event *result ) { struct bluetooth_get_event_params params = {0}; diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 3c277b873d7..4b19d13c239 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -33,6 +33,7 @@ #include <bthsdpdef.h> #include <bluetoothapis.h> #include <bthdef.h> +#include <bthledef.h> #include <winioctl.h> #include <bthioctl.h> #include <ddk/wdm.h> @@ -97,7 +98,23 @@ struct bluetooth_remote_device CRITICAL_SECTION props_cs; winebluetooth_device_props_mask_t props_mask; /* Guarded by props_cs */ struct winebluetooth_device_properties props; /* Guarded by props_cs */ + unsigned int started : 1; /* Whether the device has been started. Guarded by props_cs */ unsigned int removed : 1; + + CRITICAL_SECTION le_cs; + unsigned int le : 1; /* Guarded by le_cs */ + UNICODE_STRING bthle_symlink_name; /* Guarded by le_cs */ + struct list gatt_services; /* Guarded by le_cs */ +}; + +struct bluetooth_gatt_service +{ + struct list entry; + + winebluetooth_gatt_service_t service; + GUID uuid; + unsigned int primary : 1; + UINT16 handle; };
enum bluetooth_pdo_ext_type @@ -713,7 +730,11 @@ static void bluetooth_radio_add_remote_device( struct winebluetooth_watcher_even ext->remote_device.props_mask = event.known_props_mask; ext->remote_device.props = event.props; ext->remote_device.removed = 0; + ext->remote_device.started = 0;
+ InitializeCriticalSection( &ext->remote_device.le_cs ); + ext->remote_device.le = 0; + list_init( &ext->remote_device.gatt_services );
if (!event.init_entry) { @@ -920,6 +941,84 @@ static void complete_irp( IRP *irp, NTSTATUS result ) IoCompleteRequest( irp, IO_NO_INCREMENT ); }
+static void bluetooth_device_create_le_symlink( struct bluetooth_remote_device *device ) +{ + EnterCriticalSection( &device->props_cs ); + /* The device hasn't been started by the PnP manager yet. Set le, and let remote_device_pdo_pnp enable the + * interface. */ + if (!device->started) + { + EnterCriticalSection( &device->le_cs ); + device->le = 1; + LeaveCriticalSection( &device->le_cs ); + LeaveCriticalSection( &device->props_cs ); + return; + } + LeaveCriticalSection( &device->props_cs ); + + EnterCriticalSection( &device->le_cs ); + if (device->le) + { + LeaveCriticalSection( &device->le_cs ); + return; + } + device->le = 1; + if (!IoRegisterDeviceInterface( device->device_obj, &GUID_BLUETOOTHLE_DEVICE_INTERFACE, NULL, + &device->bthle_symlink_name )) + IoSetDeviceInterfaceState( &device->bthle_symlink_name, TRUE ); + LeaveCriticalSection( &device->le_cs ); +} + +static void bluetooth_device_add_gatt_service( struct winebluetooth_watcher_event_gatt_service_added event ) +{ + struct bluetooth_radio *radio; + + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY( radio, &device_list, struct bluetooth_radio, entry ) + { + struct bluetooth_remote_device *device; + + EnterCriticalSection( &radio->remote_devices_cs ); + LIST_FOR_EACH_ENTRY( device, &radio->remote_devices, struct bluetooth_remote_device, entry ) + { + if (winebluetooth_device_equal( event.device, device->device )) + { + struct bluetooth_gatt_service *service; + + TRACE( "Adding GATT service %s for remote device %p\n", debugstr_guid( &event.uuid ), + (void *)event.device.handle ); + + service = calloc( 1, sizeof( *service ) ); + if (!service) + { + LeaveCriticalSection( &radio->remote_devices_cs ); + LeaveCriticalSection( &device_list_cs ); + return; + } + + service->service = event.service; + service->uuid = event.uuid; + service->primary = !!event.is_primary; + service->handle = event.attr_handle; + bluetooth_device_create_le_symlink( device ); + + EnterCriticalSection( &device->le_cs ); + list_add_tail( &device->gatt_services, &service->entry ); + LeaveCriticalSection( &device->le_cs ); + LeaveCriticalSection( &radio->remote_devices_cs ); + LeaveCriticalSection( &device_list_cs ); + winebluetooth_device_free( event.device ); + return; + } + } + LeaveCriticalSection( &radio->remote_devices_cs ); + } + LeaveCriticalSection( &device_list_cs ); + + winebluetooth_device_free( event.device ); + winebluetooth_gatt_service_free( event.service ); +} + static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) { NTSTATUS status; @@ -958,6 +1057,8 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) case BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED: complete_irp( event->event_data.pairing_finished.irp, event->event_data.pairing_finished.result ); + case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED: + bluetooth_device_add_gatt_service( event->event_data.gatt_service_added ); break; default: FIXME( "Unknown bluetooth watcher event code: %#x\n", event->event_type ); @@ -1173,6 +1274,27 @@ static void remove_pending_irps( struct bluetooth_radio *radio ) } }
+static void remote_device_destroy( struct bluetooth_remote_device *ext ) +{ + struct bluetooth_gatt_service *svc, *next; + + if (ext->le && ext->bthle_symlink_name.Buffer) + { + IoSetDeviceInterfaceState( &ext->bthle_symlink_name, FALSE ); + RtlFreeUnicodeString( &ext->bthle_symlink_name ); + } + DeleteCriticalSection( &ext->props_cs ); + DeleteCriticalSection( &ext->le_cs ); + winebluetooth_device_free( ext->device ); + LIST_FOR_EACH_ENTRY_SAFE( svc, next, &ext->gatt_services, struct bluetooth_gatt_service, entry ) + { + winebluetooth_gatt_service_free( svc->service ); + list_remove( &svc->entry ); + free( svc ); + } + IoDeleteDevice( ext->device_obj ); +} + static NTSTATUS WINAPI remote_device_pdo_pnp( DEVICE_OBJECT *device_obj, struct bluetooth_remote_device *ext, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); @@ -1201,6 +1323,15 @@ static NTSTATUS WINAPI remote_device_pdo_pnp( DEVICE_OBJECT *device_obj, struct BLUETOOTH_ADDRESS addr;
EnterCriticalSection( &ext->props_cs ); + EnterCriticalSection( &ext->le_cs ); + if (ext->le) + { + if (!IoRegisterDeviceInterface( device_obj, &GUID_BLUETOOTHLE_DEVICE_INTERFACE, NULL, + &ext->bthle_symlink_name )) + IoSetDeviceInterfaceState( &ext->bthle_symlink_name, TRUE ); + } + LeaveCriticalSection( &ext->le_cs ); + ext->started = 1; addr = ext->props.address; LeaveCriticalSection( &ext->props_cs ); swprintf( addr_str, ARRAY_SIZE( addr_str ), L"%02x%02x%02x%02x%02x%02x", addr.rgBytes[0], addr.rgBytes[1], @@ -1213,9 +1344,7 @@ static NTSTATUS WINAPI remote_device_pdo_pnp( DEVICE_OBJECT *device_obj, struct case IRP_MN_REMOVE_DEVICE: { assert( ext->removed ); - DeleteCriticalSection( &ext->props_cs ); - winebluetooth_device_free( ext->device ); - IoDeleteDevice( ext->device_obj ); + remote_device_destroy( ext ); ret = STATUS_SUCCESS; break; } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index 5addf6303ec..afacccdfb2c 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -187,6 +187,11 @@ struct winebluetooth_device_properties UINT32 class; };
+typedef struct +{ + UINT_PTR handle; +} winebluetooth_gatt_service_t; + NTSTATUS winebluetooth_radio_get_unique_name( winebluetooth_radio_t radio, char *name, SIZE_T *size ); void winebluetooth_radio_free( winebluetooth_radio_t radio ); @@ -215,6 +220,9 @@ NTSTATUS winebluetooth_device_disconnect( winebluetooth_device_t device ); NTSTATUS winebluetooth_auth_send_response( winebluetooth_device_t device, BLUETOOTH_AUTHENTICATION_METHOD method, UINT32 numeric_or_passkey, BOOL negative, BOOL *authenticated ); NTSTATUS winebluetooth_device_start_pairing( winebluetooth_device_t device, IRP *irp ); + +void winebluetooth_gatt_service_free( winebluetooth_gatt_service_t service ); + enum winebluetooth_watcher_event_type { BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, @@ -224,6 +232,7 @@ enum winebluetooth_watcher_event_type BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_REMOVED, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED, BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED, + BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED, };
struct winebluetooth_watcher_event_radio_added @@ -271,6 +280,16 @@ struct winebluetooth_watcher_event_pairing_finished NTSTATUS result; };
+struct winebluetooth_watcher_event_gatt_service_added +{ + winebluetooth_device_t device; + winebluetooth_gatt_service_t service; + + UINT16 attr_handle; + BOOL is_primary; + GUID uuid; +}; + union winebluetooth_watcher_event_data { struct winebluetooth_watcher_event_radio_added radio_added; @@ -280,6 +299,7 @@ union winebluetooth_watcher_event_data struct winebluetooth_watcher_event_device_removed device_removed; struct winebluetooth_watcher_event_device_props_changed device_props_changed; struct winebluetooth_watcher_event_pairing_finished pairing_finished; + struct winebluetooth_watcher_event_gatt_service_added gatt_service_added; };
struct winebluetooth_watcher_event diff --git a/include/Makefile.in b/include/Makefile.in index 4446d4821d6..d9190ec2599 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -59,6 +59,7 @@ SOURCES = \ bitsmsg.h \ bluetoothapis.h \ bthdef.h \ + bthledef.h \ bthioctl.h \ bthsdpdef.h \ cderr.h \ diff --git a/include/bthledef.h b/include/bthledef.h new file mode 100644 index 00000000000..f689260c7fe --- /dev/null +++ b/include/bthledef.h @@ -0,0 +1,21 @@ +/* + * Copyright 2025 Vibhav Pant + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#ifndef __BTHLEDEFS_H +#define __BTHLEDEFS_H +DEFINE_GUID( GUID_BLUETOOTHLE_DEVICE_INTERFACE, 0x781aee18, 0x7733, 0x4ce4, 0xad, 0xd0, 0x91, 0xf4, 0x1c, 0x67, 0xb5, 0x92 ); +#endif