This MR adds support for getting a Bluetooth adapter's properties from its corresponding `org.bluez.Adapter1` object, and making them available to userspace via device properties and the `IOCTL_BTH_GET_LOCAL_INFO` ioctl, updating these properties whenever a `PropertiesChanged` signal is received for the adapter.
It also adds code for creating and removing radio PDOs on receiving `InterafacesAdded` and `InterfacesRemoved` signals from BlueZ, respectively.
-- v4: winebth.sys: Implement IOCTL_BTH_GET_LOCAL_INFO. winebth.sys: Update radio PDO properties on receiving PropertiesChanged for an org.bluez.Adapter1 object. winebth.sys: Remove the corresponding radio PDO on receiving InterfacesRemoved for a org.bluez.Adapter1 object. winebth.sys: Create new radio PDOs on receiving InterfacesAdded for objects that implement org.bluez.Adapter1. winebth.sys: Set radio PDO properties from the device's corresponding org.bluez.Adapter1 object properties.
From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/dbus.c | 99 +++++++++++++++++++++++++++++++++ dlls/winebth.sys/winebth.c | 47 ++++++++++++++++ dlls/winebth.sys/winebth_priv.h | 46 +++++++++++++++ 3 files changed, 192 insertions(+)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 6c74c377d5c..758f5e2be1b 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -148,6 +148,97 @@ static NTSTATUS bluez_get_objects_async( DBusConnection *connection, DBusPending DBUS_DICT_ENTRY_END_CHAR_AS_STRING \ DBUS_DICT_ENTRY_END_CHAR_AS_STRING
+ +static void parse_mac_address( const char *addr_str, BYTE dest[6] ) +{ + int addr[6], i; + + sscanf( addr_str, "%x:%x:%x:%x:%x:%x", &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], + &addr[5] ); + for (i = 0 ; i < 6; i++) + dest[i] = addr[i]; +} + +static void bluez_radio_prop_from_dict_entry( const char *prop_name, DBusMessageIter *variant, + struct winebluetooth_radio_properties *props, + winebluetooth_radio_props_mask_t *props_mask, + winebluetooth_radio_props_mask_t wanted_props_mask ) +{ + TRACE_(dbus)( "(%s, %p, %p, %p, %#x)\n", debugstr_a( prop_name ), variant, props, props_mask, + wanted_props_mask ); + + if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS && + !strcmp( prop_name, "Address" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING) + { + const char *addr_str; + p_dbus_message_iter_get_basic( variant, &addr_str ); + parse_mac_address( addr_str, props->address.rgBytes ); + *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS; + } + else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_CLASS && + !strcmp( prop_name, "Class" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT32) + { + dbus_uint32_t class; + p_dbus_message_iter_get_basic( variant, &class ); + props->class = class; + *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_CLASS; + } + else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER && + !strcmp( prop_name, "Manufacturer" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT16) + { + dbus_uint16_t manufacturer; + p_dbus_message_iter_get_basic( variant, &manufacturer ); + props->manufacturer = manufacturer; + *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER; + } + else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE && + !strcmp( prop_name, "Connectable" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN) + { + dbus_bool_t connectable; + p_dbus_message_iter_get_basic( variant, &connectable ); + props->connectable = connectable != 0; + *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE; + } + else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE && + !strcmp( prop_name, "Discoverable" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN) + { + dbus_bool_t discoverable; + p_dbus_message_iter_get_basic( variant, &discoverable ); + props->discoverable = discoverable != 0; + *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE; + } + else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING && + !strcmp( prop_name, "Discovering") && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN) + { + dbus_bool_t discovering; + p_dbus_message_iter_get_basic( variant, &discovering ); + props->discovering = discovering != 0; + *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING; + } + else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE && + !strcmp( prop_name, "Pairable" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN) + { + dbus_bool_t pairable; + p_dbus_message_iter_get_basic( variant, &pairable ); + props->pairable = pairable != 0; + *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE; + } + else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_VERSION && + !strcmp( prop_name, "Version" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BYTE) + { + p_dbus_message_iter_get_basic( variant, &props->version ); + *props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_VERSION; + } +} + struct bluez_watcher_ctx { void *init_device_list_call; @@ -253,6 +344,8 @@ static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct lis { if (!strcmp( iface, BLUEZ_INTERFACE_ADAPTER )) { + const char *prop_name; + DBusMessageIter variant; struct bluez_init_entry *init_device = calloc( 1, sizeof( *init_device ) ); struct unix_name *radio_name;
@@ -268,6 +361,12 @@ static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct lis status = STATUS_NO_MEMORY; goto done; } + while ((prop_name = bluez_next_dict_entry( &prop_iter, &variant ))) + { + bluez_radio_prop_from_dict_entry( + prop_name, &variant, &init_device->object.radio.props, + &init_device->object.radio.props_mask, WINEBLUETOOTH_RADIO_ALL_PROPERTIES ); + } init_device->object.radio.radio.handle = (UINT_PTR)radio_name; list_add_tail( adapter_list, &init_device->entry ); TRACE( "Found BlueZ org.bluez.Adapter1 object %s: %p\n", diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index ca8b1b4dadb..38f716a72a1 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -27,6 +27,7 @@ #include <windef.h> #include <winbase.h> #include <winternl.h> +#include <winnls.h> #include <initguid.h> #include <devpkey.h> #include <bthdef.h> @@ -66,6 +67,9 @@ struct bluetooth_radio BOOL removed;
DEVICE_OBJECT *device_obj; + CRITICAL_SECTION props_cs; + winebluetooth_radio_props_mask_t props_mask; /* Guarded by props_cs */ + struct winebluetooth_radio_properties props; /* Guarded by props_cs */ winebluetooth_radio_t radio; WCHAR *hw_name; UNICODE_STRING bthport_symlink_name; @@ -175,6 +179,10 @@ static void add_bluetooth_radio( struct winebluetooth_watcher_event_radio_added device->radio = event.radio; device->removed = FALSE; device->hw_name = hw_name; + device->props = event.props; + device->props_mask = event.props_mask; + + InitializeCriticalSection( &device->props_cs );
EnterCriticalSection( &device_list_cs ); list_add_tail( &device_list, &device->entry ); @@ -329,6 +337,41 @@ static NTSTATUS query_id(const struct bluetooth_radio *ext, IRP *irp, BUS_QUERY_ return STATUS_SUCCESS; }
+/* Caller must hold props_cs */ +static void bluetooth_radio_set_properties( DEVICE_OBJECT *obj, + winebluetooth_radio_props_mask_t mask, + struct winebluetooth_radio_properties *props ) +{ + if (mask & WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS) + { + union + { + UINT64 uint; + BYTE addr[8]; + } radio_addr = {0}; + memcpy( &radio_addr.addr[2], props->address.rgBytes, sizeof( props->address.rgBytes ) ); + IoSetDevicePropertyData( obj, &DEVPKEY_BluetoothRadio_Address, LOCALE_NEUTRAL, 0, + DEVPROP_TYPE_UINT64, sizeof( radio_addr ), &radio_addr ); + } + if (mask & WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER) + { + UINT16 manufacturer = props->manufacturer; + IoSetDevicePropertyData( obj, &DEVPKEY_BluetoothRadio_Manufacturer, LOCALE_NEUTRAL, + 0, DEVPROP_TYPE_UINT16, sizeof( manufacturer ), &manufacturer ); + } + if (mask & WINEBLUETOOTH_RADIO_PROPERTY_NAME) + { + WCHAR buf[BLUETOOTH_MAX_NAME_SIZE * sizeof(WCHAR)]; + INT ret; + + if ((ret = MultiByteToWideChar( CP_ACP, 0, props->name, -1, buf, BLUETOOTH_MAX_NAME_SIZE))) + IoSetDevicePropertyData( obj, &DEVPKEY_NAME, LOCALE_NEUTRAL, 0, DEVPROP_TYPE_STRING, ret, buf ); + } + if (mask & WINEBLUETOOTH_RADIO_PROPERTY_VERSION) + IoSetDevicePropertyData( obj, &DEVPKEY_BluetoothRadio_LMPVersion, LOCALE_NEUTRAL, 0, DEVPROP_TYPE_BYTE, + sizeof( props->version ), &props->version ); +} + static NTSTATUS WINAPI pdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); @@ -351,6 +394,10 @@ static NTSTATUS WINAPI pdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) break; } case IRP_MN_START_DEVICE: + EnterCriticalSection( &device->props_cs ); + bluetooth_radio_set_properties( device_obj, device->props_mask, &device->props ); + LeaveCriticalSection( &device->props_cs ); + if (IoRegisterDeviceInterface( device_obj, &GUID_BTHPORT_DEVICE_INTERFACE, NULL, &device->bthport_symlink_name ) == STATUS_SUCCESS) IoSetDeviceInterfaceState( &device->bthport_symlink_name, TRUE ); diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index d047dcbc8e8..a47b4ed9d9b 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -95,11 +95,55 @@ static inline const char *debugstr_minor_function_code( UCHAR code ) } #undef XX
+/* Undocumented device properties for Bluetooth radios. */ +#define DEFINE_BTH_RADIO_DEVPROPKEY( d, i ) \ + DEFINE_DEVPROPKEY( DEVPKEY_BluetoothRadio_##d, 0xa92f26ca, 0xeda7, 0x4b1d, 0x9d, 0xb2, 0x27, \ + 0xb6, 0x8a, 0xa5, 0xa2, 0xeb, (i) ) + +DEFINE_BTH_RADIO_DEVPROPKEY( Address, 1 ); /* DEVPROP_TYPE_UINT64 */ +DEFINE_BTH_RADIO_DEVPROPKEY( Manufacturer, 2 ); /* DEVPROP_TYPE_UINT16 */ +DEFINE_BTH_RADIO_DEVPROPKEY( LMPSupportedFeatures, 3 ); /* DEVPROP_TYPE_UINT64 */ +DEFINE_BTH_RADIO_DEVPROPKEY( LMPVersion, 4 ); /* DEVPROP_TYPE_BYTE */ +DEFINE_BTH_RADIO_DEVPROPKEY( HCIVendorFeatures, 8 ); /* DEVPROP_TYPE_UINT64 */ +DEFINE_BTH_RADIO_DEVPROPKEY( MaximumAdvertisementDataLength, 17 ); /* DEVPROP_TYPE_UINT16 */ +DEFINE_BTH_RADIO_DEVPROPKEY( LELocalSupportedFeatures, 22 ); /* DEVPROP_TYPE_UINT64 */ + typedef struct { UINT_PTR handle; } winebluetooth_radio_t;
+typedef UINT16 winebluetooth_radio_props_mask_t; + +#define WINEBLUETOOTH_RADIO_PROPERTY_NAME (1) +#define WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS (1 << 2) +#define WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE (1 << 3) +#define WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE (1 << 4) +#define WINEBLUETOOTH_RADIO_PROPERTY_CLASS (1 << 5) +#define WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER (1 << 6) +#define WINEBLUETOOTH_RADIO_PROPERTY_VERSION (1 << 7) +#define WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING (1 << 8) +#define WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE (1 << 9) + +#define WINEBLUETOOTH_RADIO_ALL_PROPERTIES \ + (WINEBLUETOOTH_RADIO_PROPERTY_NAME | WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS | \ + WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE | WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE | \ + WINEBLUETOOTH_RADIO_PROPERTY_CLASS | WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER | \ + WINEBLUETOOTH_RADIO_PROPERTY_VERSION | WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING | \ + WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE) + +struct winebluetooth_radio_properties +{ + BOOL discoverable; + BOOL connectable; + BOOL discovering; + BOOL pairable; + BLUETOOTH_ADDRESS address; + CHAR name[BLUETOOTH_MAX_NAME_SIZE]; + ULONG class; + USHORT manufacturer; + BYTE version; +};
NTSTATUS winebluetooth_radio_get_unique_name( winebluetooth_radio_t radio, char *name, SIZE_T *size ); @@ -112,6 +156,8 @@ enum winebluetooth_watcher_event_type
struct winebluetooth_watcher_event_radio_added { + winebluetooth_radio_props_mask_t props_mask; + struct winebluetooth_radio_properties props; winebluetooth_radio_t radio; };
From: Vibhav Pant vibhavp@gmail.com
The unix bluetooth watcher uses a message filter on the DBus connection to listen for "InterfacesAdded" signals on DBus. If such a signal is received for an object, and the list of interfaces includes org.bluez.Adapter1, queue a BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED event. --- dlls/winebth.sys/dbus.c | 239 ++++++++++++++++++++++++++++++-- dlls/winebth.sys/unixlib_priv.h | 1 + 2 files changed, 226 insertions(+), 14 deletions(-)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 758f5e2be1b..222ee29474d 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -58,6 +58,17 @@ WINE_DECLARE_DEBUG_CHANNEL( dbus ); const int bluez_timeout = -1;
#define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager" +#define DBUS_OBJECTMANAGER_SIGNAL_INTERFACESADDED "InterfacesAdded" + +#define DBUS_INTERFACES_ADDED_SIGNATURE \ + DBUS_TYPE_OBJECT_PATH_AS_STRING \ + DBUS_TYPE_ARRAY_AS_STRING \ + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING \ + DBUS_TYPE_STRING_AS_STRING \ + DBUS_TYPE_ARRAY_AS_STRING \ + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING \ + DBUS_DICT_ENTRY_END_CHAR_AS_STRING \ + DBUS_DICT_ENTRY_END_CHAR_AS_STRING
#define BLUEZ_DEST "org.bluez" #define BLUEZ_INTERFACE_ADAPTER "org.bluez.Adapter1" @@ -87,6 +98,31 @@ failed: return FALSE; }
+static NTSTATUS bluez_dbus_error_to_ntstatus( const DBusError *error ) +{ + +#define DBUS_ERROR_CASE(n, s) if (p_dbus_error_has_name( error, (n)) ) return (s) + + DBUS_ERROR_CASE( "org.bluez.Error.Failed", STATUS_INTERNAL_ERROR); + DBUS_ERROR_CASE( "org.bluez.Error.NotReady", STATUS_DEVICE_NOT_READY ); + DBUS_ERROR_CASE( "org.bluez.Error.NotAuthorized", STATUS_ACCESS_DENIED ); + DBUS_ERROR_CASE( "org.bluez.Error.InvalidArguments", STATUS_INVALID_PARAMETER ); + DBUS_ERROR_CASE( "org.bluez.Error.AlreadyExists", STATUS_NO_MORE_ENTRIES ); + DBUS_ERROR_CASE( "org.bluez.Error.AuthenticationCanceled", STATUS_CANCELLED ); + DBUS_ERROR_CASE( "org.bluez.Error.AuthenticationFailed", STATUS_INTERNAL_ERROR ); + DBUS_ERROR_CASE( "org.bluez.Error.AuthenticationRejected", STATUS_INTERNAL_ERROR ); + DBUS_ERROR_CASE( "org.bluez.Error.AuthenticationTimeout", STATUS_TIMEOUT ); + DBUS_ERROR_CASE( "org.bluez.Error.ConnectionAttemptFailed", STATUS_DEVICE_NOT_CONNECTED); + DBUS_ERROR_CASE( "org.bluez.Error.NotConnected", STATUS_DEVICE_NOT_CONNECTED ); + DBUS_ERROR_CASE( "org.bluez.Error.InProgress", STATUS_OPERATION_IN_PROGRESS ); + DBUS_ERROR_CASE( DBUS_ERROR_UNKNOWN_OBJECT, STATUS_INVALID_PARAMETER ); + DBUS_ERROR_CASE( DBUS_ERROR_NO_MEMORY, STATUS_NO_MEMORY ); + DBUS_ERROR_CASE( DBUS_ERROR_NOT_SUPPORTED, STATUS_NOT_SUPPORTED ); + DBUS_ERROR_CASE( DBUS_ERROR_ACCESS_DENIED, STATUS_ACCESS_DENIED ); + return STATUS_INTERNAL_ERROR; +#undef DBUS_ERROR_CASE +} + static const char *bluez_next_dict_entry( DBusMessageIter *iter, DBusMessageIter *variant ) { DBusMessageIter sub; @@ -103,6 +139,37 @@ static const char *bluez_next_dict_entry( DBusMessageIter *iter, DBusMessageIter return name; }
+static const char *dbgstr_dbus_message( DBusMessage *message ) +{ + const char *interface; + const char *member; + const char *path; + const char *sender; + const char *signature; + int type; + + interface = p_dbus_message_get_interface( message ); + member = p_dbus_message_get_member( message ); + path = p_dbus_message_get_path( message ); + sender = p_dbus_message_get_sender( message ); + type = p_dbus_message_get_type( message ); + signature = p_dbus_message_get_signature( message ); + + switch (type) + { + case DBUS_MESSAGE_TYPE_METHOD_CALL: + return wine_dbg_sprintf( "{method_call sender=%s interface=%s member=%s path=%s signature=%s}", + debugstr_a( sender ), debugstr_a( interface ), debugstr_a( member ), + debugstr_a( path ), debugstr_a( signature ) ); + case DBUS_MESSAGE_TYPE_SIGNAL: + return wine_dbg_sprintf( "{signal sender=%s interface=%s member=%s path=%s signature=%s}", + debugstr_a( sender ), debugstr_a( interface ), debugstr_a( member ), + debugstr_a( path ), debugstr_a( signature ) ); + default: + return wine_dbg_sprintf( "%p", message ); + } +} + static inline const char *dbgstr_dbus_connection( DBusConnection *connection ) { return wine_dbg_sprintf( "{%p connected=%d}", connection, @@ -245,6 +312,9 @@ struct bluez_watcher_ctx
/* struct bluez_init_entry */ struct list initial_radio_list; + + /* struct bluez_watcher_event */ + struct list event_list; };
void *bluez_dbus_init( void ) @@ -290,12 +360,108 @@ struct bluez_watcher_event union winebluetooth_watcher_event_data event; };
+static BOOL bluez_event_list_queue_new_event( struct list *event_list, + enum winebluetooth_watcher_event_type event_type, + union winebluetooth_watcher_event_data event ) +{ + struct bluez_watcher_event *event_entry; + + event_entry = calloc( 1, sizeof( *event_entry ) ); + if (!event_entry) + { + ERR( "Could not allocate memory for DBus event.\n" ); + return FALSE; + } + + event_entry->event_type = event_type; + event_entry->event = event; + list_add_tail( event_list, &event_entry->entry ); + + return TRUE; +} + +static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, void *user_data ) +{ + struct list *event_list; + + if (TRACE_ON( dbus )) + TRACE_( dbus )( "(%s, %s, %p)\n", dbgstr_dbus_connection( conn ), dbgstr_dbus_message( msg ), user_data ); + + event_list = &((struct bluez_watcher_ctx *)user_data)->event_list; + + if (p_dbus_message_is_signal( msg, DBUS_INTERFACE_OBJECTMANAGER, DBUS_OBJECTMANAGER_SIGNAL_INTERFACESADDED ) + && p_dbus_message_has_signature( msg, DBUS_INTERFACES_ADDED_SIGNATURE )) + { + DBusMessageIter iter, ifaces_iter; + const char *object_path; + + p_dbus_message_iter_init( msg, &iter ); + p_dbus_message_iter_get_basic( &iter, &object_path ); + p_dbus_message_iter_next( &iter ); + p_dbus_message_iter_recurse( &iter, &ifaces_iter ); + while (p_dbus_message_iter_has_next( &ifaces_iter )) + { + DBusMessageIter iface_entry; + const char *iface_name; + + p_dbus_message_iter_recurse( &ifaces_iter, &iface_entry ); + p_dbus_message_iter_get_basic( &iface_entry, &iface_name ); + if (!strcmp( iface_name, BLUEZ_INTERFACE_ADAPTER )) + { + struct winebluetooth_watcher_event_radio_added radio_added = {0}; + struct unix_name *radio; + DBusMessageIter props_iter, variant; + const char *prop_name; + + p_dbus_message_iter_next( &iface_entry ); + p_dbus_message_iter_recurse( &iface_entry, &props_iter ); + + while((prop_name = bluez_next_dict_entry( &props_iter, &variant ))) + { + bluez_radio_prop_from_dict_entry( prop_name, &variant, &radio_added.props, + &radio_added.props_mask, + WINEBLUETOOTH_RADIO_ALL_PROPERTIES ); + } + + radio = unix_name_get_or_create( object_path ); + radio_added.radio.handle = (UINT_PTR)radio; + if (!radio_added.radio.handle) + { + ERR( "failed to allocate memory for adapter path %s\n", debugstr_a( object_path ) ); + break; + } + else + { + union winebluetooth_watcher_event_data event = { .radio_added = radio_added }; + TRACE( "New BlueZ org.bluez.Adapter1 object added at %s: %p\n", + debugstr_a( object_path ), radio ); + if (!bluez_event_list_queue_new_event( + event_list, BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, event )) + unix_name_free( radio ); + } + } + p_dbus_message_iter_next( &ifaces_iter ); + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const char BLUEZ_MATCH_OBJECTMANAGER[] = "type='signal'," + "interface='org.freedesktop.DBus.ObjectManager'," + "sender='"BLUEZ_DEST"'," + "path='/'"; + +static const char *BLUEZ_MATCH_RULES[] = { BLUEZ_MATCH_OBJECTMANAGER }; + NTSTATUS bluez_watcher_init( void *connection, void **ctx ) { + DBusError err; NTSTATUS status; DBusPendingCall *call; struct bluez_watcher_ctx *watcher_ctx = calloc( 1, sizeof( struct bluez_watcher_ctx ) ); + SIZE_T i;
if (watcher_ctx == NULL) return STATUS_NO_MEMORY; status = bluez_get_objects_async( connection, &call ); @@ -308,11 +474,46 @@ 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->event_list ); + + if (!p_dbus_connection_add_filter( connection, bluez_filter, watcher_ctx, free )) + { + p_dbus_pending_call_cancel( call ); + p_dbus_pending_call_unref( call ); + free( watcher_ctx ); + ERR( "Could not add DBus filter\n" ); + return STATUS_NO_MEMORY; + } + p_dbus_error_init( &err ); + for (i = 0; i < ARRAY_SIZE( BLUEZ_MATCH_RULES ); i++) + { + TRACE( "Adding DBus match rule %s\n", debugstr_a( BLUEZ_MATCH_RULES[i] ) ); + + p_dbus_bus_add_match( connection, BLUEZ_MATCH_RULES[i], &err ); + if (p_dbus_error_is_set( &err )) + { + NTSTATUS status = bluez_dbus_error_to_ntstatus( &err ); + ERR( "Could not add DBus match %s: %s: %s\n", debugstr_a( BLUEZ_MATCH_RULES[i] ), debugstr_a( err.name ), + debugstr_a( err.message ) ); + p_dbus_pending_call_cancel( call ); + p_dbus_pending_call_unref( call ); + p_dbus_error_free( &err ); + free( watcher_ctx ); + return status; + } + } + p_dbus_error_free( &err ); *ctx = watcher_ctx; TRACE( "ctx=%p\n", ctx ); return STATUS_SUCCESS; }
+void bluez_watcher_close( void *connection, void *ctx ) +{ + p_dbus_bus_remove_match( connection, BLUEZ_MATCH_OBJECTMANAGER, NULL ); + p_dbus_connection_remove_filter( connection, bluez_filter, ctx ); +} + struct bluez_init_entry { union { @@ -381,13 +582,30 @@ static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct lis return status; }
-static struct bluez_init_entry *bluez_init_entries_list_pop( struct list *list ) +static BOOL bluez_watcher_event_queue_ready( struct bluez_watcher_ctx *ctx, struct winebluetooth_watcher_event *event ) { - struct list *entry = list_head( list ); - struct bluez_init_entry *device = LIST_ENTRY( entry, struct bluez_init_entry, entry ); - - list_remove( entry ); - return device; + if (!list_empty( &ctx->initial_radio_list )) + { + struct bluez_init_entry *radio; + + radio = LIST_ENTRY( list_head( &ctx->initial_radio_list ), struct bluez_init_entry, entry ); + event->event_type = BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED; + event->event_data.radio_added = radio->object.radio; + list_remove( &radio->entry ); + free( radio ); + return TRUE; + } + if (!list_empty( &ctx->event_list )) + { + struct bluez_watcher_event *event = + LIST_ENTRY( list_head( &ctx->event_list ), struct bluez_watcher_event, entry ); + event->event_type = event->event_type; + event->event = event->event; + list_remove( &event->entry ); + free( event ); + return TRUE; + } + return FALSE; }
NTSTATUS bluez_dbus_loop( void *c, void *watcher, @@ -401,16 +619,9 @@ NTSTATUS bluez_dbus_loop( void *c, void *watcher,
while (TRUE) { - if (!list_empty( &watcher_ctx->initial_radio_list )) + if (bluez_watcher_event_queue_ready( watcher_ctx, &result->data.watcher_event )) { - struct bluez_init_entry *radio = - bluez_init_entries_list_pop( &watcher_ctx->initial_radio_list ); - struct winebluetooth_watcher_event *watcher_event = &result->data.watcher_event; - result->status = WINEBLUETOOTH_EVENT_WATCHER_EVENT; - watcher_event->event_type = BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED; - watcher_event->event_data.radio_added = radio->object.radio; - free( radio ); p_dbus_connection_unref( connection ); return STATUS_PENDING; } diff --git a/dlls/winebth.sys/unixlib_priv.h b/dlls/winebth.sys/unixlib_priv.h index f74bd6b2e8b..5ed5204e930 100644 --- a/dlls/winebth.sys/unixlib_priv.h +++ b/dlls/winebth.sys/unixlib_priv.h @@ -48,4 +48,5 @@ extern void bluez_dbus_close( void *connection ); extern void bluez_dbus_free( void *connection ); extern NTSTATUS bluez_dbus_loop( void *connection, void *watcher_ctx, struct winebluetooth_event *result ); extern NTSTATUS bluez_watcher_init( void *connection, void **ctx ); +extern void bluez_watcher_close( void *connection, void *ctx ); #endif /* __WINE_WINEBTH_UNIXLIB_PRIV_H */
From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/dbus.c | 50 +++++++++++++++++++++++++++++++++ dlls/winebth.sys/winebth.c | 25 +++++++++++++++++ dlls/winebth.sys/winebth_priv.h | 6 ++++ 3 files changed, 81 insertions(+)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 222ee29474d..a478971c2b0 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -59,6 +59,7 @@ const int bluez_timeout = -1;
#define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager" #define DBUS_OBJECTMANAGER_SIGNAL_INTERFACESADDED "InterfacesAdded" +#define DBUS_OBJECTMANAGER_SIGNAL_INTERFACESREMOVED "InterfacesRemoved"
#define DBUS_INTERFACES_ADDED_SIGNATURE \ DBUS_TYPE_OBJECT_PATH_AS_STRING \ @@ -70,6 +71,10 @@ const int bluez_timeout = -1; DBUS_DICT_ENTRY_END_CHAR_AS_STRING \ DBUS_DICT_ENTRY_END_CHAR_AS_STRING
+#define DBUS_INTERFACES_REMOVED_SIGNATURE \ + DBUS_TYPE_OBJECT_PATH_AS_STRING \ + DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING + #define BLUEZ_DEST "org.bluez" #define BLUEZ_INTERFACE_ADAPTER "org.bluez.Adapter1"
@@ -443,6 +448,51 @@ static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, v p_dbus_message_iter_next( &ifaces_iter ); } } + else if (p_dbus_message_is_signal( msg, DBUS_INTERFACE_OBJECTMANAGER, DBUS_OBJECTMANAGER_SIGNAL_INTERFACESREMOVED ) + && p_dbus_message_has_signature( msg, DBUS_INTERFACES_REMOVED_SIGNATURE )) + { + const char *object_path; + char **interfaces; + int n_interfaces, i; + DBusError error; + dbus_bool_t success; + + p_dbus_error_init( &error ); + success = p_dbus_message_get_args( msg, &error, DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &interfaces, + &n_interfaces, DBUS_TYPE_INVALID ); + if (!success) + { + ERR( "error getting arguments from message: %s: %s\n", debugstr_a( error.name ), + debugstr_a( error.message ) ); + p_dbus_error_free( &error ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + p_dbus_error_free( &error ); + for (i = 0; i < n_interfaces; i++) + { + if (!strcmp( interfaces[i], BLUEZ_INTERFACE_ADAPTER )) + { + winebluetooth_radio_t radio; + struct unix_name *radio_name; + union winebluetooth_watcher_event_data event; + + radio_name = unix_name_get_or_create( object_path ); + if (!radio_name) + { + ERR( "failed to allocate memory for adapter path %s\n", object_path ); + continue; + } + radio.handle = (UINT_PTR)radio_name; + event.radio_removed = radio; + if (!bluez_event_list_queue_new_event( + event_list, BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED, event )) + unix_name_free( radio_name ); + } + } + p_dbus_free_string_array( interfaces ); + }
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 38f716a72a1..96b24f974e1 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -191,6 +191,28 @@ static void add_bluetooth_radio( struct winebluetooth_watcher_event_radio_added IoInvalidateDeviceRelations( bus_pdo, BusRelations ); }
+static void remove_bluetooth_radio( winebluetooth_radio_t radio ) +{ + struct bluetooth_radio *device; + + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY( device, &device_list, struct bluetooth_radio, entry ) + { + if (winebluetooth_radio_equal( radio, device->radio ) && !device->removed) + { + TRACE( "Removing bluetooth radio %p\n", (void *)radio.handle ); + device->removed = TRUE; + list_remove( &device->entry ); + IoInvalidateDeviceRelations( device->device_obj, BusRelations ); + break; + } + } + LeaveCriticalSection( &device_list_cs ); + + IoInvalidateDeviceRelations( bus_pdo, BusRelations ); + winebluetooth_radio_free( radio ); +} + static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) { NTSTATUS status; @@ -211,6 +233,9 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED: add_bluetooth_radio( event->event_data.radio_added ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED: + remove_bluetooth_radio( event->event_data.radio_removed ); + break; default: FIXME( "Unknown bluetooth watcher event code: %#x\n", event->event_type ); } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index a47b4ed9d9b..bf0354d33ef 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -148,10 +148,15 @@ struct winebluetooth_radio_properties NTSTATUS winebluetooth_radio_get_unique_name( winebluetooth_radio_t radio, char *name, SIZE_T *size ); void winebluetooth_radio_free( winebluetooth_radio_t radio ); +static inline BOOL winebluetooth_radio_equal( winebluetooth_radio_t r1, winebluetooth_radio_t r2 ) +{ + return r1.handle == r2.handle; +}
enum winebluetooth_watcher_event_type { BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, + BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED, };
struct winebluetooth_watcher_event_radio_added @@ -164,6 +169,7 @@ struct winebluetooth_watcher_event_radio_added union winebluetooth_watcher_event_data { struct winebluetooth_watcher_event_radio_added radio_added; + winebluetooth_radio_t radio_removed; };
struct winebluetooth_watcher_event
From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/dbus.c | 268 +++++++++++++++++++++++++++++++- dlls/winebth.sys/winebth.c | 31 ++++ dlls/winebth.sys/winebth_priv.h | 11 ++ 3 files changed, 303 insertions(+), 7 deletions(-)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index a478971c2b0..155b5f14190 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -52,6 +52,30 @@
#ifdef SONAME_LIBDBUS_1
+/* BlueZ is the userspace Bluetooth management daemon for Linux systems, which provides an + * object-based API for interacting with Bluetooth adapters and devices on top of DBus. Every DBus + * object has one or more "interfaces", with each interface have its own set of methods and signals + * that we can call or listen for, respectively. Objects are identified by their "path", which + * provide a way to organize objects under a hierarchy. For instance, a bluetooth adapter/radio will + * have a path of the form "/org/bluez/hciX", with any remote devices discovered by or bonded to it + * having the path "/org/bluez/hciX/XX:XX:XX:XX:XX:XX". Similarly, any LE GATT services, + * characteristics or descriptors specific to this remote device are represented as objects under + * the device's path. Among BlueZ's own interfaces, the other important interfaces we rely on are: + * + * org.freedesktop.DBus.Properties: Allows us to read and (optionally) modify properties specific + * to an object's interface using the "Get" and "Set" methods. Additionally, some objects may + * broadcast a "PropertiesChanged" signal if any of their properties change, which gets used by + * bluez_filter to let the driver know to appropriately update the equivalent PDO. The "GetAll" + * method provides a convenient way to get all properties for an interface. + * + * org.freedesktop.DBus.ObjectManager: This contains the "GetManagedObjects" method, that returns a + * list of all known objects under the object's path namespace, with the interfaces they implement + * (and any properties those interfaces might have). This is used to build a list of initially known + * devices in bluez_build_initial_device_lists. The "InterfacesAdded" and "InterfacesRemoved" + * signals are used to detect when a bluetooth adapter or device has been addeed/removed in + * bluez_filter, which we relay to the bluetooth driver to add/remove the corresponding PDO. + */ + WINE_DEFAULT_DEBUG_CHANNEL( winebth ); WINE_DECLARE_DEBUG_CHANNEL( dbus );
@@ -60,6 +84,7 @@ const int bluez_timeout = -1; #define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager" #define DBUS_OBJECTMANAGER_SIGNAL_INTERFACESADDED "InterfacesAdded" #define DBUS_OBJECTMANAGER_SIGNAL_INTERFACESREMOVED "InterfacesRemoved" +#define DBUS_PROPERTIES_SIGNAL_PROPERTIESCHANGED "PropertiesChanged"
#define DBUS_INTERFACES_ADDED_SIGNATURE \ DBUS_TYPE_OBJECT_PATH_AS_STRING \ @@ -75,6 +100,14 @@ const int bluez_timeout = -1; DBUS_TYPE_OBJECT_PATH_AS_STRING \ DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING
+#define DBUS_PROPERTIES_CHANGED_SIGNATURE \ + DBUS_TYPE_STRING_AS_STRING \ + DBUS_TYPE_ARRAY_AS_STRING \ + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING \ + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING \ + DBUS_DICT_ENTRY_END_CHAR_AS_STRING \ + DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING + #define BLUEZ_DEST "org.bluez" #define BLUEZ_INTERFACE_ADAPTER "org.bluez.Adapter1"
@@ -311,6 +344,29 @@ static void bluez_radio_prop_from_dict_entry( const char *prop_name, DBusMessage } }
+static NTSTATUS bluez_adapter_get_props_async( void *connection, const char *radio_object_path, + DBusPendingCall **call ) +{ + DBusMessage *request; + static const char *adapter_iface = BLUEZ_INTERFACE_ADAPTER; + dbus_bool_t success; + + request = p_dbus_message_new_method_call( BLUEZ_DEST, radio_object_path, + DBUS_INTERFACE_PROPERTIES, "GetAll" ); + if (!request) return STATUS_NO_MEMORY; + + p_dbus_message_append_args( request, DBUS_TYPE_STRING, &adapter_iface, DBUS_TYPE_INVALID ); + + success = p_dbus_connection_send_with_reply( connection, request, call, bluez_timeout ); + p_dbus_message_unref( request ); + if (!success) + return STATUS_NO_MEMORY; + if (!*call) + return STATUS_INTERNAL_ERROR; + + return STATUS_SUCCESS; +} + struct bluez_watcher_ctx { void *init_device_list_call; @@ -363,16 +419,22 @@ struct bluez_watcher_event struct list entry; enum winebluetooth_watcher_event_type event_type; union winebluetooth_watcher_event_data event; + + /* Some DBus signals, like PropertiesChanged in org.freedesktop.DBus.Properties, require us to + * perform an additional call to get the complete state of the object (in this instance, call + * Get/GetAll to get the values of invalidated properties). The event is queued out only once + * this call completes. */ + DBusPendingCall *pending_call; };
-static BOOL bluez_event_list_queue_new_event( struct list *event_list, - enum winebluetooth_watcher_event_type event_type, - union winebluetooth_watcher_event_data event ) +static BOOL bluez_event_list_queue_new_event_with_call( + struct list *event_list, enum winebluetooth_watcher_event_type event_type, + union winebluetooth_watcher_event_data event, DBusPendingCall *call, + DBusPendingCallNotifyFunction callback ) { struct bluez_watcher_event *event_entry;
- event_entry = calloc( 1, sizeof( *event_entry ) ); - if (!event_entry) + if (!(event_entry = calloc(1, sizeof( *event_entry ) ))) { ERR( "Could not allocate memory for DBus event.\n" ); return FALSE; @@ -380,11 +442,87 @@ static BOOL bluez_event_list_queue_new_event( struct list *event_list,
event_entry->event_type = event_type; event_entry->event = event; + event_entry->pending_call = call; + if (!call) + p_dbus_pending_call_set_notify( call, callback, &event_entry->event, NULL ); list_add_tail( event_list, &event_entry->entry );
return TRUE; }
+static BOOL bluez_event_list_queue_new_event( struct list *event_list, + enum winebluetooth_watcher_event_type event_type, + union winebluetooth_watcher_event_data event ) +{ + return bluez_event_list_queue_new_event_with_call( event_list, event_type, event, NULL, NULL ); +} + +static void bluez_filter_radio_props_changed_callback( DBusPendingCall *call, void *user_data ) +{ + union winebluetooth_watcher_event_data *event = user_data; + struct winebluetooth_watcher_event_radio_props_changed *changed = &event->radio_props_changed; + const struct unix_name *radio = (struct unix_name *)event->radio_props_changed.radio.handle; + DBusMessage *reply; + DBusMessageIter dict, prop_iter, variant; + const char *prop_name; + DBusError error; + + TRACE( "call %p, radio %s\n", call, debugstr_a( radio->str ) ); + + reply = p_dbus_pending_call_steal_reply( call ); + p_dbus_error_init( &error ); + if (p_dbus_set_error_from_message( &error, reply )) + { + ERR( "Failed to get adapter properties for %s: %s: %s\n", debugstr_a( radio->str ), + debugstr_a( error.name ), debugstr_a( error.message ) ); + p_dbus_error_free( &error ); + p_dbus_message_unref( reply ); + return; + } + p_dbus_error_free( &error ); + + p_dbus_message_iter_init( reply, &dict ); + p_dbus_message_iter_recurse( &dict, &prop_iter ); + while((prop_name = bluez_next_dict_entry( &prop_iter, &variant ))) + { + bluez_radio_prop_from_dict_entry( prop_name, &variant, &changed->props, + &changed->changed_props_mask, changed->invalid_props_mask ); + } + changed->invalid_props_mask &= ~changed->changed_props_mask; + p_dbus_message_unref( reply ); +} + +struct bluez_object_property_masks +{ + const char *prop_name; + UINT16 mask; +}; + +static UINT16 bluez_dbus_get_invalidated_properties_from_iter( + DBusMessageIter *invalid_prop_iter, const struct bluez_object_property_masks *prop_masks, + SIZE_T len ) +{ + UINT16 mask = 0; + + while (p_dbus_message_iter_has_next( invalid_prop_iter )) + { + const char *prop_name; + SIZE_T i; + + assert( p_dbus_message_iter_get_arg_type( invalid_prop_iter ) == DBUS_TYPE_STRING ); + p_dbus_message_iter_get_basic( invalid_prop_iter, &prop_name ); + for (i = 0; i < len; i++) + { + if (strcmp( prop_masks[i].prop_name, prop_name ) == 0) + mask |= prop_masks[i].mask; + } + + p_dbus_message_iter_next( invalid_prop_iter ); + } + + return mask; +} + static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, void *user_data ) { struct list *event_list; @@ -493,6 +631,101 @@ static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, v } p_dbus_free_string_array( interfaces ); } + else if (p_dbus_message_is_signal( msg, DBUS_INTERFACE_PROPERTIES, DBUS_PROPERTIES_SIGNAL_PROPERTIESCHANGED ) && + p_dbus_message_has_signature( msg, DBUS_PROPERTIES_CHANGED_SIGNATURE )) + { + DBusMessageIter iter; + const char *iface; + + p_dbus_message_iter_init( msg, &iter ); + p_dbus_message_iter_get_basic( &iter, &iface ); + + if (!strcmp( iface, BLUEZ_INTERFACE_ADAPTER )) + { + struct winebluetooth_watcher_event_radio_props_changed props_changed = {0}; + struct unix_name *radio; + DBusMessageIter changed_props_iter, invalid_props_iter, variant; + const char *prop_name; + const static struct bluez_object_property_masks radio_prop_masks[] = { + { "Name", WINEBLUETOOTH_RADIO_PROPERTY_NAME }, + { "Address", WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS }, + { "Discoverable", WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE }, + { "Connectable", WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE }, + { "Class", WINEBLUETOOTH_RADIO_PROPERTY_CLASS }, + { "Manufacturer", WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER }, + { "Version", WINEBLUETOOTH_RADIO_PROPERTY_VERSION }, + { "Discovering", WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING }, + { "Pairable", WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE }, + }; + const char *object_path; + + p_dbus_message_iter_next( &iter ); + p_dbus_message_iter_recurse( &iter, &changed_props_iter ); + while ((prop_name = bluez_next_dict_entry( &changed_props_iter, &variant ))) + { + bluez_radio_prop_from_dict_entry( prop_name, &variant, &props_changed.props, + &props_changed.changed_props_mask, + WINEBLUETOOTH_RADIO_ALL_PROPERTIES ); + } + + p_dbus_message_iter_next( &iter ); + p_dbus_message_iter_recurse( &iter, &invalid_props_iter ); + props_changed.invalid_props_mask = bluez_dbus_get_invalidated_properties_from_iter( + &invalid_props_iter, radio_prop_masks, ARRAY_SIZE( radio_prop_masks ) ); + if (!props_changed.changed_props_mask && !props_changed.invalid_props_mask) + /* No properties that are of any interest to us have changed or been invalidated, + * no need to generate an event. */ + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + object_path = p_dbus_message_get_path( msg ); + radio = unix_name_get_or_create( object_path ); + if (!radio) + { + ERR( "failed to allocate memory for adapter path %s\n", debugstr_a( object_path ) ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + props_changed.radio.handle = (UINT_PTR)radio; + TRACE( "Properties changed for radio %s, changed %#x, invalid %#x\n", + debugstr_a( radio->str ), props_changed.changed_props_mask, + props_changed.invalid_props_mask ); + if (props_changed.invalid_props_mask != 0) + { + DBusPendingCall *pending_call = NULL; + union winebluetooth_watcher_event_data event = { .radio_props_changed = props_changed }; + NTSTATUS status = bluez_adapter_get_props_async( conn, radio->str, &pending_call ); + + if (status != STATUS_SUCCESS) + { + ERR( "Failed to create async call to get adapter properties: %#x\n", + (int)status ); + unix_name_free( radio ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (!bluez_event_list_queue_new_event_with_call( event_list, + BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED, + event, pending_call, + bluez_filter_radio_props_changed_callback )) + { + unix_name_free( radio ); + p_dbus_pending_call_cancel( pending_call ); + p_dbus_pending_call_unref( pending_call ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + else + { + union winebluetooth_watcher_event_data event = { .radio_props_changed = props_changed }; + if (!bluez_event_list_queue_new_event( event_list, + BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED, + event )) + { + unix_name_free( radio ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + } + }
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } @@ -501,8 +734,12 @@ static const char BLUEZ_MATCH_OBJECTMANAGER[] = "type='signal'," "interface='org.freedesktop.DBus.ObjectManager'," "sender='"BLUEZ_DEST"'," "path='/'"; +static const char BLUEZ_MATCH_PROPERTIES[] = "type='signal'," + "interface='"DBUS_INTERFACE_PROPERTIES"'," + "member='PropertiesChanged'," + "sender='"BLUEZ_DEST"',";
-static const char *BLUEZ_MATCH_RULES[] = { BLUEZ_MATCH_OBJECTMANAGER }; +static const char *BLUEZ_MATCH_RULES[] = { BLUEZ_MATCH_OBJECTMANAGER, BLUEZ_MATCH_PROPERTIES };
NTSTATUS bluez_watcher_init( void *connection, void **ctx ) { @@ -560,7 +797,18 @@ NTSTATUS bluez_watcher_init( void *connection, void **ctx )
void bluez_watcher_close( void *connection, void *ctx ) { - p_dbus_bus_remove_match( connection, BLUEZ_MATCH_OBJECTMANAGER, NULL ); + SIZE_T i; + for (i = 0; i < ARRAY_SIZE( BLUEZ_MATCH_RULES ); i++) + { + DBusError error; + + p_dbus_error_init( &error ); + p_dbus_bus_remove_match( connection, BLUEZ_MATCH_RULES[i], &error ); + if (p_dbus_error_is_set( &error )) + ERR( "Could not remove DBus match %s: %s: %s", BLUEZ_MATCH_RULES[i], + debugstr_a( error.name ), debugstr_a( error.message ) ); + p_dbus_error_free( &error ); + } p_dbus_connection_remove_filter( connection, bluez_filter, ctx ); }
@@ -649,9 +897,15 @@ static BOOL bluez_watcher_event_queue_ready( struct bluez_watcher_ctx *ctx, stru { struct bluez_watcher_event *event = LIST_ENTRY( list_head( &ctx->event_list ), struct bluez_watcher_event, entry ); + + if (event->pending_call && !p_dbus_pending_call_get_completed( event->pending_call )) + return FALSE; + event->event_type = event->event_type; event->event = event->event; list_remove( &event->entry ); + if (event->pending_call) + p_dbus_pending_call_unref( event->pending_call ); free( event ); return TRUE; } diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 96b24f974e1..e8f64ae6217 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -213,6 +213,34 @@ static void remove_bluetooth_radio( winebluetooth_radio_t radio ) winebluetooth_radio_free( radio ); }
+static void bluetooth_radio_set_properties( DEVICE_OBJECT *obj, + winebluetooth_radio_props_mask_t mask, + struct winebluetooth_radio_properties *props ); + +static void update_bluetooth_radio_properties( struct winebluetooth_watcher_event_radio_props_changed event ) +{ + struct bluetooth_radio *device; + winebluetooth_radio_t radio = event.radio; + winebluetooth_radio_props_mask_t mask = event.changed_props_mask; + struct winebluetooth_radio_properties props = event.props; + + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY( device, &device_list, struct bluetooth_radio, entry ) + { + if (winebluetooth_radio_equal( radio, device->radio ) && !device->removed) + { + EnterCriticalSection( &device->props_cs ); + device->props_mask = mask; + device->props = props; + bluetooth_radio_set_properties( device->device_obj, device->props_mask, + &device->props ); + LeaveCriticalSection( &device->props_cs ); + break; + } + } + LeaveCriticalSection( &device_list_cs ); +} + static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) { NTSTATUS status; @@ -236,6 +264,9 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED: remove_bluetooth_radio( event->event_data.radio_removed ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED: + update_bluetooth_radio_properties( event->event_data.radio_props_changed ); + break; default: FIXME( "Unknown bluetooth watcher event code: %#x\n", event->event_type ); } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index bf0354d33ef..d8667a29fbc 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -157,6 +157,7 @@ enum winebluetooth_watcher_event_type { BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED, + BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED, };
struct winebluetooth_watcher_event_radio_added @@ -166,10 +167,20 @@ struct winebluetooth_watcher_event_radio_added winebluetooth_radio_t radio; };
+struct winebluetooth_watcher_event_radio_props_changed +{ + winebluetooth_radio_props_mask_t changed_props_mask; + struct winebluetooth_radio_properties props; + + winebluetooth_radio_props_mask_t invalid_props_mask; + winebluetooth_radio_t radio; +}; + union winebluetooth_watcher_event_data { struct winebluetooth_watcher_event_radio_added radio_added; winebluetooth_radio_t radio_removed; + struct winebluetooth_watcher_event_radio_props_changed radio_props_changed; };
struct winebluetooth_watcher_event
From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/winebth.c | 68 +++++++++++++++++++++++++++++++++++++- include/Makefile.in | 1 + include/bthdef.h | 23 +++++++++++++ include/bthioctl.h | 49 +++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 include/bthioctl.h
diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index e8f64ae6217..0b85352537a 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -30,8 +30,10 @@ #include <winnls.h> #include <initguid.h> #include <devpkey.h> +#include <bthsdpdef.h> +#include <bluetoothapis.h> #include <bthdef.h> -#include <winioctl.h> +#include <bthioctl.h> #include <ddk/wdm.h>
#include <wine/debug.h> @@ -76,6 +78,69 @@ struct bluetooth_radio UNICODE_STRING bthradio_symlink_name; };
+static NTSTATUS WINAPI dispatch_bluetooth( DEVICE_OBJECT *device, IRP *irp ) +{ + struct bluetooth_radio *ext = (struct bluetooth_radio *)device->DeviceExtension; + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + ULONG outsize = stack->Parameters.DeviceIoControl.OutputBufferLength; + NTSTATUS status = irp->IoStatus.Status; + + TRACE( "device %p irp %p code %#lx\n", device, irp, code ); + + switch (code) + { + case IOCTL_BTH_GET_LOCAL_INFO: + { + BTH_LOCAL_RADIO_INFO *info = (BTH_LOCAL_RADIO_INFO *)irp->AssociatedIrp.SystemBuffer; + + if (!info || outsize < sizeof(*info)) + { + status = STATUS_INVALID_USER_BUFFER; + break; + } + + memset( info, 0, sizeof( *info ) ); + + EnterCriticalSection( &ext->props_cs ); + if (ext->props_mask & WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS) + { + info->localInfo.flags |= BDIF_ADDRESS; + info->localInfo.address = RtlUlonglongByteSwap( ext->props.address.ullLong ); + } + if (ext->props_mask & WINEBLUETOOTH_RADIO_PROPERTY_NAME) + { + info->localInfo.flags |= BDIF_NAME; + strcpy( info->localInfo.name, ext->props.name ); + } + if (ext->props_mask & WINEBLUETOOTH_RADIO_PROPERTY_CLASS) + { + info->localInfo.flags |= BDIF_COD; + info->localInfo.classOfDevice = ext->props.class; + } + if (ext->props_mask & WINEBLUETOOTH_RADIO_PROPERTY_VERSION) + info->hciVersion = info->radioInfo.lmpVersion = ext->props.version; + if (ext->props.connectable) + info->flags |= LOCAL_RADIO_CONNECTABLE; + if (ext->props.discoverable) + info->flags |= LOCAL_RADIO_DISCOVERABLE; + if (ext->props_mask & WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER) + info->radioInfo.mfg = ext->props.manufacturer; + LeaveCriticalSection( &ext->props_cs ); + + status = STATUS_SUCCESS; + break; + } + default: + FIXME( "Unimplemented IOCTL code: %#lx\n", code ); + break; + } + + irp->IoStatus.Status = status; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return status; +} + void WINAPIV append_id( struct string_buffer *buffer, const WCHAR *format, ... ) { va_list args; @@ -541,5 +606,6 @@ NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path ) driver->DriverExtension->AddDevice = driver_add_device; driver->DriverUnload = driver_unload; driver->MajorFunction[IRP_MJ_PNP] = bluetooth_pnp; + driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dispatch_bluetooth; return STATUS_SUCCESS; } diff --git a/include/Makefile.in b/include/Makefile.in index f3e915d3959..d56bc5182d8 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -59,6 +59,7 @@ SOURCES = \ bitsmsg.h \ bluetoothapis.h \ bthdef.h \ + bthioctl.h \ bthsdpdef.h \ cderr.h \ cdosys.idl \ diff --git a/include/bthdef.h b/include/bthdef.h index 2b81235a776..d71b2e005e7 100644 --- a/include/bthdef.h +++ b/include/bthdef.h @@ -29,6 +29,29 @@ DEFINE_GUID( GUID_BTHPORT_DEVICE_INTERFACE, 0x850302a, 0xb344, 0x4fda, 0x9b, 0xe DEFINE_GUID( GUID_BLUETOOTH_RADIO_INTERFACE, 0x92383b0e, 0xf90e, 0x4ac9, 0x8d, 0x44, 0x8c, 0x2d, 0x0d, 0x0e, 0xbd, 0xa2 );
+typedef ULONG BTH_COD; + +#define BTH_MAX_NAME_SIZE (248) + +#define BDIF_ADDRESS 0x00000001 +#define BDIF_COD 0x00000002 +#define BDIF_NAME 0x00000004 +#define BDIF_PAIRED 0x00000008 +#define BDIF_PERSONAL 0x00000010 +#define BDIF_CONNECTED 0x00000020 + +#define BDIF_SSP_SUPPORTED 0x00000100 +#define BDIF_SSP_PAIRED 0x00000200 +#define BDIF_SSP_MITM_PROTECTED 0x00000200 + +typedef struct _BTH_DEVICE_INFO +{ + ULONG flags; + BTH_ADDR address; + BTH_COD classOfDevice; + CHAR name[BTH_MAX_NAME_SIZE]; +} BTH_DEVICE_INFO, *PBTH_DEVICE_INFO; + #ifdef __cplusplus } #endif diff --git a/include/bthioctl.h b/include/bthioctl.h new file mode 100644 index 00000000000..b840cbc19c6 --- /dev/null +++ b/include/bthioctl.h @@ -0,0 +1,49 @@ +/* + * IOCTL definitions for interfacing with winebth.sys + * + * Copyright 2024 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 __WINE_BTHIOCTL_H_ +#define __WINE_BTHIOCTL_H_ + +#include <winioctl.h> + +#define IOCTL_BTH_GET_LOCAL_INFO CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x00, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define LOCAL_RADIO_DISCOVERABLE 0x0001 +#define LOCAL_RADIO_CONNECTABLE 0x0002 + +typedef struct _BTH_RADIO_INFO +{ + ULONGLONG lmpSupportedFeatures; + USHORT mfg; + USHORT lmpSubversion; + UCHAR lmpVersion; +} BTH_RADIO_INFO, *PBTH_RADIO_INFO; + +typedef struct _BTH_LOCAL_RADIO_INFO +{ + BTH_DEVICE_INFO localInfo; + ULONG flags; + USHORT hciRevision; + UCHAR hciVersion; + BTH_RADIO_INFO radioInfo; +} BTH_LOCAL_RADIO_INFO, *PBTH_LOCAL_RADIO_INFO; + +#endif /* __WINE_BTHIOCTL_H_ */
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=150281
Your paranoid android.
=== debian11b (64 bit WoW report) ===
user32: win.c:4070: Test failed: Expected active window 0000000003CA0164, got 0000000000000000. win.c:4071: Test failed: Expected focus window 0000000003CA0164, got 0000000000000000.
Alexandre Julliard (@julliard) commented about include/bthioctl.h:
- 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 __WINE_BTHIOCTL_H_ +#define __WINE_BTHIOCTL_H_
+#include <winioctl.h>
+#define IOCTL_BTH_GET_LOCAL_INFO CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x00, METHOD_BUFFERED, FILE_ANY_ACCESS)
+#define LOCAL_RADIO_DISCOVERABLE 0x0001 +#define LOCAL_RADIO_CONNECTABLE 0x0002
These don't seem to exist in the SDK header either.
On Fri Dec 6 15:03:53 2024 +0000, Alexandre Julliard wrote:
These don't seem to exist in the SDK header either.
Moved them to winebth_priv.h as well. They're used for `BluetoothIsConnectable` and `BluetoothIsDiscoverable`, but it doesn't make a lot of sense to add a new header to `include/wine` for just two constants, I guess.