From: Vibhav Pant vibhavp@gmail.com
The unixlib implementation currently supports Linux using BlueZ over DBus. --- dlls/winebth.sys/Makefile.in | 8 +- dlls/winebth.sys/dbus.c | 789 +++++++++++++++++++++++++++++++ dlls/winebth.sys/dbus.h | 113 +++++ dlls/winebth.sys/unixlib.c | 188 ++++++++ dlls/winebth.sys/unixlib.h | 116 +++++ dlls/winebth.sys/unixlib_priv.h | 55 +++ dlls/winebth.sys/winebluetooth.c | 114 +++++ dlls/winebth.sys/winebluetooth.h | 147 ++++++ dlls/winebth.sys/winebth.c | 413 +++++++++++++++- dlls/winebth.sys/winebth_priv.h | 5 + include/Makefile.in | 2 + include/bthdef.h | 33 ++ include/ddk/bthguid.h | 57 +++ 13 files changed, 2035 insertions(+), 5 deletions(-) create mode 100644 dlls/winebth.sys/dbus.c create mode 100644 dlls/winebth.sys/dbus.h create mode 100644 dlls/winebth.sys/unixlib.c create mode 100644 dlls/winebth.sys/unixlib.h create mode 100644 dlls/winebth.sys/unixlib_priv.h create mode 100644 dlls/winebth.sys/winebluetooth.c create mode 100644 dlls/winebth.sys/winebluetooth.h create mode 100644 include/bthdef.h create mode 100644 include/ddk/bthguid.h
diff --git a/dlls/winebth.sys/Makefile.in b/dlls/winebth.sys/Makefile.in index 44a966f3ac1..f34f71d5ca5 100644 --- a/dlls/winebth.sys/Makefile.in +++ b/dlls/winebth.sys/Makefile.in @@ -1,8 +1,14 @@ MODULE = winebth.sys -IMPORTS = ntoskrnl +IMPORTS = ntoskrnl uuid +UNIXLIB = winebth.so +UNIX_CFLAGS = $(DBUS_CFLAGS)
EXTRADLLFLAGS = -Wl,--subsystem,native
SOURCES = \ winebth.c \ + bthenum.c \ + dbus.c \ + unixlib.c \ + winebluetooth.c \ winebth.rc diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c new file mode 100644 index 00000000000..4878d9dc8a4 --- /dev/null +++ b/dlls/winebth.sys/dbus.c @@ -0,0 +1,789 @@ +/* + * Support for communicating with BlueZ over DBus. + * + * Copyright 2023 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 + */ + + +#if 0 +#pragma makedep unix +#endif + +#include "config.h" + +#include <stdlib.h> +#include <dlfcn.h> +#include <assert.h> +#include <pthread.h> + +#ifdef SONAME_LIBDBUS_1 +#include <dbus/dbus.h> +#endif + +#include <ntstatus.h> +#define WIN32_NO_STATUS +#include <windef.h> +#include <winternl.h> +#include <winbase.h> +#include <bthsdpdef.h> +#include <bluetoothapis.h> + +#include <wine/debug.h> + +#include "winebluetooth.h" + +#include "unixlib.h" +#include "unixlib_priv.h" +#include "dbus.h" + +WINE_DEFAULT_DEBUG_CHANNEL( winebluetooth ); +WINE_DECLARE_DEBUG_CHANNEL( dbus ); + +#ifdef SONAME_LIBDBUS_1 + +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 \ + 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 DBUS_INTERFACES_REMOVED_SIGNATURE \ + 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" + +#define DO_FUNC( f ) typeof( f ) (*p_##f) +DBUS_FUNCS; +#undef DO_FUNC + +static BOOL load_dbus_functions( void ) +{ + void *handle = dlopen( SONAME_LIBDBUS_1, RTLD_NOW ); + + if (handle == NULL) goto failed; + +#define DO_FUNC( f ) \ + if (!( p_##f = dlsym( handle, #f ) )) \ + { \ + ERR( "failed to load symbol %s: %s\n", #f, dlerror() ); \ + goto failed; \ + } + DBUS_FUNCS; +#undef DO_FUNC + return TRUE; + +failed: + WARN( "failed to load DBus support: %s\n", dlerror() ); + 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; + const char *name; + + if (p_dbus_message_iter_get_arg_type( iter ) != DBUS_TYPE_DICT_ENTRY) + { + return NULL; + } + p_dbus_message_iter_recurse( iter, &sub ); + p_dbus_message_iter_next( iter ); + p_dbus_message_iter_get_basic( &sub, &name ); + p_dbus_message_iter_next( &sub ); + p_dbus_message_iter_recurse( &sub, variant ); + 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, + p_dbus_connection_get_is_connected( connection ) ); +} + +static NTSTATUS bluez_get_objects_async( DBusConnection *connection, DBusPendingCall **call ) +{ + DBusMessage *request; + dbus_bool_t success; + + TRACE( "Getting managed objects under '/' at service '%s'\n", BLUEZ_DEST ); + request = p_dbus_message_new_method_call( + BLUEZ_DEST, "/", DBUS_INTERFACE_OBJECTMANAGER, "GetManagedObjects" ); + if (!request) + { + return STATUS_NO_MEMORY; + } + + success = p_dbus_connection_send_with_reply( connection, request, call, -1 ); + p_dbus_message_unref( request ); + if (!success) + { + return STATUS_NO_MEMORY; + } + + if (*call == NULL) + { + return STATUS_INVALID_PARAMETER; + } + + return STATUS_SUCCESS; +} + +#define DBUS_OBJECTMANAGER_METHOD_GETMANAGEDOBJECTS_RETURN_SIGNATURE \ + DBUS_TYPE_ARRAY_AS_STRING \ + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING \ + 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 \ + 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_DISCOVERABLE; + } + 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; + } +} + +struct bluez_pair_complete_params +{ + void *user_data; + struct list *event_list; +}; + +struct bluez_watcher_ctx +{ + void *init_device_list_call; + + /* struct bluez_init_entry */ + struct list initial_radio_list; + /* struct bluez_init_entry */ + struct list initial_device_list; + + /* struct bluez_watcher_event */ + struct list event_list; +}; + +void *bluez_dbus_init( void ) +{ + DBusError error; + DBusConnection *connection; + + if (!load_dbus_functions()) return NULL; + + p_dbus_threads_init_default(); + p_dbus_error_init ( &error ); + + connection = p_dbus_bus_get_private ( DBUS_BUS_SYSTEM, &error ); + if (connection == NULL) + { + ERR( "Failed to get system dbus connection: %s: %s\n", error.name, error.message ); + p_dbus_error_free( &error ); + return NULL; + } + TRACE_( dbus )( "new dbus connection: %s\n", dbgstr_dbus_connection( connection ) ); + + return connection; +} + +void bluez_dbus_close( void *connection ) +{ + TRACE_(dbus)("(%s)\n", dbgstr_dbus_connection( connection )); + + p_dbus_connection_flush( connection ); + p_dbus_connection_close( connection ); +} + +void bluez_dbus_free( void *connection ) +{ + TRACE_( dbus )( "(%s)\n", dbgstr_dbus_connection( connection ) ); + + p_dbus_connection_unref( connection ); +} + +struct bluez_watcher_event_pending_call +{ + struct list entry; + DBusPendingCall *call; +}; + +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_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) + { + ERR( "Could not allocate memory for DBus event.\n" ); + return FALSE; + } + + 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 DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, void *user_data ) +{ + struct list *event_list; + SIZE_T init_count, final_count; + + 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; + init_count = list_count( 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 ) == 0) + { + 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_added.radio.handle = radio = unix_name_get_or_create( object_path ); + if (radio_added.radio.handle == NULL) + { + ERR("failed to allocate memory for adapter path %s\n", object_path); + break; + } + else + { + union winebluetooth_watcher_event_data event = { .radio_added = radio_added }; + TRACE( "New BlueZ %s object added at %s: %p\n", BLUEZ_INTERFACE_ADAPTER, + object_path, radio_added.radio.handle ); + 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 ); + } + } + 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_( dbus ) + ( "error getting arguments from message: '%s: '%s'\n", error.name, 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 ) == 0) + { + winebluetooth_radio_t radio; + union winebluetooth_watcher_event_data event; + radio.handle = unix_name_get_or_create( object_path ); + + if (!radio.handle) + { + ERR( "failed to allocate memory for adapter path %s\n", object_path ); + continue; + } + event.radio_removed = radio; + if (!bluez_event_list_queue_new_event( + event_list, BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED, event )) + { + unix_name_free(radio.handle); + } + } + } + p_dbus_free_string_array( interfaces ); + } + + final_count = list_count( event_list ); + assert( final_count >= init_count ); + if (final_count > init_count) + TRACE( "Received %ld new watcher-related event(s) from BlueZ\n", (final_count - init_count) ); + + 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 ); + if (status != STATUS_SUCCESS) + { + free( watcher_ctx ); + ERR( "could not create async GetManagedObjects call: %#x\n", (int)status); + return status; + } + watcher_ctx->init_device_list_call = call; + list_init( &watcher_ctx->initial_radio_list ); + list_init( &watcher_ctx->initial_device_list ); + + list_init( &watcher_ctx->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", 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", BLUEZ_MATCH_RULES[i], err.name, + 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 { + struct winebluetooth_watcher_event_radio_added radio; + } object; + struct list entry; +}; + +static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct list *adapter_list, + struct list *device_list ) +{ + DBusMessageIter dict, paths_iter, iface_iter, prop_iter; + const char *path; + NTSTATUS status = STATUS_SUCCESS; + + p_dbus_message_iter_init( reply, &dict ); + p_dbus_message_iter_recurse( &dict, &paths_iter ); + while((path = bluez_next_dict_entry( &paths_iter, &iface_iter ))) + { + const char *iface; + while ((iface = bluez_next_dict_entry ( &iface_iter, &prop_iter ))) + { + 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; + + if (!init_device) + { + status = STATUS_NO_MEMORY; + goto done; + } + init_device->object.radio.radio.handle = radio_name = + unix_name_get_or_create( path ); + if (!radio_name) + { + free( init_device ); + 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 ); + } + list_add_tail( adapter_list, &init_device->entry ); + TRACE( "Found BlueZ %s object '%s': %p\n", BLUEZ_INTERFACE_ADAPTER, radio_name->str, + init_device->object.radio.radio.handle ); + break; + } + } + } + + TRACE( "Initial device list: radios: %d, devices: %d\n", + list_count( adapter_list ), list_count( device_list ) ); + done: + return status; +} + +static struct bluez_init_entry *bluez_init_entries_list_pop( struct list *list ) +{ + struct list *entry = list_head( list ); + struct bluez_init_entry *device = LIST_ENTRY( entry, struct bluez_init_entry, entry ); + + list_remove( entry ); + return device; +} + +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); + + 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; +} + +NTSTATUS bluez_dbus_loop( void *c, void *watcher, + struct winebluetooth_event_loop_result *result ) +{ + DBusConnection *connection; + struct bluez_watcher_ctx *watcher_ctx = watcher; + + TRACE( "(%p, %p, %p)\n", c, watcher, result ); + connection = p_dbus_connection_ref( c ); + + while(TRUE) + { + struct bluez_watcher_event *bluez_event; + + if (!list_empty( &watcher_ctx->initial_radio_list )) + { + 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_LOOP_STATUS_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; + } + else if ((bluez_event = bluez_watcher_event_queue_ready( watcher_ctx ))) + { + struct winebluetooth_watcher_event *watcher_event = &result->data.watcher_event; + + assert( bluez_event->pending_call == NULL ); + result->status = WINEBLUETOOTH_EVENT_LOOP_STATUS_WATCHER_EVENT; + watcher_event->event_type = bluez_event->event_type; + watcher_event->event_data = bluez_event->event; + free( bluez_event ); + p_dbus_connection_unref( connection ); + return STATUS_PENDING; + } + else if (!p_dbus_connection_read_write_dispatch( connection, -1 )) + { + p_dbus_connection_unref( connection ); + TRACE( "Disconnected from DBus\n" ); + return STATUS_SUCCESS; + } + + if (watcher_ctx->init_device_list_call != NULL + && p_dbus_pending_call_get_completed( watcher_ctx->init_device_list_call )) + { + DBusMessage *reply = p_dbus_pending_call_steal_reply( watcher_ctx->init_device_list_call ); + DBusError error; + NTSTATUS status; + + TRACE_( dbus ) + ( "Received GetManagedObjects reply: %s\n", dbgstr_dbus_message( reply ) ); + p_dbus_pending_call_unref( watcher_ctx->init_device_list_call ); + watcher_ctx->init_device_list_call = NULL; + + p_dbus_error_init( &error ); + if (p_dbus_set_error_from_message( &error, reply )) + { + ERR( "error getting object list from BlueZ: '%s': '%s'\n", error.name, + error.message ); + p_dbus_error_free( &error ); + p_dbus_message_unref( reply ); + p_dbus_connection_unref( connection ); + return STATUS_NO_MEMORY; + } + status = bluez_build_initial_device_lists( reply, &watcher_ctx->initial_radio_list, + &watcher_ctx->initial_device_list ); + p_dbus_message_unref( reply ); + if (status != STATUS_SUCCESS) + { + ERR( "error building initial bluetooth devices list: %#x\n", (int)status ); + p_dbus_connection_unref( connection ); + return status; + } + } + } +} +#else + +#endif /* SONAME_LIBDBUS_1 */ diff --git a/dlls/winebth.sys/dbus.h b/dlls/winebth.sys/dbus.h new file mode 100644 index 00000000000..5859b6a4aef --- /dev/null +++ b/dlls/winebth.sys/dbus.h @@ -0,0 +1,113 @@ +/* + * DBus declarations. + * + * Copyright 2023 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_BLUETOOTHAPIS_UNIXLIB_DBUS_H +#define __WINE_BLUETOOTHAPIS_UNIXLIB_DBUS_H + +#include "config.h" + +#ifdef SONAME_LIBDBUS_1 +#include <dbus/dbus.h> +#endif + +#ifdef SONAME_LIBDBUS_1 + +#define DBUS_FUNCS \ + DO_FUNC(dbus_bus_add_match); \ + DO_FUNC(dbus_bus_get); \ + DO_FUNC(dbus_bus_get_id); \ + DO_FUNC(dbus_bus_get_private); \ + DO_FUNC(dbus_bus_remove_match); \ + DO_FUNC(dbus_connection_add_filter); \ + DO_FUNC(dbus_connection_close); \ + DO_FUNC(dbus_connection_flush); \ + DO_FUNC(dbus_connection_free_preallocated_send); \ + DO_FUNC(dbus_connection_get_is_anonymous); \ + DO_FUNC(dbus_connection_get_is_authenticated); \ + DO_FUNC(dbus_connection_get_is_connected); \ + DO_FUNC(dbus_connection_get_server_id); \ + DO_FUNC(dbus_connection_get_unix_process_id); \ + DO_FUNC(dbus_connection_get_unix_fd); \ + DO_FUNC(dbus_connection_get_unix_user); \ + DO_FUNC(dbus_connection_preallocate_send); \ + DO_FUNC(dbus_connection_read_write_dispatch); \ + DO_FUNC(dbus_connection_remove_filter); \ + DO_FUNC(dbus_connection_ref); \ + DO_FUNC(dbus_connection_send); \ + DO_FUNC(dbus_connection_send_preallocated); \ + DO_FUNC(dbus_connection_send_with_reply); \ + DO_FUNC(dbus_connection_send_with_reply_and_block); \ + DO_FUNC(dbus_connection_try_register_object_path); \ + DO_FUNC(dbus_connection_unref); \ + DO_FUNC(dbus_connection_unregister_object_path); \ + DO_FUNC(dbus_error_free); \ + DO_FUNC(dbus_error_has_name) ; \ + DO_FUNC(dbus_error_init); \ + DO_FUNC(dbus_error_is_set); \ + DO_FUNC(dbus_free); \ + DO_FUNC(dbus_free_string_array); \ + DO_FUNC(dbus_message_append_args); \ + DO_FUNC(dbus_message_get_args); \ + DO_FUNC(dbus_message_iter_get_element_count); \ + DO_FUNC(dbus_message_get_interface); \ + DO_FUNC(dbus_message_get_member); \ + DO_FUNC(dbus_message_get_path); \ + DO_FUNC(dbus_message_get_sender); \ + DO_FUNC(dbus_message_get_serial); \ + DO_FUNC(dbus_message_get_signature); \ + DO_FUNC(dbus_message_get_type); \ + DO_FUNC(dbus_message_has_signature); \ + DO_FUNC(dbus_message_iter_has_next); \ + DO_FUNC(dbus_message_is_error); \ + 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_close_container); \ + DO_FUNC(dbus_message_iter_get_arg_type); \ + DO_FUNC(dbus_message_iter_get_element_type); \ + DO_FUNC(dbus_message_iter_get_basic); \ + DO_FUNC(dbus_message_iter_get_fixed_array); \ + DO_FUNC(dbus_message_iter_get_signature); \ + DO_FUNC(dbus_message_iter_init); \ + DO_FUNC(dbus_message_iter_init_append); \ + DO_FUNC(dbus_message_iter_next); \ + DO_FUNC(dbus_message_iter_open_container); \ + DO_FUNC(dbus_message_iter_recurse); \ + DO_FUNC(dbus_message_new_error); \ + DO_FUNC(dbus_message_new_error_printf); \ + DO_FUNC(dbus_message_new_method_return); \ + DO_FUNC(dbus_message_new_method_call); \ + DO_FUNC(dbus_message_ref); \ + DO_FUNC(dbus_message_unref); \ + DO_FUNC(dbus_pending_call_block); \ + DO_FUNC(dbus_pending_call_cancel); \ + DO_FUNC(dbus_pending_call_get_completed); \ + DO_FUNC(dbus_pending_call_set_notify); \ + DO_FUNC(dbus_pending_call_steal_reply); \ + DO_FUNC(dbus_pending_call_unref); \ + DO_FUNC(dbus_set_error_from_message); \ + DO_FUNC(dbus_threads_init_default); + +#define DO_FUNC( f ) extern typeof( f ) *p_##f +DBUS_FUNCS; +#undef DO_FUNC + +#endif /* SONAME_LIBDBUS_1 */ +#endif /* __WINE_BLUETOOTHAPIS_UNIXLIB_DBUS_H */ diff --git a/dlls/winebth.sys/unixlib.c b/dlls/winebth.sys/unixlib.c new file mode 100644 index 00000000000..56c51f5d76f --- /dev/null +++ b/dlls/winebth.sys/unixlib.c @@ -0,0 +1,188 @@ +/* + * winebluetooth Unix interface + * + * 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 + */ + +#if 0 +#pragma makedep unix +#endif + +#include <config.h> + +#include <stdlib.h> +#include <stdarg.h> +#include <assert.h> +#include <ctype.h> +#include <pthread.h> + +#include <ntstatus.h> +#define WIN32_NO_STATUS +#include <winternl.h> +#include <winbase.h> +#include <windef.h> +#include <wine/debug.h> +#include <wine/list.h> +#include <wine/rbtree.h> + +#include "unixlib.h" +#include "unixlib_priv.h" + +WINE_DEFAULT_DEBUG_CHANNEL( winebluetooth ); + +static int compare_string( const void *key, const struct wine_rb_entry *entry ) +{ + struct unix_name *str = WINE_RB_ENTRY_VALUE( entry, struct unix_name, entry ); + return strcmp( key, str->str ); +} + +static struct rb_tree names = { .compare = compare_string }; +static pthread_mutex_t names_mutex = PTHREAD_MUTEX_INITIALIZER; + +struct unix_name *unix_name_get_or_create( const char *str ) +{ + struct rb_entry *entry; + struct unix_name *s; + + pthread_mutex_lock( &names_mutex ); + entry = rb_get( &names, str ); + if (entry == NULL) + { + struct unix_name *s = malloc( sizeof( struct unix_name ) ); + if (s == NULL) + { + pthread_mutex_unlock(&names_mutex); + return NULL; + } + s->str = strdup( str ); + s->refcnt = 0; + rb_put( &names, str, &s->entry ); + entry = &s->entry; + } + s = RB_ENTRY_VALUE( entry, struct unix_name, entry ); + s->refcnt++; + pthread_mutex_unlock( &names_mutex ); + return s; +} + +void unix_name_free( struct unix_name *name ) +{ + pthread_mutex_lock( &names_mutex ); + name->refcnt--; + if (name->refcnt == 0) + { + rb_remove( &names, &name->entry ); + free( name ); + } + pthread_mutex_unlock( &names_mutex ); +} + +static void *dbus_connection; + +static NTSTATUS bluetooth_init ( void *params ) +{ + dbus_connection = bluez_dbus_init(); + TRACE("dbus_connection=%p\n", dbus_connection); + + return dbus_connection ? STATUS_SUCCESS : STATUS_INTERNAL_ERROR; +} + +static NTSTATUS bluetooth_shutdown( void *params ) +{ + if (!dbus_connection) return STATUS_NOT_SUPPORTED; + + bluez_dbus_close( dbus_connection ); + bluez_dbus_free( dbus_connection ); + return STATUS_SUCCESS; +} + +static NTSTATUS get_unique_name( const struct unix_name *name, char *buf, SIZE_T *buf_size ) +{ + SIZE_T path_len, i; + + path_len = strlen( name->str ); + if (*buf_size <= (path_len * sizeof(char))) + { + *buf_size = ( path_len + 1 ) * sizeof(char); + return STATUS_BUFFER_TOO_SMALL; + } + + for (i = 0; i < path_len; i++) + { + if (name->str[i] == '/') buf[i] = '_'; + else + buf[i] = isalpha( name->str[i] ) ? toupper( name->str[i] ) : name->str[i]; + } + buf[path_len] = '\0'; + return STATUS_SUCCESS; +} + +static NTSTATUS bluetooth_adapter_get_unique_name( void *args ) +{ + struct bluetooth_adapter_get_unique_name_params *params = args; + if (!dbus_connection) return STATUS_NOT_SUPPORTED; + + return get_unique_name( params->adapter, params->buf, ¶ms->buf_size ); +} + +static NTSTATUS bluetooth_adapter_free( void *args ) +{ + struct bluetooth_adapter_free_params *params = args; + unix_name_free( params->adapter ); + return STATUS_SUCCESS; +} + +static NTSTATUS bluetooth_watcher_init( void *args ) +{ + struct bluetooth_watcher_init_params *params = args; + + if (!dbus_connection) return STATUS_NOT_SUPPORTED; + return bluez_watcher_init( dbus_connection, ¶ms->unix_watcher_ctx ); +} + +static NTSTATUS bluetooth_watcher_close( void *args ) +{ + struct bluetooth_watcher_close_params *params = args; + + if ( !dbus_connection ) return STATUS_NOT_SUPPORTED; + bluez_watcher_close(dbus_connection, params->unix_watcher_ctx); + return STATUS_SUCCESS; +} + +static NTSTATUS bluetooth_event_loop_once( void *args ) +{ + struct bluetooth_event_loop_once_params *params = args; + + if (!dbus_connection) return STATUS_NOT_SUPPORTED; + memset( ¶ms->result, 0, sizeof( params->result ) ); + return bluez_dbus_loop( dbus_connection, params->unix_watcher_ctx, ¶ms->result ); +} + +const unixlib_entry_t __wine_unix_call_funcs[] = { + bluetooth_init, + bluetooth_shutdown, + + bluetooth_adapter_get_unique_name, + bluetooth_adapter_free, + + bluetooth_watcher_init, + bluetooth_watcher_close, + + bluetooth_event_loop_once, +}; + +C_ASSERT( ARRAYSIZE( __wine_unix_call_funcs ) == unix_funcs_count ); diff --git a/dlls/winebth.sys/unixlib.h b/dlls/winebth.sys/unixlib.h new file mode 100644 index 00000000000..177faf4e855 --- /dev/null +++ b/dlls/winebth.sys/unixlib.h @@ -0,0 +1,116 @@ +/* + * Unix interface definitions + * + * Copyright 2023 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_WINEBLUETOOTH_UNIXLIB_H +#define __WINE_WINEBLUETOOTH_UNIXLIB_H + +#include <stdlib.h> +#include <stdarg.h> + +#include <windef.h> +#include <winbase.h> + +#include <bthsdpdef.h> +#include <bluetoothapis.h> + +#include <wine/unixlib.h> + +#include "winebluetooth.h" + +#define BLUETOOTH_ADAPTER_PROP_ROLES ( 1 ) +#define BLUETOOTH_ADAPTER_PROP_DISCOVERABLE ( 1 << 2 ) +#define BLUETOOTH_ADAPTER_PROP_PAIRABLE ( 1 << 3 ) +#define BLUETOOTH_ADAPTER_PROP_POWERED ( 1 << 4 ) +#define BLUETOOTH_ADAPTER_PROP_ADDRESS ( 1 << 5 ) +#define BLUETOOTH_ADAPTER_PROP_NAME ( 1 << 6 ) +#define BLUETOOTH_ADAPTER_PROP_MANUFACTURER ( 1 << 7 ) +#define BLUETOOTH_ADAPTER_PROP_CLASS ( 1 << 8 ) +#define BLUETOOTH_ADAPTER_PROP_DISCOVERING ( 1 << 9 ) +#define BLUETOOTH_ADAPTER_PROP_VERSION ( 1 << 10 ) + +#define BLUETOOTH_ADAPTER_ROLE_CENTRAL ( 1 ) +#define BLUETOOTH_ADAPTER_ROLE_PERIPHERAL ( 1 << 2 ) +#define BLUETOOTH_ADAPTER_ROLE_CENTRAL_PERIPHERAL ( 1 << 3 ) + +#ifdef WINE_UNIX_LIB +typedef struct unix_name *unix_name_t; +typedef void *unix_handle_t; +#else +typedef UINT_PTR unix_name_t; +typedef UINT_PTR unix_handle_t; +#endif + +struct bluetooth_adapter_free_params +{ + unix_name_t adapter; +}; + +struct bluetooth_adapter_get_unique_name_params +{ + unix_name_t adapter; + + char *buf; + SIZE_T buf_size; +}; + +struct bluetooth_device_get_unique_name_params +{ + unix_name_t device; + + char *buf; + SIZE_T buf_size; +}; + +struct bluetooth_watcher_init_params +{ + unix_handle_t unix_watcher_ctx; +}; + +struct bluetooth_watcher_close_params +{ + unix_handle_t unix_watcher_ctx; +}; + +struct bluetooth_event_loop_once_params +{ + unix_handle_t unix_watcher_ctx; + + struct winebluetooth_event_loop_result result; +}; + +enum bluetoothapis_funcs +{ + unix_bluetooth_init, + unix_bluetooth_shutdown, + + unix_bluetooth_adapter_get_unique_name, + unix_bluetooth_adapter_free, + + unix_bluetooth_watcher_init, + unix_bluetooth_watcher_close, + + unix_bluetooth_event_loop_once, + + unix_funcs_count +}; + +#define UNIX_BLUETOOTH_CALL( func, params ) WINE_UNIX_CALL( unix_##func, params ) + +#endif /* __WINE_WINEBLUETOOTH_UNIXLIB_H */ diff --git a/dlls/winebth.sys/unixlib_priv.h b/dlls/winebth.sys/unixlib_priv.h new file mode 100644 index 00000000000..0690e86f99b --- /dev/null +++ b/dlls/winebth.sys/unixlib_priv.h @@ -0,0 +1,55 @@ +/* + * Bluetoothapis Unix interface + * + * Copyright 2023 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_BLUETOOTHAPIS_UNIXLIB_PRIV_H +#define __WINE_BLUETOOTHAPIS_UNIXLIB_PRIV_H + +#include <config.h> + +#include <ntstatus.h> +#define WIN32_NO_STATUS +#include <windef.h> + +#include <wine/list.h> +#include <wine/rbtree.h> + +#include "unixlib.h" + +#ifdef SONAME_LIBDBUS_1 + +struct unix_name +{ + char *str; + SIZE_T refcnt; + + struct wine_rb_entry entry; +}; + +extern struct unix_name *unix_name_get_or_create( const char *str ); +extern void unix_name_free( struct unix_name *name ); + +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_loop_result *result ); +extern NTSTATUS bluez_watcher_init( void *connection, void **ctx ); +extern void bluez_watcher_close( void *connection, void *ctx ); +#endif /* SONAME_LIBDBUS_1 */ +#endif /* __WINE_BLUETOOTHAPIS_UNIXLIB_PRIV_H */ diff --git a/dlls/winebth.sys/winebluetooth.c b/dlls/winebth.sys/winebluetooth.c new file mode 100644 index 00000000000..509595b5f84 --- /dev/null +++ b/dlls/winebth.sys/winebluetooth.c @@ -0,0 +1,114 @@ +/* + * Wine bluetooth APIs + * + * 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 + * + */ + +#include <stdarg.h> + +#include <ntstatus.h> +#define WIN32_NO_STATUS + +#include <windef.h> +#include <winbase.h> + +#include <wine/debug.h> +#include <wine/heap.h> +#include <wine/unixlib.h> + +#include "winebluetooth.h" +#include "unixlib.h" + +WINE_DEFAULT_DEBUG_CHANNEL( winebluetooth ); + +NTSTATUS winebluetooth_radio_get_unique_name( winebluetooth_radio_t radio, char *name, + SIZE_T *size ) +{ + struct bluetooth_adapter_get_unique_name_params params = {0}; + NTSTATUS status; + + TRACE( "(%p, %p, %p)\n", (void *)radio.handle, name, size ); + + params.adapter = radio.handle; + params.buf = name; + params.buf_size = *size; + status = UNIX_BLUETOOTH_CALL( bluetooth_adapter_get_unique_name, ¶ms ); + if (status == STATUS_BUFFER_TOO_SMALL) + { + *size = params.buf_size; + } + return status; +} + +void winebluetooth_radio_free( winebluetooth_radio_t radio ) +{ + struct bluetooth_adapter_free_params args = { 0 }; + TRACE( "(%p)\n", (void *)radio.handle ); + + args.adapter = radio.handle; + UNIX_BLUETOOTH_CALL( bluetooth_adapter_free, &args ); +} + +NTSTATUS winebluetooth_watcher_init( winebluetooth_watcher_t *watcher ) +{ + struct bluetooth_watcher_init_params args = { 0 }; + NTSTATUS status; + + TRACE( "(%p)\n", watcher ); + status = UNIX_BLUETOOTH_CALL( bluetooth_watcher_init, &args ); + watcher->handle = args.unix_watcher_ctx; + return status; +} + +NTSTATUS winebluetooth_watcher_close( winebluetooth_watcher_t watcher ) +{ + struct bluetooth_watcher_close_params args = { .unix_watcher_ctx = watcher.handle }; + + TRACE( "(%p)\n", (void *)watcher.handle ); + return UNIX_BLUETOOTH_CALL( bluetooth_watcher_close, &args ); +} + +NTSTATUS winebluetooth_event_loop_run( winebluetooth_watcher_t watcher, + struct winebluetooth_event_loop_result *result ) +{ + struct bluetooth_event_loop_once_params params = {0}; + NTSTATUS status; + + TRACE( "(%p, %p, %p)\n", (void *)watcher.handle, result ); + + params.unix_watcher_ctx = watcher.handle; + status = UNIX_BLUETOOTH_CALL( bluetooth_event_loop_once, ¶ms ); + *result = params.result; + return status; +} + +NTSTATUS winebluetooth_init( void ) +{ + NTSTATUS status; + + status = __wine_init_unix_call(); + if (status != STATUS_SUCCESS) + return status; + + return UNIX_BLUETOOTH_CALL( bluetooth_init, NULL ); +} + +NTSTATUS winebluetooth_shutdown( void ) +{ + return UNIX_BLUETOOTH_CALL( bluetooth_shutdown, NULL ); +} diff --git a/dlls/winebth.sys/winebluetooth.h b/dlls/winebth.sys/winebluetooth.h new file mode 100644 index 00000000000..0cf90620ba8 --- /dev/null +++ b/dlls/winebth.sys/winebluetooth.h @@ -0,0 +1,147 @@ +/* + * Wine bluetooth APIs + * + * 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_WINEBLUETOOTH_H__ +#define __WINE_WINEBLUETOOTH_H__ + +#include <bthsdpdef.h> +#include <bluetoothapis.h> + +typedef struct +{ +#ifdef WINE_UNIX_LIB + void *handle; +#else + UINT_PTR handle; +#endif +} winebluetooth_radio_t; + +typedef UINT16 winebluetooth_radio_props_mask_t; + +#define WINEBLUTOOTH_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 \ + (WINEBLUTOOTH_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) + +union winebluetooth_property +{ + BOOL bool; + ULONG ulong; + BLUETOOTH_ADDRESS address; + WCHAR name[BLUETOOTH_MAX_NAME_SIZE]; +}; + +struct winebluetooth_bluetooth_version +{ + UCHAR major; + UCHAR minor; +}; + +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; + struct winebluetooth_bluetooth_version version; +}; + +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; +} + +typedef struct +{ +#ifdef WINE_UNIX_LIB + void *handle; +#else + UINT_PTR handle; +#endif +} winebluetooth_watcher_t; + +enum winebluetooth_watcher_event_type +{ + BLUETOOTH_WATCHER_EVENT_TYPE_NONE, + BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, + BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED, +}; + +struct winebluetooth_watcher_event_radio_added +{ + winebluetooth_radio_props_mask_t props_mask; + struct winebluetooth_radio_properties props; + 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 +{ + enum winebluetooth_watcher_event_type event_type; + union winebluetooth_watcher_event_data event_data; +}; + +NTSTATUS winebluetooth_watcher_init( winebluetooth_watcher_t *watcher ); +NTSTATUS winebluetooth_watcher_close( winebluetooth_watcher_t watcher ); + +enum winebluetooth_event_loop_status +{ + WINEBLUETOOTH_EVENT_LOOP_STATUS_WATCHER_EVENT, +}; + +struct winebluetooth_event_loop_result +{ + enum winebluetooth_event_loop_status status; + union { + struct winebluetooth_watcher_event watcher_event; + } data; +}; + +NTSTATUS winebluetooth_event_loop_run( winebluetooth_watcher_t watcher, + struct winebluetooth_event_loop_result *result ); +NTSTATUS winebluetooth_init( void ); +NTSTATUS winebluetooth_shutdown( void ); + +#endif /* __WINE_WINEBLUETOOTH_H__ */ diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 699c9915e27..48d7da23308 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -27,13 +27,18 @@ #include <windef.h> #include <winbase.h> #include <winternl.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>
#include "winebth_priv.h" +#include "winebluetooth.h"
WINE_DEFAULT_DEBUG_CHANNEL( winebth );
@@ -41,6 +46,233 @@ static DRIVER_OBJECT *driver_obj;
static DEVICE_OBJECT *bus_fdo, *bus_pdo;
+#define DECLARE_CRITICAL_SECTION( cs ) \ + static CRITICAL_SECTION cs; \ + static CRITICAL_SECTION_DEBUG cs##_debug = { \ + 0, \ + 0, \ + &( cs ), \ + { &cs##_debug.ProcessLocksList, &cs##_debug.ProcessLocksList }, \ + 0, \ + 0, \ + { (DWORD_PTR)( __FILE__ ": " #cs ) } }; \ + static CRITICAL_SECTION cs = { &cs##_debug, -1, 0, 0, 0, 0 }; + +DECLARE_CRITICAL_SECTION( device_list_cs ); + +static struct list device_list = LIST_INIT( device_list ); + +static winebluetooth_watcher_t watcher; +struct bluetooth_radio +{ + struct list entry; + BOOL removed; + + DEVICE_OBJECT *device_obj; + DEVICE_OBJECT *device_fdo_bus; + winebluetooth_radio_props_mask_t props_mask; + struct winebluetooth_radio_properties props; + winebluetooth_radio_t radio; + WCHAR *hw_name; + UNICODE_STRING bthport_symlink_name; + UNICODE_STRING bthradio_symlink_name; + LIST_ENTRY irp_list; +}; + +void WINAPIV append_id(struct string_buffer *buffer, const WCHAR *format, ...) +{ + va_list args; + WCHAR *string; + int len; + + va_start(args, format); + + len = _vsnwprintf(NULL, 0, format, args) + 1; + if (!(string = ExAllocatePool(PagedPool, (buffer->len + len) * sizeof(WCHAR)))) + { + if (buffer->string) + ExFreePool(buffer->string); + buffer->string = NULL; + return; + } + if (buffer->string) + { + memcpy(string, buffer->string, buffer->len * sizeof(WCHAR)); + ExFreePool(buffer->string); + } + _vsnwprintf(string + buffer->len, len, format, args); + buffer->string = string; + buffer->len += len; + + va_end(args); +} + + +static HANDLE event_loop_thread; +static NTSTATUS radio_get_hw_name_w( winebluetooth_radio_t radio, WCHAR **name ) +{ + char *name_a; + SIZE_T size = sizeof(char) * 256; + NTSTATUS status; + + name_a = malloc( size ); + if (name_a == NULL) + { + return STATUS_NO_MEMORY; + } + + status = winebluetooth_radio_get_unique_name( radio, name_a, &size ); + if (status == STATUS_BUFFER_TOO_SMALL) + { + void *ptr = realloc( name_a, size ); + if (ptr == NULL) + { + free( name_a ); + return STATUS_NO_MEMORY; + } + name_a = ptr; + status = winebluetooth_radio_get_unique_name( radio, name_a, &size ); + } + if (status != STATUS_SUCCESS) + { + free( name_a ); + return status; + } + + *name = malloc( (mbstowcs( NULL, name_a, 0 ) + 1) * sizeof(WCHAR)); + if (*name == NULL) + { + free( name_a ); + return status; + } + + mbstowcs( *name, name_a, strlen( name_a ) + 1 ); + free( name_a ); + return STATUS_SUCCESS; +} +static void add_bluetooth_radio( struct winebluetooth_watcher_event_radio_added event ) +{ + struct bluetooth_radio *device; + DEVICE_OBJECT *device_obj; + UNICODE_STRING string; + NTSTATUS status; + WCHAR name[256]; + WCHAR *hw_name; + static unsigned int radio_index; + + swprintf( name, ARRAY_SIZE( name ), L"\Device\WINEBTH-RADIO-%d", radio_index++ ); + TRACE( "Adding new bluetooth radio %p: %s\n", (void *)event.radio.handle, debugstr_w( name ) ); + + status = radio_get_hw_name_w(event.radio, &hw_name); + if (status != STATUS_SUCCESS) + { + ERR( "Failed to get hardware name for radio %p, status %#lx\n", (void *)event.radio.handle, status ); + return; + } + + RtlInitUnicodeString( &string, name ); + status = IoCreateDevice( driver_obj, sizeof( *device ), &string, FILE_DEVICE_BLUETOOTH, 0, + FALSE, &device_obj ); + if ( status != STATUS_SUCCESS ) + { + ERR( "Failed to create device, status %#lx\n", status ); + return; + } + + device = device_obj->DeviceExtension; + device->device_obj = device_obj; + device->radio = event.radio; + device->removed = FALSE; + device->hw_name = hw_name; + device->device_fdo_bus = NULL; + device->props = event.props; + device->props_mask = event.props_mask; + InitializeListHead(&device->irp_list); + + EnterCriticalSection( &device_list_cs ); + list_add_tail( &device_list, &device->entry ); + LeaveCriticalSection( &device_list_cs ); + + 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 void remove_pending_irps(struct bluetooth_radio *device) +{ + LIST_ENTRY *entry; + IRP *irp; + + while ((entry = RemoveHeadList( &device->irp_list )) != &device->irp_list) + { + irp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry); + irp->IoStatus.Status = STATUS_DELETE_PENDING; + irp->IoStatus.Information = 0; + IoCompleteRequest(irp, IO_NO_INCREMENT); + } +} + +static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) +{ + NTSTATUS status; + while (TRUE) + { + struct winebluetooth_event_loop_result result = {0}; + + status = winebluetooth_event_loop_run( watcher, &result ); + if (status != STATUS_PENDING) break; + + switch (result.status) + { + case WINEBLUETOOTH_EVENT_LOOP_STATUS_WATCHER_EVENT: + { + struct winebluetooth_watcher_event *event = &result.data.watcher_event; + switch (event->event_type) + { + 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 ); + } + break; + } + default: + FIXME( "Unknown bluetooth event loop status code: %#x\n", result.status ); + } + } + + if (status != STATUS_SUCCESS) + ERR( "Bluetooth event loop terminated with %#lx", status ); + else + TRACE( "Exiting bluetooth event loop\n" ); + winebluetooth_watcher_close( watcher ); + return 0; +} + static NTSTATUS WINAPI fdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); @@ -50,8 +282,41 @@ static NTSTATUS WINAPI fdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) switch (stack->MinorFunction) { case IRP_MN_QUERY_DEVICE_RELATIONS: + { + struct bluetooth_radio *radio; + DEVICE_RELATIONS *devices; + SIZE_T i = 0; + + if (stack->Parameters.QueryDeviceRelations.Type != BusRelations) + { + break; + } + + EnterCriticalSection( &device_list_cs ); + devices = ExAllocatePool( + PagedPool, offsetof( DEVICE_RELATIONS, Objects[list_count( &device_list )] ) ); + if (devices == NULL) + { + LeaveCriticalSection( &device_list_cs ); + irp->IoStatus.Status = STATUS_NO_MEMORY; + break; + } + + LIST_FOR_EACH_ENTRY(radio, &device_list, struct bluetooth_radio, entry) + { + devices->Objects[i++] = radio->device_obj; + call_fastcall_func1( ObfReferenceObject, radio->device_obj ); + } + LeaveCriticalSection( &device_list_cs ); + + devices->Count = i; + irp->IoStatus.Information = (ULONG_PTR)devices; + irp->IoStatus.Status = STATUS_SUCCESS; break; + } case IRP_MN_START_DEVICE: + event_loop_thread = + CreateThread( NULL, 0, bluetooth_event_loop_thread_proc, NULL, 0, NULL ); irp->IoStatus.Status = STATUS_SUCCESS; break; case IRP_MN_SURPRISE_REMOVAL: @@ -59,7 +324,20 @@ static NTSTATUS WINAPI fdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) break; case IRP_MN_REMOVE_DEVICE: { + struct bluetooth_radio *device, *cur; NTSTATUS ret; + winebluetooth_shutdown(); + WaitForSingleObject( event_loop_thread, INFINITE ); + CloseHandle( event_loop_thread ); + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY_SAFE( device, cur, &device_list, struct bluetooth_radio, entry ) + { + assert( !device->removed ); + winebluetooth_radio_free( device->radio ); + list_remove( &device->entry ); + IoDeleteDevice( device->device_obj ); + } + LeaveCriticalSection( &device_list_cs ); IoSkipCurrentIrpStackLocation( irp ); ret = IoCallDriver( bus_pdo, irp ); IoDetachDevice( bus_pdo ); @@ -74,17 +352,74 @@ static NTSTATUS WINAPI fdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) return IoCallDriver( bus_pdo, irp ); }
+static NTSTATUS query_id(const struct bluetooth_radio *ext, IRP *irp, BUS_QUERY_ID_TYPE type ) +{ + struct string_buffer buf = {0}; + + TRACE( "(%p, %p, %s)\n", ext, irp, debugstr_BUS_QUERY_ID_TYPE( type ) ); + switch (type) + { + case BusQueryDeviceID: + append_id( &buf, L"WINEBTH\%s", ext->hw_name ); + break; + case BusQueryInstanceID: + append_id(&buf, L"%p", ext->radio); + break; + case BusQueryHardwareIDs: + append_id( &buf, L"WINEBTH\%s", ext->hw_name ); + case BusQueryCompatibleIDs: + append_id( &buf, L"WINEBTH\WINE_COMP_BTH" ); + append_id( &buf, L"" ); + break; + default: + return irp->IoStatus.Status; + } + + if (!buf.string) + return STATUS_NO_MEMORY; + + irp->IoStatus.Information = (ULONG_PTR)buf.string; + return STATUS_SUCCESS; +} + static NTSTATUS WINAPI pdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) { IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); + struct bluetooth_radio *device = device_obj->DeviceExtension; NTSTATUS ret = irp->IoStatus.Status;
TRACE( "device_obj %p, irp %p, minor function %s\n", device_obj, irp, debugstr_minor_function_code( stack->MinorFunction ) ); switch (stack->MinorFunction) { case IRP_MN_QUERY_DEVICE_RELATIONS: + { + DEVICE_RELATIONS *devices; + if (stack->Parameters.QueryDeviceRelations.Type != BusRelations) + { + break; + } + + devices = ExAllocatePool( PagedPool, offsetof( DEVICE_RELATIONS, Objects[1] ) ); + if (devices == NULL) + { + irp->IoStatus.Status = STATUS_NO_MEMORY; + break; + } + + devices->Count = 0; + if (!device->removed) + { + devices->Objects[0] = device->device_fdo_bus; + call_fastcall_func1( ObfReferenceObject, device->device_fdo_bus ); + devices->Count++; + } + + irp->IoStatus.Information = (ULONG_PTR)devices; + ret = STATUS_SUCCESS; break; + } case IRP_MN_QUERY_ID: + ret = query_id( device, irp, stack->Parameters.QueryId.IdType ); break; case IRP_MN_QUERY_CAPABILITIES: { @@ -96,17 +431,76 @@ static NTSTATUS WINAPI pdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) break; } 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 ); + } + + if (IoRegisterDeviceInterface( device_obj, &GUID_BTHPORT_DEVICE_INTERFACE, NULL, + &device->bthport_symlink_name ) == STATUS_SUCCESS) + IoSetDeviceInterfaceState( &device->bthport_symlink_name, TRUE ); + + if (IoRegisterDeviceInterface( device_obj, &GUID_BLUETOOTH_RADIO_INTERFACE, NULL, + &device->bthradio_symlink_name ) == STATUS_SUCCESS) + IoSetDeviceInterfaceState( &device->bthradio_symlink_name, TRUE ); + + ret = STATUS_SUCCESS; break; + } case IRP_MN_REMOVE_DEVICE: { + assert( device->removed ); + remove_pending_irps( device ); + if (device->bthport_symlink_name.Buffer) + { + IoSetDeviceInterfaceState(&device->bthport_symlink_name, FALSE); + RtlFreeUnicodeString( &device->bthport_symlink_name ); + } + if (device->bthradio_symlink_name.Buffer) + { + IoSetDeviceInterfaceState(&device->bthradio_symlink_name, FALSE); + RtlFreeUnicodeString( &device->bthradio_symlink_name ); + } + free( device->hw_name ); + winebluetooth_radio_free( device->radio ); + IoDeleteDevice( device->device_obj ); + irp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest( irp, IO_NO_INCREMENT ); return STATUS_SUCCESS; } case IRP_MN_SURPRISE_REMOVAL: + { + remove_pending_irps( device ); + EnterCriticalSection( &device_list_cs ); + if ( !device->removed ) + { + device->removed = TRUE; + list_remove( &device->entry ); + } + LeaveCriticalSection( &device_list_cs ); ret = STATUS_SUCCESS; - break; + } }
irp->IoStatus.Status = ret; @@ -146,13 +540,24 @@ static void WINAPI driver_unload( DRIVER_OBJECT *driver )
NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path ) { + NTSTATUS status; TRACE( "(%p, %s)\n", driver, debugstr_w( path->Buffer ) );
- driver->DriverExtension->AddDevice = driver_add_device; + status = winebluetooth_init(); + if (status != STATUS_SUCCESS) + return status; + + status = winebluetooth_watcher_init( &watcher ); + if ( status != STATUS_SUCCESS ) + { + ERR( "Failed to initialize bluetooth watcher: %#lx\n", status ); + return status; + } + driver_obj = driver;
- driver->MajorFunction[IRP_MJ_PNP] = bluetooth_pnp; + driver->DriverExtension->AddDevice = driver_add_device; driver->DriverUnload = driver_unload; - + driver->MajorFunction[IRP_MJ_PNP] = bluetooth_pnp; return STATUS_SUCCESS; } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index ef29ed2f8c0..e73586fead3 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -32,6 +32,11 @@ __ASM_STDCALL_FUNC(wrap_fastcall_func1, 8, #else #define call_fastcall_func1(func,a) func(a) #endif +struct string_buffer +{ + WCHAR *string; + size_t len; +};
#define XX(i) case (i): return #i
diff --git a/include/Makefile.in b/include/Makefile.in index d4f63fe169c..a62a24d327f 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -58,6 +58,7 @@ SOURCES = \ bits5_0.idl \ bitsmsg.h \ bluetoothapis.h \ + bthdef.h \ bthsdpdef.h \ cderr.h \ cdosys.idl \ @@ -192,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/bthdef.h b/include/bthdef.h new file mode 100644 index 00000000000..9b65cc8d48f --- /dev/null +++ b/include/bthdef.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 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 __BTHDEF_H__ +#define __BTHDEF_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +DEFINE_GUID( GUID_BTHPORT_DEVICE_INTERFACE, 0x850302a, 0xb344, 0x4fda, 0x9b, 0xe9, 0x90, 0x57, 0x6b, + 0x8d, 0x46, 0xf0 ); + +#ifdef __cplusplus +} +#endif + +#endif /* __BTHDEF_H__ */ diff --git a/include/ddk/bthguid.h b/include/ddk/bthguid.h new file mode 100644 index 00000000000..067e9cac59a --- /dev/null +++ b/include/ddk/bthguid.h @@ -0,0 +1,57 @@ +/* + * 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__ + +/* Not sure how this is any different from GUID_BTHPORT_DEVICE_INTERFACE, but it's used as the + * enumeration ID by windows.devices.bluetooth (see dlls/windows.devices.bluetooth/tests/bluetooth.c). */ +DEFINE_GUID( GUID_BLUETOOTH_RADIO_INTERFACE, 0x92383b0e, 0xf90e, 0x4ac9, 0x8d, 0x44, 0x8c, 0x2d, + 0x0d, 0x0e, 0xbd, 0xa2 ); + +#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 */ + + +#ifdef WINE_BTH_EXTENSIONS +DEFINE_GUID( GUID_BLUETOOTH_WINE_AUTH_REQUEST, 0x1a188a9b, 0x2d33, 0x4269, 0x85, 0xea, 0xd5, 0x3d, + 0x69, 0x18, 0xdc, 0xeb ); + +#define DEFINE_WINEBTH_RADIO_DEVPROPKEY( d, i ) \ + DEFINE_DEVPROPKEY( DEVPKEY_WineBluetooth_Radio_##d, 0x9ccd2c1a, 0x4b06, 0x4ecf, 0xb1, 0xd5, \ + 0x35, 0x62, 0xd6, 0xb0, 0xda, 0xac, (i) ) + +/* Authentication I/O capability as currently advertised by the radio. DEVPROP_TYPE_BYTE */ +DEFINE_WINEBTH_RADIO_DEVPROPKEY( Capability, 1 ); +#endif + +#undef DEFINE_BTH_RADIO_DEVPROPKEY + +#endif