The Windows Bluetooth stack uses private IOCTLs in order to set the [discoverability](https://learn.microsoft.com/en-us/windows/win32/api/bluetoothapis/nf-bluetoo...) and [connectability](https://learn.microsoft.com/en-us/windows/win32/api/bluetoothapis/nf-bluetoo...) status of a local radio. This MR introduces the wine-specific IOCTL `IOCTL_WINBTH_RADIO_SET_FLAG` for the same purpose, as most applications seem to the Win32 API itself for these operations.
-- v13: bluetoothapis: Implement BluetoothEnableDiscovery. bluetoothapis/tests: Add tests for BluetoothEnableDiscovery. bluetoothapis: Add stub for BluetoothEnableDiscovery. bluetoothapis: Implement BluetoothEnableIncomingConnections. bluetoothapis/tests: Add tests for BluetoothEnableIncomingConnections. bluetoothapis: Add stub for BluetoothEnableIncomingConnections. winebth.sys: Call bluez_watcher_close as part of bluetooth_shutdown. winebth.sys: Implement IOCTL_WINEBTH_RADIO_SET_FLAG.
From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/Makefile.in | 1 + dlls/winebth.sys/dbus.c | 150 +++++++++++++++++++++++++++++++ dlls/winebth.sys/dbus.h | 2 + dlls/winebth.sys/unixlib.c | 10 +++ dlls/winebth.sys/unixlib.h | 10 +++ dlls/winebth.sys/unixlib_priv.h | 3 + dlls/winebth.sys/winebluetooth.c | 15 ++++ dlls/winebth.sys/winebth.c | 37 +++++++- dlls/winebth.sys/winebth_priv.h | 16 +++- include/Makefile.in | 1 + include/wine/winebth.h | 42 +++++++++ 11 files changed, 282 insertions(+), 5 deletions(-) create mode 100644 include/wine/winebth.h
diff --git a/dlls/winebth.sys/Makefile.in b/dlls/winebth.sys/Makefile.in index 12d1b4ddcc9..b09cfe40aa7 100644 --- a/dlls/winebth.sys/Makefile.in +++ b/dlls/winebth.sys/Makefile.in @@ -2,6 +2,7 @@ MODULE = winebth.sys IMPORTS = ntoskrnl uuid UNIXLIB = winebth.so UNIX_CFLAGS = $(DBUS_CFLAGS) +UNIX_LIBS = $(PTHREAD_LIBS)
EXTRADLLFLAGS = -Wl,--subsystem,native
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 1cd6870e7de..192e52e5202 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -2,6 +2,7 @@ * Support for communicating with BlueZ over DBus. * * Copyright 2024 Vibhav Pant + * Copyright 2025 Vibhav Pant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -29,6 +30,9 @@ #include <dlfcn.h> #include <assert.h> #include <pthread.h> +#include <semaphore.h> +#include <errno.h> +#include <string.h>
#ifdef SONAME_LIBDBUS_1 #include <dbus/dbus.h> @@ -264,6 +268,148 @@ static void parse_mac_address( const char *addr_str, BYTE dest[6] ) dest[i] = addr[i]; }
+static void bluez_dbus_wait_for_reply_callback( DBusPendingCall *pending_call, void *wait ) +{ + sem_post( wait ); +} + +static NTSTATUS bluez_dbus_pending_call_wait( DBusPendingCall *pending_call, DBusMessage **reply, DBusError *error ) +{ + sem_t wait; + + sem_init( &wait, 0, 0 ); + if (!p_dbus_pending_call_set_notify( pending_call, bluez_dbus_wait_for_reply_callback, &wait, NULL )) + { + sem_destroy( &wait ); + p_dbus_pending_call_unref( pending_call ); + return STATUS_NO_MEMORY; + } + for (;;) + { + int ret = sem_wait( &wait ); + if (!ret) + { + *reply = p_dbus_pending_call_steal_reply( pending_call ); + if (p_dbus_set_error_from_message( error, *reply )) + { + p_dbus_message_unref( *reply ); + *reply = NULL; + } + p_dbus_pending_call_unref( pending_call ); + sem_destroy( &wait ); + return STATUS_SUCCESS; + } + if (errno == EINTR) + continue; + + ERR( "Failed to wait for DBus method reply: %s\n", debugstr_a( strerror( errno ) ) ); + sem_destroy( &wait ); + p_dbus_pending_call_cancel( pending_call ); + p_dbus_pending_call_unref( pending_call ); + return STATUS_INTERNAL_ERROR; + } +} + +/* Like dbus_connection_send_with_reply_and_block, but it does not acquire a lock on the connection, instead relying on + * the main loop in bluez_dbus_loop. This is faster than send_with_reply_and_block. + * This takes ownership of the request, so there is no need to unref it. */ +static NTSTATUS bluez_dbus_send_and_wait_for_reply( DBusConnection *connection, DBusMessage *request, DBusMessage **reply, + DBusError *error ) +{ + DBusPendingCall *pending_call; + dbus_bool_t success; + + success = p_dbus_connection_send_with_reply( connection, request, &pending_call, bluez_timeout ); + p_dbus_message_unref( request ); + + if (!success) + return STATUS_NO_MEMORY; + return bluez_dbus_pending_call_wait( pending_call, reply, error ); +} + +NTSTATUS bluez_adapter_set_prop( void *connection, struct bluetooth_adapter_set_prop_params *params ) +{ + DBusMessage *request, *reply; + DBusMessageIter iter, sub_iter; + DBusError error; + DBusBasicValue val; + int val_type; + static const char *adapter_iface = BLUEZ_INTERFACE_ADAPTER; + const char *prop_name; + NTSTATUS status; + + TRACE( "(%p, %p)\n", connection, params ); + + switch (params->prop_flag) + { + case WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE: + prop_name = "Discoverable"; + val.bool_val = params->prop->boolean; + val_type = DBUS_TYPE_BOOLEAN; + break; + case WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE: + prop_name = "Connectable"; + val.bool_val = params->prop->boolean; + val_type = DBUS_TYPE_BOOLEAN; + break; + default: + return STATUS_INVALID_PARAMETER; + } + + TRACE( "Setting property %s for adapter %s\n", debugstr_a( prop_name ), debugstr_a( params->adapter->str ) ); + request = p_dbus_message_new_method_call( BLUEZ_DEST, params->adapter->str, + DBUS_INTERFACE_PROPERTIES, "Set" ); + if (!request) return STATUS_NO_MEMORY; + + p_dbus_message_iter_init_append( request, &iter ); + if (!p_dbus_message_iter_append_basic( &iter, DBUS_TYPE_STRING, &adapter_iface )) + { + p_dbus_message_unref( request ); + return STATUS_NO_MEMORY; + } + if (!p_dbus_message_iter_append_basic( &iter, DBUS_TYPE_STRING, &prop_name )) + { + p_dbus_message_unref( request ); + return STATUS_NO_MEMORY; + } + if (!p_dbus_message_iter_open_container( &iter, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &sub_iter )) + { + p_dbus_message_unref( request ); + return STATUS_NO_MEMORY; + } + if (!p_dbus_message_iter_append_basic( &sub_iter, val_type, &val )) + { + p_dbus_message_iter_abandon_container( &iter, &sub_iter ); + p_dbus_message_unref( request ); + return STATUS_NO_MEMORY; + } + if (!p_dbus_message_iter_close_container( &iter, &sub_iter )) + { + p_dbus_message_unref( request ); + return STATUS_NO_MEMORY; + } + + p_dbus_error_init( &error ); + status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error ); + if (status) + { + p_dbus_error_free( &error ); + return status; + } + if (!reply) + { + ERR( "Failed to set property %s for adapter %s: %s: %s\n", debugstr_a( prop_name ), + debugstr_a( params->adapter->str ), debugstr_a( error.name ), debugstr_a( error.message ) ); + status = bluez_dbus_error_to_ntstatus( &error ); + p_dbus_error_free( &error ); + return status; + } + p_dbus_error_free( &error ); + p_dbus_message_unref( reply ); + + return STATUS_SUCCESS; +} + 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, @@ -977,5 +1123,9 @@ NTSTATUS bluez_dbus_loop( void *c, void *watcher, struct winebluetooth_event *re { return STATUS_NOT_SUPPORTED; } +NTSTATUS bluez_adapter_set_prop( void *connection, struct bluetooth_adapter_set_prop_params *params ) +{ + return STATUS_NOT_SUPPORTED; +}
#endif /* SONAME_LIBDBUS_1 */ diff --git a/dlls/winebth.sys/dbus.h b/dlls/winebth.sys/dbus.h index 025d2c6fd2c..19b94776fd3 100644 --- a/dlls/winebth.sys/dbus.h +++ b/dlls/winebth.sys/dbus.h @@ -2,6 +2,7 @@ * DBus declarations. * * Copyright 2024 Vibhav Pant + * Copyright 2025 Vibhav Pant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -79,6 +80,7 @@ DO_FUNC(dbus_message_is_method_call); \ DO_FUNC(dbus_message_is_signal); \ DO_FUNC(dbus_message_iter_append_basic); \ + DO_FUNC(dbus_message_iter_abandon_container); \ DO_FUNC(dbus_message_iter_close_container); \ DO_FUNC(dbus_message_iter_get_arg_type); \ DO_FUNC(dbus_message_iter_get_element_type); \ diff --git a/dlls/winebth.sys/unixlib.c b/dlls/winebth.sys/unixlib.c index 09641be6396..982652498e5 100644 --- a/dlls/winebth.sys/unixlib.c +++ b/dlls/winebth.sys/unixlib.c @@ -2,6 +2,7 @@ * winebluetooth Unix interface * * Copyright 2024 Vibhav Pant + * Copyright 2025 Vibhav Pant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -154,6 +155,14 @@ static NTSTATUS bluetooth_adapter_free( void *args ) return STATUS_SUCCESS; }
+static NTSTATUS bluetooth_adapter_set_prop( void *arg ) +{ + struct bluetooth_adapter_set_prop_params *params = arg; + + if (!dbus_connection) return STATUS_NOT_SUPPORTED; + return bluez_adapter_set_prop( dbus_connection, params ); +} + static NTSTATUS bluetooth_get_event( void *args ) { struct bluetooth_get_event_params *params = args; @@ -167,6 +176,7 @@ const unixlib_entry_t __wine_unix_call_funcs[] = { bluetooth_init, bluetooth_shutdown,
+ bluetooth_adapter_set_prop, bluetooth_adapter_get_unique_name, bluetooth_adapter_free,
diff --git a/dlls/winebth.sys/unixlib.h b/dlls/winebth.sys/unixlib.h index 45a000a2e88..b16f873deef 100644 --- a/dlls/winebth.sys/unixlib.h +++ b/dlls/winebth.sys/unixlib.h @@ -2,6 +2,7 @@ * Unix interface definitions * * Copyright 2024 Vibhav Pant + * Copyright 2025 Vibhav Pant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -56,6 +57,14 @@ struct bluetooth_adapter_get_unique_name_params SIZE_T buf_size; };
+struct bluetooth_adapter_set_prop_params +{ + unix_name_t adapter; + winebluetooth_radio_props_mask_t prop_flag; + + union winebluetooth_property *prop; +}; + struct bluetooth_get_event_params { struct winebluetooth_event result; @@ -66,6 +75,7 @@ enum bluetoothapis_funcs unix_bluetooth_init, unix_bluetooth_shutdown,
+ unix_bluetooth_adapter_set_prop, unix_bluetooth_adapter_get_unique_name, unix_bluetooth_adapter_free,
diff --git a/dlls/winebth.sys/unixlib_priv.h b/dlls/winebth.sys/unixlib_priv.h index 5ed5204e930..5146799e705 100644 --- a/dlls/winebth.sys/unixlib_priv.h +++ b/dlls/winebth.sys/unixlib_priv.h @@ -2,6 +2,7 @@ * Bluetoothapis Unix interface * * Copyright 2024 Vibhav Pant + * Copyright 2025 Vibhav Pant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -47,6 +48,8 @@ extern void *bluez_dbus_init( void ); 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_adapter_set_prop( void *connection, + struct bluetooth_adapter_set_prop_params *params ); extern NTSTATUS bluez_watcher_init( void *connection, void **ctx ); extern void bluez_watcher_close( void *connection, void *ctx ); #endif /* __WINE_WINEBTH_UNIXLIB_PRIV_H */ diff --git a/dlls/winebth.sys/winebluetooth.c b/dlls/winebth.sys/winebluetooth.c index 3d5aa77094d..8f83e3f1155 100644 --- a/dlls/winebth.sys/winebluetooth.c +++ b/dlls/winebth.sys/winebluetooth.c @@ -2,6 +2,7 @@ * Wine bluetooth APIs * * Copyright 2024 Vibhav Pant + * Copyright 2025 Vibhav Pant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -53,6 +54,20 @@ NTSTATUS winebluetooth_radio_get_unique_name( winebluetooth_radio_t radio, char return status; }
+NTSTATUS winebluetooth_radio_set_property( winebluetooth_radio_t radio, + winebluetooth_radio_props_mask_t prop_flag, + union winebluetooth_property *property ) +{ + struct bluetooth_adapter_set_prop_params params = { 0 }; + + TRACE( "(%p, %#x, %p)\n", (void *)radio.handle, prop_flag, property ); + + params.adapter = radio.handle; + params.prop_flag = prop_flag; + params.prop = property; + return UNIX_BLUETOOTH_CALL( bluetooth_adapter_set_prop, ¶ms ); +} + void winebluetooth_radio_free( winebluetooth_radio_t radio ) { struct bluetooth_adapter_free_params args = { 0 }; diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 5ba846e6459..58c62e1828b 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -1,7 +1,7 @@ /* * Bluetooth bus driver * - * Copyright 2024 Vibhav Pant + * Copyright 2024-2025 Vibhav Pant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -37,6 +37,7 @@ #include <bthioctl.h> #include <ddk/wdm.h>
+#include <wine/winebth.h> #include <wine/debug.h> #include <wine/list.h>
@@ -84,6 +85,7 @@ 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 insize = stack->Parameters.DeviceIoControl.InputBufferLength; ULONG outsize = stack->Parameters.DeviceIoControl.OutputBufferLength; NTSTATUS status = irp->IoStatus.Status;
@@ -133,6 +135,39 @@ static NTSTATUS WINAPI dispatch_bluetooth( DEVICE_OBJECT *device, IRP *irp ) status = STATUS_SUCCESS; break; } + case IOCTL_WINEBTH_RADIO_SET_FLAG: + { + const struct winebth_radio_set_flag_params *params = irp->AssociatedIrp.SystemBuffer; + union winebluetooth_property prop_value = {0}; + + if (!params) + { + status = STATUS_INVALID_PARAMETER; + break; + } + if (insize < sizeof( *params )) + { + status = STATUS_BUFFER_TOO_SMALL; + break; + } + + prop_value.boolean = !!params->enable; + switch (params->flag) + { + case LOCAL_RADIO_CONNECTABLE: + status = + winebluetooth_radio_set_property( ext->radio, WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE, &prop_value ); + + break; + case LOCAL_RADIO_DISCOVERABLE: + status = + winebluetooth_radio_set_property( ext->radio, WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE, &prop_value ); + break; + default: + status = STATUS_INVALID_PARAMETER; + } + break; + } default: FIXME( "Unimplemented IOCTL code: %#lx\n", code ); break; diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index 117b02f3294..06dc3a8b799 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -2,6 +2,7 @@ * Private winebth.sys defs * * Copyright 2024 Vibhav Pant + * Copyright 2025 Vibhav Pant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -108,10 +109,6 @@ DEFINE_BTH_RADIO_DEVPROPKEY( HCIVendorFeatures, 8 ); /* DEVPROP_TY DEFINE_BTH_RADIO_DEVPROPKEY( MaximumAdvertisementDataLength, 17 ); /* DEVPROP_TYPE_UINT16 */ DEFINE_BTH_RADIO_DEVPROPKEY( LELocalSupportedFeatures, 22 ); /* DEVPROP_TYPE_UINT64 */
-/* Valid masks for the "flags" field in BTH_LOCAL_RADIO_INFO. */ -#define LOCAL_RADIO_DISCOVERABLE 0x0001 -#define LOCAL_RADIO_CONNECTABLE 0x0002 - typedef struct { UINT_PTR handle; @@ -136,6 +133,14 @@ typedef UINT16 winebluetooth_radio_props_mask_t; WINEBLUETOOTH_RADIO_PROPERTY_VERSION | WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING | \ WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE)
+union winebluetooth_property +{ + BOOL boolean; + ULONG ulong; + BLUETOOTH_ADDRESS address; + WCHAR name[BLUETOOTH_MAX_NAME_SIZE]; +}; + struct winebluetooth_radio_properties { BOOL discoverable; @@ -156,6 +161,9 @@ static inline BOOL winebluetooth_radio_equal( winebluetooth_radio_t r1, wineblue { return r1.handle == r2.handle; } +NTSTATUS winebluetooth_radio_set_property( winebluetooth_radio_t radio, + winebluetooth_radio_props_mask_t prop, + union winebluetooth_property *property );
enum winebluetooth_watcher_event_type { diff --git a/include/Makefile.in b/include/Makefile.in index cb2b83b6d8c..1886c623466 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -957,6 +957,7 @@ SOURCES = \ wine/wgl_driver.h \ wine/winbase16.h \ wine/windef16.h \ + wine/winebth.h \ wine/wine_common_ver.rc \ wine/wined3d.h \ wine/winedmo.h \ diff --git a/include/wine/winebth.h b/include/wine/winebth.h new file mode 100644 index 00000000000..c26a2100fb9 --- /dev/null +++ b/include/wine/winebth.h @@ -0,0 +1,42 @@ +/* + * Wine-specific IOCTL definitions for interfacing with winebth.sys + * + * Copyright 2025 Vibhav Pant + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + */ + +#ifndef __WINEBTH_H__ +#define __WINEBTH_H__ + +/* Set the discoverability or connectable flag for a local radio. Enabling discoverability will also enable incoming + * connections, while disabling incoming connections disables discoverability as well. */ +#define IOCTL_WINEBTH_RADIO_SET_FLAG CTL_CODE(FILE_DEVICE_BLUETOOTH, 0xa3, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#include <pshpack1.h> + +#define LOCAL_RADIO_DISCOVERABLE 0x0001 +#define LOCAL_RADIO_CONNECTABLE 0x0002 + +struct winebth_radio_set_flag_params +{ + unsigned int flag: 2; + unsigned int enable : 1; +}; + +#include <poppack.h> + +#endif /* __WINEBTH_H__ */
From: Vibhav Pant vibhavp@gmail.com
This frees all associated resources with the watcher, including events and initial devices that were not consumed by the PE driver. --- dlls/winebth.sys/dbus.c | 63 ++++++++++++++++++++++++++++++++------ dlls/winebth.sys/unixlib.c | 1 + 2 files changed, 55 insertions(+), 9 deletions(-)
diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 192e52e5202..0d4bc25f08a 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -524,6 +524,14 @@ struct bluez_watcher_ctx struct list event_list; };
+struct bluez_init_entry +{ + union { + struct winebluetooth_watcher_event_radio_added radio; + } object; + struct list entry; +}; + void *bluez_dbus_init( void ) { DBusError error; @@ -887,6 +895,46 @@ static const char BLUEZ_MATCH_PROPERTIES[] = "type='signal',"
static const char *BLUEZ_MATCH_RULES[] = { BLUEZ_MATCH_OBJECTMANAGER, BLUEZ_MATCH_PROPERTIES };
+/* Free up the watcher alongside any remaining events and initial devices and other associated resources. */ +static void bluez_watcher_free( struct bluez_watcher_ctx *watcher ) +{ + struct bluez_watcher_event *event1, *event2; + struct bluez_init_entry *entry1, *entry2; + + if (watcher->init_device_list_call) + { + p_dbus_pending_call_cancel( watcher->init_device_list_call ); + p_dbus_pending_call_unref( watcher->init_device_list_call ); + } + + LIST_FOR_EACH_ENTRY_SAFE( entry1, entry2, &watcher->initial_radio_list, struct bluez_init_entry, entry ) + { + list_remove( &entry1->entry ); + unix_name_free( (struct unix_name *)entry1->object.radio.radio.handle ); + free( entry1 ); + } + + LIST_FOR_EACH_ENTRY_SAFE( event1, event2, &watcher->event_list, struct bluez_watcher_event, entry ) + { + list_remove( &event1->entry ); + switch (event1->event_type) + { + case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED: + unix_name_free( (struct unix_name *)event1->event.radio_added.radio.handle ); + break; + case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED: + unix_name_free( (struct unix_name *)event1->event.radio_removed.handle ); + break; + case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED: + unix_name_free( (struct unix_name *)event1->event.radio_props_changed.radio.handle ); + break; + } + free( event1 ); + } + + free( watcher ); +} + NTSTATUS bluez_watcher_init( void *connection, void **ctx ) { DBusError err; @@ -909,7 +957,10 @@ NTSTATUS bluez_watcher_init( void *connection, void **ctx )
list_init( &watcher_ctx->event_list );
- if (!p_dbus_connection_add_filter( connection, bluez_filter, watcher_ctx, free )) + /* The bluez_dbus_loop thread will free up the watcher when the disconnect message is processed (i.e, + * dbus_connection_read_write_dispatch returns false). Using a free-function with dbus_connection_add_filter + * is racy as the filter is removed from a different thread. */ + if (!p_dbus_connection_add_filter( connection, bluez_filter, watcher_ctx, NULL )) { p_dbus_pending_call_cancel( call ); p_dbus_pending_call_unref( call ); @@ -958,14 +1009,6 @@ void bluez_watcher_close( void *connection, void *ctx ) p_dbus_connection_remove_filter( connection, bluez_filter, ctx ); }
-struct bluez_init_entry -{ - union { - struct winebluetooth_watcher_event_radio_added radio; - } object; - struct list entry; -}; - static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct list *adapter_list ) { DBusMessageIter dict, paths_iter, iface_iter, prop_iter; @@ -1077,6 +1120,7 @@ NTSTATUS bluez_dbus_loop( void *c, void *watcher, } else if (!p_dbus_connection_read_write_dispatch( connection, 100 )) { + bluez_watcher_free( watcher_ctx ); p_dbus_connection_unref( connection ); TRACE( "Disconnected from DBus\n" ); return STATUS_SUCCESS; @@ -1119,6 +1163,7 @@ void *bluez_dbus_init( void ) { return NULL; } void bluez_dbus_close( void *connection ) {} void bluez_dbus_free( void *connection ) {} NTSTATUS bluez_watcher_init( void *connection, void **ctx ) { return STATUS_NOT_SUPPORTED; } +void bluez_watcher_close( void *connection, void *ctx ) {} NTSTATUS bluez_dbus_loop( void *c, void *watcher, struct winebluetooth_event *result ) { return STATUS_NOT_SUPPORTED; diff --git a/dlls/winebth.sys/unixlib.c b/dlls/winebth.sys/unixlib.c index 982652498e5..b7bf4012ce3 100644 --- a/dlls/winebth.sys/unixlib.c +++ b/dlls/winebth.sys/unixlib.c @@ -115,6 +115,7 @@ static NTSTATUS bluetooth_shutdown( void *params ) if (!dbus_connection) return STATUS_NOT_SUPPORTED;
bluez_dbus_close( dbus_connection ); + bluez_watcher_close( dbus_connection, bluetooth_watcher ); bluez_dbus_free( dbus_connection ); return STATUS_SUCCESS; }
From: Vibhav Pant vibhavp@gmail.com
--- MAINTAINERS | 2 +- dlls/bluetoothapis/bluetoothapis.spec | 2 +- dlls/bluetoothapis/main.c | 10 ++++++++++ dlls/bthprops.cpl/bthprops.cpl.spec | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS index 2510938d94e..87e2895ddf3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -52,7 +52,7 @@ F: dlls/xaudio*/ Bluetooth support M: Vibhav Pant vibhavp@gmail.com F: dlls/winebth.sys/ -F: dlls/bluetoothapis/sdp.c +F: dlls/bluetoothapis/
Common Controls Library P: Nikolay Sivov nsivov@codeweavers.com diff --git a/dlls/bluetoothapis/bluetoothapis.spec b/dlls/bluetoothapis/bluetoothapis.spec index a28a493311d..5277ff825be 100644 --- a/dlls/bluetoothapis/bluetoothapis.spec +++ b/dlls/bluetoothapis/bluetoothapis.spec @@ -1,7 +1,7 @@ @ stub BluetoothAddressToString @ stub BluetoothDisconnectDevice @ stub BluetoothEnableDiscovery -@ stub BluetoothEnableIncomingConnections +@ stdcall BluetoothEnableIncomingConnections(ptr long) @ stub BluetoothEnumerateInstalledServices @ stub BluetoothEnumerateInstalledServicesEx @ stub BluetoothEnumerateLocalServices diff --git a/dlls/bluetoothapis/main.c b/dlls/bluetoothapis/main.c index fd3496e3fab..299eddbc876 100644 --- a/dlls/bluetoothapis/main.c +++ b/dlls/bluetoothapis/main.c @@ -2,6 +2,7 @@ * Bluetooth APIs * * Copyright 2016 Austin English + * Copyright 2025 Vibhav Pant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -263,6 +264,15 @@ BOOL WINAPI BluetoothIsConnectable( HANDLE radio ) } }
+/********************************************************************* + * BluetoothEnableIncomingConnections + */ +BOOL WINAPI BluetoothEnableIncomingConnections( HANDLE radio, BOOL enable ) +{ + FIXME( "(%p, %d): stub!\n", radio, enable ); + return FALSE; +} + /********************************************************************* * BluetoothIsDiscoverable */ diff --git a/dlls/bthprops.cpl/bthprops.cpl.spec b/dlls/bthprops.cpl/bthprops.cpl.spec index 751eec41e79..2e72c48dcc4 100644 --- a/dlls/bthprops.cpl/bthprops.cpl.spec +++ b/dlls/bthprops.cpl/bthprops.cpl.spec @@ -6,7 +6,7 @@ @ stub BluetoothDisconnectDevice @ stub BluetoothDisplayDeviceProperties @ stub BluetoothEnableDiscovery -@ stub BluetoothEnableIncomingConnections +@ stdcall -import BluetoothEnableIncomingConnections(ptr long) @ stub BluetoothEnumerateInstalledServices @ stub BluetoothEnumerateInstalledServicesEx @ stub BluetoothFindBrowseGroupClose
From: Vibhav Pant vibhavp@gmail.com
--- dlls/bluetoothapis/tests/radio.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+)
diff --git a/dlls/bluetoothapis/tests/radio.c b/dlls/bluetoothapis/tests/radio.c index 60e0a7f4ebc..d16dfcde102 100644 --- a/dlls/bluetoothapis/tests/radio.c +++ b/dlls/bluetoothapis/tests/radio.c @@ -219,6 +219,35 @@ void test_BluetoothIsDiscoverable( void ) ok( ret == result, "%d != %d\n", ret, result ); }
+void test_radio_BluetoothEnableIncomingConnections( HANDLE radio, void *data ) +{ + BOOL connectable; + BOOL *result = data; + + *result |= BluetoothEnableIncomingConnections( radio, TRUE ); + todo_wine ok( *result, "%d != 1\n", *result ); + if (*result) + { + connectable = BluetoothIsConnectable( radio ); + ok( connectable, "%d != 1\n", connectable ); + } + else + skip("BluetoothEnableIncomingConnections failed, skipping.\n"); +} + +void test_BluetoothEnableIncomingConnections( void ) +{ + BOOL result = FALSE; + + test_for_all_radios( test_radio_BluetoothEnableIncomingConnections, &result ); + if (result) + { + BOOL connectable; + connectable = BluetoothIsConnectable( NULL ); + ok( connectable, "%d != 1\n", connectable ); + } +} + START_TEST( radio ) { test_BluetoothFindFirstRadio(); @@ -228,4 +257,5 @@ START_TEST( radio ) test_for_all_radios( test_BluetoothGetRadioInfo, NULL ); test_BluetoothIsDiscoverable(); test_BluetoothIsConnectable(); + test_BluetoothEnableIncomingConnections(); }
From: Vibhav Pant vibhavp@gmail.com
--- dlls/bluetoothapis/main.c | 47 ++++++++++++++++++++++++++++---- dlls/bluetoothapis/tests/radio.c | 2 +- 2 files changed, 43 insertions(+), 6 deletions(-)
diff --git a/dlls/bluetoothapis/main.c b/dlls/bluetoothapis/main.c index 299eddbc876..646f73f44e6 100644 --- a/dlls/bluetoothapis/main.c +++ b/dlls/bluetoothapis/main.c @@ -32,6 +32,7 @@ #include "bluetoothapis.h" #include "setupapi.h" #include "winioctl.h" +#include "wine/winebth.h"
#include "initguid.h" #include "bthdef.h" @@ -213,9 +214,6 @@ DWORD WINAPI BluetoothGetRadioInfo( HANDLE radio, PBLUETOOTH_RADIO_INFO info ) return ERROR_SUCCESS; }
-#define LOCAL_RADIO_DISCOVERABLE 0x0001 -#define LOCAL_RADIO_CONNECTABLE 0x0002 - /********************************************************************* * BluetoothIsConnectable */ @@ -269,8 +267,47 @@ BOOL WINAPI BluetoothIsConnectable( HANDLE radio ) */ BOOL WINAPI BluetoothEnableIncomingConnections( HANDLE radio, BOOL enable ) { - FIXME( "(%p, %d): stub!\n", radio, enable ); - return FALSE; + TRACE( "(%p, %d)\n", radio, enable ); + if (!radio) + { + BLUETOOTH_FIND_RADIO_PARAMS params = {.dwSize = sizeof( params )}; + HBLUETOOTH_RADIO_FIND find = BluetoothFindFirstRadio( ¶ms, &radio ); + + if (!find) + return FALSE; + for (;;) + { + if (BluetoothEnableIncomingConnections( radio, enable )) + { + CloseHandle( radio ); + BluetoothFindRadioClose( find ); + return TRUE; + } + + CloseHandle(radio ); + if (!BluetoothFindNextRadio( find, &radio )) + { + BluetoothFindRadioClose( find ); + return FALSE; + } + } + } + else if (!enable && !BluetoothIsDiscoverable( radio )) + /* The local radio can only be made non-connectable if it is non-discoverable. */ + return FALSE; + else + { + struct winebth_radio_set_flag_params params = {0}; + BOOL ret; + DWORD bytes; + + params.flag = LOCAL_RADIO_CONNECTABLE; + params.enable = !!enable; + ret = DeviceIoControl( radio, IOCTL_WINEBTH_RADIO_SET_FLAG, ¶ms, sizeof( params ), NULL, 0, &bytes, NULL ); + if (!ret) + ERR("DeviceIoControl failed: %#lx\n", GetLastError()); + return ret; + } }
/********************************************************************* diff --git a/dlls/bluetoothapis/tests/radio.c b/dlls/bluetoothapis/tests/radio.c index d16dfcde102..1f967e43e06 100644 --- a/dlls/bluetoothapis/tests/radio.c +++ b/dlls/bluetoothapis/tests/radio.c @@ -225,7 +225,7 @@ void test_radio_BluetoothEnableIncomingConnections( HANDLE radio, void *data ) BOOL *result = data;
*result |= BluetoothEnableIncomingConnections( radio, TRUE ); - todo_wine ok( *result, "%d != 1\n", *result ); + ok( *result, "%d != 1\n", *result ); if (*result) { connectable = BluetoothIsConnectable( radio );
From: Vibhav Pant vibhavp@gmail.com
--- dlls/bluetoothapis/bluetoothapis.spec | 2 +- dlls/bluetoothapis/main.c | 9 +++++++++ dlls/bthprops.cpl/bthprops.cpl.spec | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/dlls/bluetoothapis/bluetoothapis.spec b/dlls/bluetoothapis/bluetoothapis.spec index 5277ff825be..77a3a931a55 100644 --- a/dlls/bluetoothapis/bluetoothapis.spec +++ b/dlls/bluetoothapis/bluetoothapis.spec @@ -1,6 +1,6 @@ @ stub BluetoothAddressToString @ stub BluetoothDisconnectDevice -@ stub BluetoothEnableDiscovery +@ stdcall BluetoothEnableDiscovery(ptr long) @ stdcall BluetoothEnableIncomingConnections(ptr long) @ stub BluetoothEnumerateInstalledServices @ stub BluetoothEnumerateInstalledServicesEx diff --git a/dlls/bluetoothapis/main.c b/dlls/bluetoothapis/main.c index 646f73f44e6..8a3acc781bd 100644 --- a/dlls/bluetoothapis/main.c +++ b/dlls/bluetoothapis/main.c @@ -358,6 +358,15 @@ BOOL WINAPI BluetoothIsDiscoverable( HANDLE radio ) } }
+/********************************************************************* + * BluetoothEnableDiscovery + */ +BOOL WINAPI BluetoothEnableDiscovery( HANDLE radio, BOOL enabled ) +{ + FIXME("(%p %d): stub!\n", radio, enabled); + return FALSE; +} + /********************************************************************* * BluetoothFindNextDevice */ diff --git a/dlls/bthprops.cpl/bthprops.cpl.spec b/dlls/bthprops.cpl/bthprops.cpl.spec index 2e72c48dcc4..dee5544baaf 100644 --- a/dlls/bthprops.cpl/bthprops.cpl.spec +++ b/dlls/bthprops.cpl/bthprops.cpl.spec @@ -5,7 +5,7 @@ @ stub BluetoothAuthenticationAgent @ stub BluetoothDisconnectDevice @ stub BluetoothDisplayDeviceProperties -@ stub BluetoothEnableDiscovery +@ stdcall -import BluetoothEnableDiscovery(ptr long) @ stdcall -import BluetoothEnableIncomingConnections(ptr long) @ stub BluetoothEnumerateInstalledServices @ stub BluetoothEnumerateInstalledServicesEx
From: Vibhav Pant vibhavp@gmail.com
--- dlls/bluetoothapis/tests/radio.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+)
diff --git a/dlls/bluetoothapis/tests/radio.c b/dlls/bluetoothapis/tests/radio.c index 1f967e43e06..903f6010edc 100644 --- a/dlls/bluetoothapis/tests/radio.c +++ b/dlls/bluetoothapis/tests/radio.c @@ -248,6 +248,33 @@ void test_BluetoothEnableIncomingConnections( void ) } }
+void test_radio_BluetoothEnableDiscovery( HANDLE radio, void *data ) +{ + BOOL ret; + + if (BluetoothIsDiscoverable( radio )) + { + ret = BluetoothEnableDiscovery( radio, FALSE ); + todo_wine ok( ret, "%d != 1\n", ret ); + } + + /* Enabling discovery requires incoming connections to be enabled as well, so this should result in an error. */ + BluetoothEnableIncomingConnections( radio, FALSE ); + ret = BluetoothEnableDiscovery( radio, TRUE ); + ok( !ret, "%d != 0\n", ret ); + + BluetoothEnableIncomingConnections( radio, TRUE ); + ret = BluetoothEnableDiscovery( radio, TRUE ); + todo_wine ok( ret, "%d != 1\n", ret ); + ret = BluetoothIsDiscoverable( radio ); + todo_wine ok ( ret, "%d != 1\n", ret ); +} + +void test_BluetoothEnableDiscovery( void ) +{ + test_for_all_radios( test_radio_BluetoothEnableDiscovery, NULL ); +} + START_TEST( radio ) { test_BluetoothFindFirstRadio(); @@ -258,4 +285,5 @@ START_TEST( radio ) test_BluetoothIsDiscoverable(); test_BluetoothIsConnectable(); test_BluetoothEnableIncomingConnections(); + test_BluetoothEnableDiscovery(); }
From: Vibhav Pant vibhavp@gmail.com
--- dlls/bluetoothapis/main.c | 43 ++++++++++++++++++++++++++++++-- dlls/bluetoothapis/tests/radio.c | 6 ++--- 2 files changed, 44 insertions(+), 5 deletions(-)
diff --git a/dlls/bluetoothapis/main.c b/dlls/bluetoothapis/main.c index 8a3acc781bd..6cdc84f6b6f 100644 --- a/dlls/bluetoothapis/main.c +++ b/dlls/bluetoothapis/main.c @@ -363,8 +363,47 @@ BOOL WINAPI BluetoothIsDiscoverable( HANDLE radio ) */ BOOL WINAPI BluetoothEnableDiscovery( HANDLE radio, BOOL enabled ) { - FIXME("(%p %d): stub!\n", radio, enabled); - return FALSE; + TRACE( "(%p, %d)\n", radio, enabled ); + if (!radio) + { + BLUETOOTH_FIND_RADIO_PARAMS params = {.dwSize = sizeof( params )}; + HBLUETOOTH_RADIO_FIND find = BluetoothFindFirstRadio( ¶ms, &radio ); + + if (!find) + return FALSE; + for (;;) + { + if (BluetoothEnableDiscovery( radio, enabled )) + { + CloseHandle( radio ); + BluetoothFindRadioClose( find ); + return TRUE; + } + + CloseHandle(radio ); + if (!BluetoothFindNextRadio( find, &radio )) + { + BluetoothFindRadioClose( find ); + return FALSE; + } + } + } + else if (enabled && !BluetoothIsConnectable( radio )) + /* The local radio can only be made discoverable if it is connectable. */ + return FALSE; + else + { + struct winebth_radio_set_flag_params params = {0}; + BOOL ret; + DWORD bytes; + + params.flag = LOCAL_RADIO_DISCOVERABLE; + params.enable = !!enabled; + ret = DeviceIoControl( radio, IOCTL_WINEBTH_RADIO_SET_FLAG, ¶ms, sizeof( params ), NULL, 0, &bytes, NULL ); + if (!ret) + ERR("DeviceIoControl failed: %#lx\n", GetLastError()); + return ret; + } }
/********************************************************************* diff --git a/dlls/bluetoothapis/tests/radio.c b/dlls/bluetoothapis/tests/radio.c index 903f6010edc..798c5ddce20 100644 --- a/dlls/bluetoothapis/tests/radio.c +++ b/dlls/bluetoothapis/tests/radio.c @@ -255,7 +255,7 @@ void test_radio_BluetoothEnableDiscovery( HANDLE radio, void *data ) if (BluetoothIsDiscoverable( radio )) { ret = BluetoothEnableDiscovery( radio, FALSE ); - todo_wine ok( ret, "%d != 1\n", ret ); + ok( ret, "%d != 1\n", ret ); }
/* Enabling discovery requires incoming connections to be enabled as well, so this should result in an error. */ @@ -265,9 +265,9 @@ void test_radio_BluetoothEnableDiscovery( HANDLE radio, void *data )
BluetoothEnableIncomingConnections( radio, TRUE ); ret = BluetoothEnableDiscovery( radio, TRUE ); - todo_wine ok( ret, "%d != 1\n", ret ); + ok( ret, "%d != 1\n", ret ); ret = BluetoothIsDiscoverable( radio ); - todo_wine ok ( ret, "%d != 1\n", ret ); + ok ( ret, "%d != 1\n", ret ); }
void test_BluetoothEnableDiscovery( void )
v2: * Removed `IOCTL_WINEBTH_RADIO_START_DISCOVERY` and `IOCTL_WINEBTH_RADIO_STOP_DISCOVERY`. * Added bluetoothapis methods `BluetoothEnableIncomingConnections` and `BluetoothEnableDiscovery`. * Replace `dbus_connection_send_with_reply_and_block` with `bluez_dbus_send_and_wait_for_reply`, letting `bluez_dbus_loop` handling the incoming message queue.
On Sat Feb 15 21:55:59 2025 +0000, Elizabeth Figura wrote:
Sorry for the late review, I don't have any good excuse. In general, these ioctls are a bit odd without the code on the bluetoothapis side to hook them up. It's probably reasonable to add those to this merge request. From 1/4:
+NTSTATUS winebluetooth_radio_set_property( winebluetooth_radio_t radio, + winebluetooth_radio_props_mask_t prop, + union winebluetooth_property *property )
The "property" parameter is not used.
+ switch (prop) + { + case WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE: + case WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE: + case WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE: + break; + default: + return STATUS_INVALID_PARAMETER; + }
The Unix side returns STATUS_INVALID_PARAMETER for the others anyway, so you might as well just get rid of this whole switch.
+ case LOCAL_RADIO_CONNECTABLE: + status = + winebluetooth_radio_set_property( ext->radio, WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE, &prop_value ); + + break; + case LOCAL_RADIO_DISCOVERABLE: + status = + winebluetooth_radio_set_property( ext->radio, WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE, &prop_value ); + break;
Why are we translating to a different flags enum? Why not pass this flag enum directly? You also implement the PAIRABLE property, but that code is never reached.
+ status = UNIX_BLUETOOTH_CALL( bluetooth_adapter_set_prop, ¶ms ); + + if (status != STATUS_SUCCESS) return status; + + return STATUS_SUCCESS;
This could just be "return UNIX_BLUETOOTH_CALL( ... )". To each their own style, I guess, but in general there are a lot of helpers that seem quite redundant... From 2/4:
+ if (filter->le && filter->bredr) + transport = WINEBLUETOOTH_DISCOVERY_TRANSPORT_AUTO; + else if (filter->le) + transport = WINEBLUETOOTH_DISCOVERY_TRANSPORT_LE; + else if (filter->bredr) + transport = WINEBLUETOOTH_DISCOVERY_TRANSPORT_BR_EDR; + else + transport = WINEBLUETOOTH_DISCOVERY_TRANSPORT_DEFAULT;
Why convert to an intermediate set of flags instead of just passing the params directly to the Unix side? Aside from the fact that the bitfield might not work, but it's pretty unnecessary anyway.
No worries!
In general, these ioctls are a bit odd without the code on the bluetoothapis side to hook them up. It's probably reasonable to add those to this merge request.
Makes sense. I have shortened this MR to only include `IOCTL_WINEBTH_RADIO_SET_FLAG`, as userspace support for the discovery ones require quite a bit of more code.
Why are we translating to a different flags enum? Why not pass this flag enum directly?
To keep it consistent. The `WINEBLUETOOTH_RADIO_PROPERTY` constants get used to inform the driver about property changes (`bluez_filter`, `bluetooth_radio_set_properties`), and to check if the properties for the adapter are known in `IOCTL_BTH_GET_LOCAL_INFO`. So, I thought it'd be better if the unix/dbus code only used the `WINEBLUETOOTH_*` constants.
Why convert to an intermediate set of flags instead of just passing the params directly to the Unix side?
Yeah, I'm also not sure if the bitfields would work across unixlib boundaries. I can just use `BOOL`s for LE and BR/EDR instead if that's better.
The "property" parameter is not used. The Unix side returns STATUS_INVALID_PARAMETER for the others anyway, so you might as well just get rid of this whole switch.
Fixed, thanks!
Why are we translating to a different flags enum? Why not pass this flag enum directly?
To keep it consistent. The `WINEBLUETOOTH_RADIO_PROPERTY` constants get used to inform the driver about property changes (`bluez_filter`, `bluetooth_radio_set_properties`), and to check if the properties for the adapter are known in `IOCTL_BTH_GET_LOCAL_INFO`. So, I thought it'd be better if the unix/dbus code only used the `WINEBLUETOOTH_*` constants.
It's bikeshedding, and I won't push very hard on this issue, but I can't say I see any reason to keep the unix side only using one set of constants. If anything it seems more obscure to me; it's clearer to do as little conversion as possible.
Why convert to an intermediate set of flags instead of just passing the params directly to the Unix side?
Yeah, I'm also not sure if the bitfields would work across unixlib boundaries. I can just use `BOOL`s for LE and BR/EDR in the next MR if that's better.
I'd recommend it. I don't know that the bitfields *won't* work, they very well may, but they're not really necessary either.