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.
-- v2: 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 | 48 ++++++++++++++++ dlls/winebth.sys/winebth_priv.h | 33 +++++++++++ include/Makefile.in | 1 + include/ddk/bthguid.h | 37 ++++++++++++ 5 files changed, 218 insertions(+) create mode 100644 include/ddk/bthguid.h
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 6c74c377d5c..2c7d7208b34 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..e9c0ecdf870 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -27,11 +27,13 @@ #include <windef.h> #include <winbase.h> #include <winternl.h> +#include <winnls.h> #include <initguid.h> #include <devpkey.h> #include <bthdef.h> #include <winioctl.h> #include <ddk/wdm.h> +#include <ddk/bthguid.h>
#include <wine/debug.h> #include <wine/list.h> @@ -66,6 +68,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 +180,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 +338,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 +395,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..acb0db9532d 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -100,6 +100,37 @@ 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 +143,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; };
diff --git a/include/Makefile.in b/include/Makefile.in index f3e915d3959..0712cfb2793 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -193,6 +193,7 @@ SOURCES = \ dde.h \ dde.rh \ ddeml.h \ + ddk/bthguid.h \ ddk/compstui.h \ ddk/csq.h \ ddk/d3dkmthk.h \ diff --git a/include/ddk/bthguid.h b/include/ddk/bthguid.h new file mode 100644 index 00000000000..0039076f92c --- /dev/null +++ b/include/ddk/bthguid.h @@ -0,0 +1,37 @@ +/* + * GUID definitions used by 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 __DDK_BTHGUID_H__ +#define __DDK_BTHGUID_H__ + +#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 */ + +#endif
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 2c7d7208b34..336e0877c12 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 336e0877c12..1af45a5ec4e 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 e9c0ecdf870..3001a186206 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -192,6 +192,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; @@ -212,6 +234,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 acb0db9532d..88a3ab82e72 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -135,10 +135,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 @@ -151,6 +156,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 | 243 +++++++++++++++++++++++++++++++- dlls/winebth.sys/winebth.c | 32 +++++ dlls/winebth.sys/winebth_priv.h | 11 ++ 3 files changed, 280 insertions(+), 6 deletions(-)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 1af45a5ec4e..901d2a897f4 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -60,6 +60,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 +76,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 +320,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,15 +395,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 ) ); + event_entry = calloc(1, sizeof( *event_entry ) ); if (!event_entry) { ERR( "Could not allocate memory for DBus event.\n" ); @@ -380,11 +419,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 != NULL) + 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 +608,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 +711,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 +774,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 +874,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 3001a186206..3273acfd214 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -22,6 +22,7 @@ #include <stdlib.h> #include <assert.h>
+#define WINE_BTH_EXTENSIONS #include <ntstatus.h> #define WIN32_NO_STATUS #include <windef.h> @@ -214,6 +215,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; @@ -237,6 +266,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 88a3ab82e72..58b0e278c3f 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -144,6 +144,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 @@ -153,10 +154,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 3273acfd214..74620f91e77 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -31,8 +31,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 <ddk/bthguid.h>
@@ -78,6 +80,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->hciRevision = 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; @@ -543,5 +608,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 0712cfb2793..13c3f791619 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..61f1ec7a42b --- /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 hciVerison; + BTH_RADIO_INFO radioInfo; +} BTH_LOCAL_RADIO_INFO, *PBTH_LOCAL_RADIO_INFO; + +#endif /* __WINE_BTHIOCTL_H_ */