From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/dbus.c | 226 ++++++++++++++++++++++++++++++- dlls/winebth.sys/winebluetooth.h | 11 ++ dlls/winebth.sys/winebth.c | 125 ++++++++++++++--- include/ddk/bthguid.h | 11 +- 4 files changed, 343 insertions(+), 30 deletions(-)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 0ea003825b5..2f90df9e952 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -75,6 +75,16 @@ const int bluez_timeout = -1; DBUS_TYPE_OBJECT_PATH_AS_STRING \ DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING
+#define DBUS_PROPERTIES_SIGNAL_PROPERTIESCHANGED "PropertiesChanged" + +#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"
@@ -310,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; @@ -365,14 +398,24 @@ 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;
+ if (call != NULL) assert( callback != NULL ); + else assert( callback == NULL ); + event_entry = calloc(1, sizeof( *event_entry ) ); if (!event_entry) { @@ -382,11 +425,86 @@ 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 = event->radio_props_changed.radio.handle; + DBusMessage *reply = p_dbus_pending_call_steal_reply( call ); + DBusMessageIter dict, prop_iter, variant; + const char *prop_name; + DBusError error; + + TRACE( "call %p, radio %s\n", call, debugstr_a( changed->radio.handle ) ); + + 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; @@ -501,6 +619,96 @@ 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 ); + assert( iface != NULL ); + + if (strcmp( iface, BLUEZ_INTERFACE_ADAPTER ) == 0) + { + 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", WINEBLUTOOTH_RADIO_PROPERTY_NAME }, + { "Address", WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS }, + { "Discoverable", WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE }, + { "Powered", WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE }, + { "Class", WINEBLUETOOTH_RADIO_PROPERTY_CLASS }, + { "Manufacturer", WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER }, + { "Version", WINEBLUETOOTH_RADIO_PROPERTY_VERSION }, + }; + const char *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; + } + + 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 ) ); + + 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; + } + } + } + }
final_count = list_count( event_list ); assert( final_count >= init_count ); @@ -652,11 +860,15 @@ static struct bluez_init_entry *bluez_init_entries_list_pop( struct list *list ) static struct bluez_watcher_event *bluez_watcher_event_queue_ready( struct bluez_watcher_ctx *ctx ) { struct list *head = list_head( &ctx->event_list ); - struct bluez_watcher_event *event = LIST_ENTRY(head, struct bluez_watcher_event, entry); - TRACE("(%p)\n", ctx); + struct bluez_watcher_event *event = LIST_ENTRY( head, struct bluez_watcher_event, entry );
- list_remove( &event->entry ); - return event; + if (head == NULL) return NULL; + if (event->pending_call == NULL || p_dbus_pending_call_get_completed( event->pending_call )) + { + list_remove( &event->entry ); + if (event->pending_call) p_dbus_pending_call_unref( event->pending_call ); + return event; + } return NULL; }
diff --git a/dlls/winebth.sys/winebluetooth.h b/dlls/winebth.sys/winebluetooth.h index bcca9d733a3..38f97f0b096 100644 --- a/dlls/winebth.sys/winebluetooth.h +++ b/dlls/winebth.sys/winebluetooth.h @@ -102,6 +102,7 @@ enum winebluetooth_watcher_event_type BLUETOOTH_WATCHER_EVENT_TYPE_NONE, 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 @@ -111,10 +112,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 diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index b57dc3bbfb3..b7f90abcc8c 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> @@ -218,6 +219,32 @@ 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) + { + device->props_mask = mask; + device->props = props; + bluetooth_radio_set_properties( device->device_obj, device->props_mask, + &device->props ); + break; + } + } + LeaveCriticalSection( &device_list_cs ); +} + static void remove_pending_irps(struct bluetooth_radio *device) { LIST_ENTRY *entry; @@ -255,6 +282,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 ); } @@ -382,6 +412,78 @@ static NTSTATUS query_id(const struct bluetooth_radio *ext, IRP *irp, BUS_QUERY_ return STATUS_SUCCESS; }
+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_DISCOVERABLE) + { + BOOL discoverable = props->discoverable; + IoSetDevicePropertyData( obj, &DEVPKEY_WineBluetooth_Radio_Discoverable, LOCALE_NEUTRAL, 0, + DEVPROP_TYPE_BOOLEAN, sizeof( BOOL ), &discoverable ); + } + if (mask & WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE) + { + BOOL discoverable = props->pairable; + IoSetDevicePropertyData( obj, &DEVPKEY_WineBluetooth_Radio_Pairable, LOCALE_NEUTRAL, 0, + DEVPROP_TYPE_BOOLEAN, sizeof( BOOL ), &discoverable ); + } + if (mask & WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE) + { + BOOL connectable = props->connectable; + IoSetDevicePropertyData( obj, &DEVPKEY_WineBluetooth_Radio_Connectable, LOCALE_NEUTRAL, 0, + DEVPROP_TYPE_BOOLEAN, sizeof( BOOL ), &connectable ); + } + if (mask & WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING) + { + BOOL discovering = props->discovering; + IoSetDevicePropertyData( obj, &DEVPKEY_WineBluetooth_Radio_Discovering, LOCALE_NEUTRAL, 0, + DEVPROP_TYPE_BOOLEAN, sizeof( BOOL ), &discovering ); + } + if (mask & WINEBLUETOOTH_RADIO_PROPERTY_CLASS) + { + UINT32 class = props->class; + IoSetDevicePropertyData( obj, &DEVPKEY_WineBluetooth_Radio_Class, LOCALE_NEUTRAL, 0, + DEVPROP_TYPE_UINT32, sizeof( UINT32 ), &class ); + } + if (mask & WINEBLUTOOTH_RADIO_PROPERTY_NAME) + { + WCHAR *name; + size_t count = mbstowcs( NULL, props->name, 0 ); + size_t size = min( count + 1, BLUETOOTH_MAX_NAME_SIZE * sizeof( WCHAR ) ); + if (count == -1) + { + ERR( "error converting Bluetooth radio name to wide string: %s\n", + debugstr_a( props->name ) ); + return; + } + + name = malloc( size ); + mbstowcs( name, props->name, size ); + name[size - 1] = '\0'; + IoSetDevicePropertyData( obj, &DEVPKEY_Device_FriendlyName, LOCALE_NEUTRAL, 0, + DEVPROP_TYPE_STRING, size, &name ); + free( name ); + } +} + static NTSTATUS WINAPI pdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); @@ -432,28 +534,7 @@ static NTSTATUS WINAPI pdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) } case IRP_MN_START_DEVICE: { - union - { - UINT64 uint; - BYTE addr[8]; - } radio_addr; - - if (device->props_mask & WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS) - { - memset( &radio_addr, 0, sizeof( radio_addr ) ); - memcpy( &radio_addr.addr[2], device->props.address.rgBytes, - sizeof( device->props.address.rgBytes ) ); - IoSetDevicePropertyData( device_obj, &DEVPKEY_BluetoothRadio_Address, - LOCALE_NEUTRAL, 0, DEVPROP_TYPE_UINT64, - sizeof( radio_addr ), &radio_addr ); - } - if (device->props_mask & WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER) - { - UINT16 manufacturer = device->props.manufacturer; - IoSetDevicePropertyData( device_obj, &DEVPKEY_BluetoothRadio_Manufacturer, - LOCALE_NEUTRAL, 0, DEVPROP_TYPE_UINT16, - sizeof( manufacturer ), &manufacturer ); - } + bluetooth_radio_set_properties( device_obj, device->props_mask, &device->props );
if (IoRegisterDeviceInterface( device_obj, &GUID_BTHPORT_DEVICE_INTERFACE, NULL, &device->bthport_symlink_name ) == STATUS_SUCCESS) diff --git a/include/ddk/bthguid.h b/include/ddk/bthguid.h index ce0f4adfad5..0dd86f7fc12 100644 --- a/include/ddk/bthguid.h +++ b/include/ddk/bthguid.h @@ -47,7 +47,16 @@ DEFINE_BTH_RADIO_DEVPROPKEY( LELocalSupportedFeatures, 22 ); /* DEVPROP_TY
/* Authentication I/O capability as currently advertised by the radio. DEVPROP_TYPE_BYTE */ DEFINE_WINEBTH_RADIO_DEVPROPKEY( Capability, 1 ); - +/* If device discovery is active for this radio. DEVPROP_TYPE_BOOLEAN */ +DEFINE_WINEBTH_RADIO_DEVPROPKEY( Discovering, 2 ); +/* If the radio is pariable. DEVPROP_TYPE_BOOLEAN */ +DEFINE_WINEBTH_RADIO_DEVPROPKEY( Pairable, 3 ); +/* If the radio is discoverable/visible. DEVPROP_TYPE_BOOLEAN */ +DEFINE_WINEBTH_RADIO_DEVPROPKEY( Discoverable, 4 ); +/* If the radio is connectable. DEVPROP_TYPE_BOOLEAN */ +DEFINE_WINEBTH_RADIO_DEVPROPKEY( Connectable, 5 ); +/* The class of device for this radio. DEVPROP_TYPE_UINT32 */ +DEFINE_WINEBTH_RADIO_DEVPROPKEY( Class, 6 ); #undef DEFINE_BTH_RADIO_DEVPROPKEY #endif /* WINE_BTH_EXTENSIONS */