From: Vibhav Pant vibhavp@gmail.com
--- dlls/winebth.sys/bthenum.c | 713 +++++++++++++++++++++++++++++++ dlls/winebth.sys/dbus.c | 632 +++++++++++++++++++++++++++ dlls/winebth.sys/unixlib.c | 9 + dlls/winebth.sys/unixlib.h | 18 +- dlls/winebth.sys/winebluetooth.c | 9 + dlls/winebth.sys/winebluetooth.h | 114 +++++ dlls/winebth.sys/winebth.c | 82 ++++ dlls/winebth.sys/winebth_priv.h | 65 +++ include/Makefile.in | 1 + include/bthdef.h | 23 + include/bthioctl.h | 71 +++ include/ddk/bthguid.h | 27 ++ 12 files changed, 1760 insertions(+), 4 deletions(-) create mode 100644 dlls/winebth.sys/bthenum.c create mode 100644 include/bthioctl.h
diff --git a/dlls/winebth.sys/bthenum.c b/dlls/winebth.sys/bthenum.c new file mode 100644 index 00000000000..2de12b334ae --- /dev/null +++ b/dlls/winebth.sys/bthenum.c @@ -0,0 +1,713 @@ +/* + * Bluetooth enumerator driver + * + * 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 <stdlib.h> +#include <assert.h> + +#define WINE_BTH_EXTENSIONS + +#include <ntstatus.h> +#define WIN32_NO_STATUS +#include <windef.h> +#include <winbase.h> +#include <winternl.h> +#include <winuser.h> +#include <devpkey.h> +#include <dbt.h> +#include <bthsdpdef.h> +#include <bluetoothapis.h> +#include <bthdef.h> +#include <bthioctl.h> +#include <ddk/wdm.h> +#include <ddk/bthguid.h> +#include <winreg.h> +#include <cfgmgr32.h> +#include <wine/debug.h> +#include <wine/list.h> +#include <wine/rbtree.h> + +#include "winebth_priv.h" +#include "winebluetooth.h" + +#include <initguid.h> +DEFINE_GUID( GUID_NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + +WINE_DEFAULT_DEBUG_CHANNEL( bthenum ); + +static DRIVER_OBJECT *driver_obj; + +/* winebluetooth device property flags that are used to construct the 'flags' field in + * BTH_DEVICE_INFO */ +#define BTH_DEVICE_INFO_FLAGS_MEMBERS \ + ( WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS | WINEBLUETOOTH_DEVICE_PROPERTY_CLASS | \ + WINEBLUETOOTH_DEVICE_PROPERTY_NAME | WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED | \ + WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED | WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED | \ + WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING_HINT ) + +enum bthenum_phys_device_kind +{ + BTHENUM_PHYS_DEVICE_KIND_REMOTE_RADIO, +}; + +struct device_ext +{ + BOOL is_fdo; + BOOL removed; +}; + +struct bthenum_func_local_radio_ext +{ + struct device_ext base; + DEVICE_OBJECT *parent_radio_pdo; + DEVICE_OBJECT *device_obj; + + RTL_CRITICAL_SECTION cs; + /* Remote bluetooth radios associated with this radio (struct bthenum_phys_remote_radio) */ + struct list devices; +}; + +struct phys_device_ext +{ + struct device_ext base; + + enum bthenum_phys_device_kind kind; + struct bthenum_func_local_radio_ext *parent_fdo; +}; + +/* Child PDO under a bluetooth radio FDO bus */ +struct bthenum_phys_remote_radio_ext +{ + struct phys_device_ext base; + struct list entry; + + DEVICE_OBJECT *device_obj; + winebluetooth_device_t remote_radio_handle; + winebluetooth_device_props_mask_t known_props; + struct winebluetooth_device_properties props; + + UNICODE_STRING device_link_name; +}; + +static struct bthenum_func_local_radio_ext *fdo_from_DEVICE_OBJECT( DEVICE_OBJECT *device ) +{ + struct device_ext *impl = (struct device_ext *)device->DeviceExtension; + + if (impl->is_fdo) return CONTAINING_RECORD( impl, struct bthenum_func_local_radio_ext, base ); + return CONTAINING_RECORD( impl, struct phys_device_ext, base )->parent_fdo; +} + +NTSTATUS WINAPI bthenum_device_added( DEVICE_OBJECT *fdo_bus, + struct winebluetooth_watcher_event_device_added event ) +{ + struct bthenum_func_local_radio_ext *fdo; + struct bthenum_phys_remote_radio_ext *child_device; + DEVICE_OBJECT *child_pdo; + NTSTATUS status; + + TRACE("fdo_bus=%p\n", fdo_bus); + + fdo = fdo_from_DEVICE_OBJECT( fdo_bus ); + + status = IoCreateDevice( fdo_bus->DriverObject, sizeof( struct bthenum_phys_remote_radio_ext ), + NULL, FILE_DEVICE_BLUETOOTH, 0, FALSE, &child_pdo ); + if (status != STATUS_SUCCESS) + { + ERR( "failed to create bluetooth radio device: %#lx\n", status ); + return status; + } + child_device = child_pdo->DeviceExtension; + child_device->base.base.is_fdo = FALSE; + child_device->base.base.removed = FALSE; + child_device->base.kind = BTHENUM_PHYS_DEVICE_KIND_REMOTE_RADIO; + child_device->base.parent_fdo = fdo; + + child_device->remote_radio_handle = event.device; + child_device->props = event.props; + child_device->known_props = event.known_props_mask; + child_device->device_obj = child_pdo; + + RtlEnterCriticalSection( &fdo->cs ); + list_add_tail(&fdo->devices, &child_device->entry); + RtlLeaveCriticalSection( &fdo->cs ); + + IoInvalidateDeviceRelations( fdo_bus, BusRelations ); + + return STATUS_SUCCESS; +} + +NTSTATUS WINAPI bthenum_device_removed( DEVICE_OBJECT *fdo_bus, + const winebluetooth_device_t device_handle ) +{ + struct bthenum_func_local_radio_ext *fdo; + struct bthenum_phys_remote_radio_ext *remote_device; + + fdo = fdo_from_DEVICE_OBJECT( fdo_bus ); + + RtlEnterCriticalSection( &fdo->cs ); + LIST_FOR_EACH_ENTRY( remote_device, &fdo->devices, struct bthenum_phys_remote_radio_ext, entry ) + { + if (winebluetooth_device_equal( device_handle, remote_device->remote_radio_handle ) + && !remote_device->base.base.removed) + { + remote_device->base.base.removed = TRUE; + list_remove( &remote_device->entry ); + break; + } + } + RtlLeaveCriticalSection( &fdo->cs ); + + IoInvalidateDeviceRelations( fdo_bus, BusRelations ); + return STATUS_SUCCESS; +} + +static UINT32 device_get_flags( winebluetooth_device_props_mask_t props_mask, + struct winebluetooth_device_properties props ); + +static BOOL device_set_properties( DEVICE_OBJECT *device_obj, UINT32 *old_flags, + winebluetooth_device_props_mask_t props_mask, + struct winebluetooth_device_properties props ); + +static void device_refresh_properties( struct bthenum_phys_remote_radio_ext *remote_device, + struct winebluetooth_device_properties new_props, + winebluetooth_device_props_mask_t changed_props) +{ + UINT32 old_flags = device_get_flags( remote_device->known_props, remote_device->props ); + device_set_properties( remote_device->device_obj, &old_flags, changed_props, new_props ); + + return; +} + +NTSTATUS +bthenum_device_props_changed( DEVICE_OBJECT *fdo_bus, + struct winebluetooth_watcher_event_device_props_changed event ) +{ + struct bthenum_func_local_radio_ext *fdo; + struct bthenum_phys_remote_radio_ext *remote_device; + + TRACE( "fdo_bus=%p\n", fdo_bus ); + + fdo = fdo_from_DEVICE_OBJECT( fdo_bus ); + + RtlEnterCriticalSection( &fdo->cs ); + LIST_FOR_EACH_ENTRY( remote_device, &fdo->devices, struct bthenum_phys_remote_radio_ext, entry ) + { + if (winebluetooth_device_equal( remote_device->remote_radio_handle, event.device )) + { + device_refresh_properties( remote_device, event.prop, event.changed_props_mask ); + } + } + RtlLeaveCriticalSection( &fdo->cs ); + + return STATUS_SUCCESS; +} + +/* Called once for every Bluetooth Radio discovered. */ +NTSTATUS WINAPI bthenum_create_fdo_bus( DEVICE_OBJECT *pdo, DEVICE_OBJECT **fdo_bus ) +{ + struct bthenum_func_local_radio_ext *fdo; + NTSTATUS status; + + TRACE("(%p, %p)\n", pdo, fdo_bus); + + status = IoCreateDevice( driver_obj, sizeof( struct bthenum_func_local_radio_ext ), NULL, + FILE_DEVICE_BUS_EXTENDER, 0, FALSE, fdo_bus ); + if (status != STATUS_SUCCESS) + { + ERR( "failed to create bus FDO, status: %#lx\n", status ); + return status; + } + + fdo = (*fdo_bus)->DeviceExtension; + fdo->base.is_fdo = TRUE; + fdo->base.removed = FALSE; + fdo->parent_radio_pdo = pdo; + fdo->device_obj = *fdo_bus; + list_init( &fdo->devices ); + + RtlInitializeCriticalSectionEx( &fdo->cs, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO ); + fdo->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": bthenum_func_local_radio.cs"); + + IoInvalidateDeviceRelations(*fdo_bus, BusRelations); + (*fdo_bus)->Flags &= ~DO_DEVICE_INITIALIZING; + return STATUS_SUCCESS; +} + +static UINT32 device_get_flags( winebluetooth_device_props_mask_t props_mask, + struct winebluetooth_device_properties props ) +{ + UINT32 flags = 0; + + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS) flags |= BDIF_ADDRESS; + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_CLASS) flags |= BDIF_COD; + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_NAME) flags |= BDIF_NAME; + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED && props.trusted) flags |= BDIF_PERSONAL; + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED && props.paired) flags |= BDIF_PAIRED; + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED && props.connected) flags |= BDIF_CONNECTED; + if (!(props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING_HINT)) + { + flags |= BDIF_SSP_SUPPORTED | BDIF_SSP_MITM_PROTECTED; + if (flags & BDIF_PAIRED) flags |= BDIF_SSP_PAIRED; + } + + return flags; +} + +static BOOL device_set_properties( DEVICE_OBJECT *device_obj, UINT32 *old_flags, + winebluetooth_device_props_mask_t props_mask, + struct winebluetooth_device_properties props ) +{ + UINT32 flags = device_get_flags( props_mask, props ); + BOOL flags_changed = (!old_flags || *old_flags != flags); + + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS) + { + WCHAR addr_str[13]; + BYTE *addr = props.address.rgBytes; + + swprintf( addr_str, ARRAY_SIZE( addr_str ), L"%02X%02X%02X%02X%02X%02X", addr[0], addr[1], + addr[2], addr[3], addr[4], addr[5] ); + IoSetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_DeviceAddress, LOCALE_NEUTRAL, + PLUGPLAY_PROPERTY_PERSISTENT, DEVPROP_TYPE_STRING, + sizeof( addr_str ), addr_str ); + } + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_NAME) + { + SIZE_T chars = mbstowcs( NULL, props.name, 0 ); + SIZE_T size = sizeof( WCHAR ) * ( chars + 1 ); + WCHAR *buf = malloc( size ); + if ( buf ) + { + mbstowcs( buf, props.name, strlen( props.name ) + 1 ); + IoSetDevicePropertyData( device_obj, &DEVPKEY_WineBluetooth_Device_Name, LOCALE_NEUTRAL, + PLUGPLAY_PROPERTY_PERSISTENT, DEVPROP_TYPE_STRING, size, buf ); + free( buf ); + } + } + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_PNPID) + { + struct winebluetooth_device_pnpid pnpid = props.pnpid; + IoSetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_DeviceVIDSource, LOCALE_NEUTRAL, + PLUGPLAY_PROPERTY_PERSISTENT, DEVPROP_TYPE_BYTE, + sizeof( pnpid.vid_source ), &pnpid.vid_source ); + IoSetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_DeviceVID, LOCALE_NEUTRAL, + PLUGPLAY_PROPERTY_PERSISTENT, DEVPROP_TYPE_UINT16, + sizeof( pnpid.vid ), &pnpid.vid ); + IoSetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_DevicePID, LOCALE_NEUTRAL, + PLUGPLAY_PROPERTY_PERSISTENT, DEVPROP_TYPE_UINT16, + sizeof( pnpid.pid ), &pnpid.pid ); + IoSetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_DeviceProductVersion, + LOCALE_NEUTRAL, PLUGPLAY_PROPERTY_PERSISTENT, DEVPROP_TYPE_UINT16, + sizeof( pnpid.product_version ), &pnpid.product_version ); + } + if (props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_CLASS) + { + UINT32 cod = props.class; + IoSetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_ClassOfDevice, LOCALE_NEUTRAL, + PLUGPLAY_PROPERTY_PERSISTENT, DEVPROP_TYPE_UINT32, sizeof( cod ), + &cod ); + } + if (flags_changed) + IoSetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_DeviceFlags, LOCALE_NEUTRAL, + PLUGPLAY_PROPERTY_PERSISTENT, DEVPROP_TYPE_UINT32, sizeof( flags ), + &flags ); + + return flags_changed; +} + +static void device_get_properties( DEVICE_OBJECT *device_obj, BTH_DEVICE_INFO *info ) +{ + NTSTATUS status; + UINT32 flags; + + TRACE( "%p\n", device_obj ); + + memset( info, 0, sizeof( *info ) ); + status = IoGetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_DeviceFlags, LOCALE_NEUTRAL, + PLUGPLAY_PROPERTY_PERSISTENT, sizeof( flags ), &flags, NULL, + NULL ); + if (status != STATUS_SUCCESS) + WARN( "could not get device flags: %#x\n", status); + + info->flags = flags; + + if (flags & BDIF_ADDRESS) + { + WCHAR addr_buf[13]; + BLUETOOTH_ADDRESS addr = {0}; + + status = IoGetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_DeviceAddress, + LOCALE_NEUTRAL, PLUGPLAY_PROPERTY_PERSISTENT, + sizeof( addr_buf ), addr_buf, NULL, NULL ); + if (status != STATUS_SUCCESS) + { + WARN( "could not get device address: %#x\n", status ); + info->flags &= ~BDIF_ADDRESS; + } + else + swscanf( addr_buf, L"%x%x%x%x%x%x", addr.rgBytes[0], addr.rgBytes[1], addr.rgBytes[2], + addr.rgBytes[3], addr.rgBytes[4], addr.rgBytes[5] ); + info->address = addr.ullLong; + } + if (flags & BDIF_COD) + { + UINT32 cod; + status = IoGetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_ClassOfDevice, + LOCALE_NEUTRAL, PLUGPLAY_PROPERTY_PERSISTENT, + sizeof( cod ), &cod, NULL, NULL ); + if (status != STATUS_SUCCESS) + { + WARN( "could not get device class: %#x\n", status ); + info->flags &= ~BDIF_COD; + } + else info->classOfDevice = cod; + } + if (flags & BDIF_NAME) + { + WCHAR name_buf[BLUETOOTH_MAX_NAME_SIZE]; + + status = IoGetDevicePropertyData( device_obj, &DEVPKEY_WineBluetooth_Device_Name, + LOCALE_NEUTRAL, PLUGPLAY_PROPERTY_PERSISTENT, + sizeof( name_buf ), name_buf, NULL, NULL ); + if (status != STATUS_SUCCESS) + { + WARN("could not get device name: %#x\n", status); + info->flags &= BDIF_NAME; + } + else + { + wcstombs(info->name, name_buf, sizeof(info->name)); + info->name[ARRAY_SIZE(info->name) - 1] = '\0'; + } + } +} + +static NTSTATUS fdo_query_id( const DEVICE_OBJECT *device_obj, IRP *irp, BUS_QUERY_ID_TYPE type, + struct string_buffer *buf ) +{ + TRACE( "device_obj=%p irp=%p type=%d buf=%p\n", device_obj, irp, + debugstr_BUS_QUERY_ID_TYPE( type ), buf ); + switch ( type ) + { + case BusQueryDeviceID: + append_id( buf, L"BTH\WINE_BTHBRB" ); + break; + case BusQueryInstanceID: + append_id(buf, L"%p", device_obj); + break; + case BusQueryHardwareIDs: + append_id( buf, L"BTH\WINE_BTHBRB" ); + case BusQueryCompatibleIDs: + append_id( buf, L"" ); + break; + default: + FIXME( "Unhandled ID query type %s.\n", debugstr_BUS_QUERY_ID_TYPE( type ) ); + return irp->IoStatus.Status; + } + if (buf->string == NULL) irp->IoStatus.Status = STATUS_NO_MEMORY; + else + { + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = (ULONG_PTR)buf->string; + } + return irp->IoStatus.Status; +} + +static NTSTATUS WINAPI fdo_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + struct bthenum_func_local_radio_ext *device = + (struct bthenum_func_local_radio_ext *)device_obj->DeviceExtension; + + TRACE( "device_obj %p, irp %p, code %s\n", device_obj, irp, + debugstr_minor_function_code( stack->MinorFunction ) ); + + switch (stack->MinorFunction) + { + case IRP_MN_QUERY_DEVICE_RELATIONS: + { + struct bthenum_phys_remote_radio_ext *remote; + DEVICE_RELATIONS *devices; + SIZE_T i = 0; + SIZE_T objects_count; + + if (stack->Parameters.QueryDeviceRelations.Type != BusRelations) + { + break; + } + + RtlEnterCriticalSection( &device->cs ); + objects_count = list_count( &device->devices ); + devices = ExAllocatePool( + PagedPool, offsetof( DEVICE_RELATIONS, Objects[objects_count] ) ); + if (devices == NULL) + { + RtlLeaveCriticalSection( &device->cs ); + irp->IoStatus.Status = STATUS_NO_MEMORY; + break; + } + + LIST_FOR_EACH_ENTRY( remote, &device->devices, struct bthenum_phys_remote_radio_ext, entry ) + { + devices->Objects[i++] = remote->device_obj; + call_fastcall_func1( ObfReferenceObject, remote->device_obj ); + } + RtlLeaveCriticalSection( &device->cs ); + assert( objects_count == i ); + devices->Count = i; + irp->IoStatus.Information = (ULONG_PTR)devices; + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_SUCCESS; + break; + } + case IRP_MN_START_DEVICE: + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return irp->IoStatus.Status; + case IRP_MN_SURPRISE_REMOVAL: + { + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_SUCCESS; + } + case IRP_MN_REMOVE_DEVICE: + { + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + IoDeleteDevice( device->parent_radio_pdo ); + return STATUS_SUCCESS; + } + case IRP_MN_QUERY_CAPABILITIES: + { + break; + } + case IRP_MN_QUERY_ID: + { + BUS_QUERY_ID_TYPE type = stack->Parameters.QueryId.IdType; + struct string_buffer buf = { 0 }; + + irp->IoStatus.Status = fdo_query_id( device_obj, irp, type, &buf ); + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return irp->IoStatus.Status; + } + default: + FIXME( "Unhandled minor function %s\n", debugstr_minor_function_code( stack->MinorFunction ) ); + } + + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( device->parent_radio_pdo, irp ); +} + +static NTSTATUS pdo_device_query_id( const struct bthenum_phys_remote_radio_ext *device, IRP *irp, + BUS_QUERY_ID_TYPE type, struct string_buffer *buf ) +{ + TRACE( "device=%p irp=%p type=%s buf=%p\n", device, irp, debugstr_BUS_QUERY_ID_TYPE( type ), buf ); + switch (type) + { + case BusQueryDeviceID: + case BusQueryHardwareIDs: + append_id( buf, L"BTHENUM\Dev_%02X%02X%02X%02X%02X%02X", + device->props.address.rgBytes[0], device->props.address.rgBytes[1], + device->props.address.rgBytes[2], device->props.address.rgBytes[3], + device->props.address.rgBytes[4], device->props.address.rgBytes[5] ); + if ( type == BusQueryHardwareIDs ) append_id( buf, L"" ); + break; + case BusQueryInstanceID: + append_id( buf, L"%p&BluetoothDevice_%02X%02X%02X%02X%02X%02X", + device->remote_radio_handle, device->props.address.rgBytes[0], + device->props.address.rgBytes[1], device->props.address.rgBytes[2], + device->props.address.rgBytes[3], device->props.address.rgBytes[4], + device->props.address.rgBytes[5] ); + break; + case BusQueryCompatibleIDs: + append_id( buf, L"BTHENUM\GENERIC_DEVICE" ); + append_id( buf, L"" ); + break; + default: + FIXME( "Unhandled ID query type %s.\n", debugstr_BUS_QUERY_ID_TYPE( type ) ); + return irp->IoStatus.Status; + } + if (buf->string == NULL) irp->IoStatus.Status = STATUS_NO_MEMORY; + else + { + irp->IoStatus.Status = STATUS_SUCCESS; + irp->IoStatus.Information = (ULONG_PTR)buf->string; + } + return irp->IoStatus.Status; +} + +static NTSTATUS WINAPI pdo_device_pnp( DEVICE_OBJECT *device_obj, IRP *irp ) +{ + struct bthenum_phys_remote_radio_ext *device = + (struct bthenum_phys_remote_radio_ext *)device_obj->DeviceExtension; + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + UCHAR code = stack->MinorFunction; + NTSTATUS status = irp->IoStatus.Status; + + TRACE( "device %p, irp %p, code %s\n", device, irp, debugstr_minor_function_code( code ) ); + switch (code) + { + case IRP_MN_START_DEVICE: + { + GUID g = GUID_NULL; + + if ( IoRegisterDeviceInterface( device->device_obj, &GUID_BTH_DEVICE_INTERFACE, NULL, + &device->device_link_name ) == STATUS_SUCCESS ) + IoSetDeviceInterfaceState( &device->device_link_name, TRUE ); + IoSetDevicePropertyData( device_obj, &DEVPKEY_Bluetooth_ServiceGUID, LOCALE_NEUTRAL, 0, + DEVPROP_TYPE_GUID, sizeof( GUID_NULL ), &g ); + device_set_properties( device_obj, NULL, device->known_props, device->props ); + break; + } + case IRP_MN_SURPRISE_REMOVAL: + if (!device->base.base.removed) + { + device->base.base.removed = TRUE; + list_remove( &device->entry ); + } + + status = STATUS_SUCCESS; + break; + case IRP_MN_REMOVE_DEVICE: + assert( device->base.base.removed ); + winebluetooth_device_free( device->remote_radio_handle ); + if (device->device_link_name.Buffer) RtlFreeUnicodeString( &device->device_link_name ); + IoDeleteDevice( device_obj ); + + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return STATUS_SUCCESS; + case IRP_MN_QUERY_ID: + { + BUS_QUERY_ID_TYPE type = stack->Parameters.QueryId.IdType; + struct string_buffer buf = { 0 }; + + status = pdo_device_query_id( device, irp, type, &buf ); + break; + } + case IRP_MN_QUERY_CAPABILITIES: + { + DEVICE_CAPABILITIES *caps = stack->Parameters.DeviceCapabilities.Capabilities; + caps->Removable = TRUE; + caps->SurpriseRemovalOK = TRUE; + caps->RawDeviceOK = TRUE; + status = STATUS_SUCCESS; + break; + } + } + irp->IoStatus.Status = status; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return status; +} + +#define PNPID_FORMAT "VID&%04x%04x_PID&%04x" +#define PNPID_FORMAT_ARGS(p) (p).vid_source, (p).vid, (p).pid + +#define LOCALMFG_FORMAT "LOCALMFG&%04x" + +static NTSTATUS WINAPI pdo_pnp( DEVICE_OBJECT *device, IRP *irp ) +{ + struct phys_device_ext *base = (struct phys_device_ext *)device->DeviceExtension; + switch (base->kind) + { + case BTHENUM_PHYS_DEVICE_KIND_REMOTE_RADIO: + return pdo_device_pnp( device, irp ); + DEFAULT_UNREACHABLE; + } +} + +static NTSTATUS WINAPI bthenum_pnp( DEVICE_OBJECT *device, IRP *irp ) +{ + struct device_ext *base = (struct device_ext *)device->DeviceExtension; + if (base->is_fdo) return fdo_pnp( device, irp ); + return pdo_pnp(device, irp); +} + +static NTSTATUS WINAPI bthenum_ioctl( DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + IO_STATUS_BLOCK *iosb = &irp->IoStatus; + struct bthenum_func_local_radio_ext *fdo = fdo_from_DEVICE_OBJECT( device ); + ULONG insize = stack->Parameters.DeviceIoControl.InputBufferLength; + ULONG outsize = stack->Parameters.DeviceIoControl.OutputBufferLength; + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + NTSTATUS status; + + TRACE( "device %p irp % code %s", device, irp, debugstr_IoControlCode( code ) ); + switch ( code ) + { + case IOCTL_BTH_GET_DEVICE_INFO: + { + BTH_DEVICE_INFO_LIST *list = (BTH_DEVICE_INFO_LIST *)irp->AssociatedIrp.SystemBuffer; + struct bthenum_phys_remote_radio_ext *remote_device; + BOOL have_buf = TRUE; + + if (list == NULL) + { + status = STATUS_INVALID_PARAMETER; + break; + } + if (insize < sizeof(*list)) + { + status = STATUS_INVALID_BUFFER_SIZE; + break; + } + memset(list, 0, sizeof(*list)); + + iosb->Information = sizeof( BTH_DEVICE_INFO_LIST ); + + RtlEnterCriticalSection( &fdo->cs ); + LIST_FOR_EACH_ENTRY( remote_device, &fdo->devices, struct bthenum_phys_remote_radio_ext, entry ) + { + SIZE_T i = list->numOfDevices++; + have_buf = have_buf && outsize >= ( sizeof( BTH_DEVICE_INFO_LIST ) + + sizeof( BTH_DEVICE_INFO ) * i ); + if (have_buf) + { + device_get_properties( remote_device->device_obj, &list->deviceList[i] ); + if (i > 0) + iosb->Information += sizeof( BTH_DEVICE_INFO ); + } + } + RtlLeaveCriticalSection( &fdo->cs ); + status = have_buf ? STATUS_SUCCESS : STATUS_INVALID_BUFFER_SIZE; + break; + } + default: + IoSkipCurrentIrpStackLocation( irp ); + return IoCallDriver( fdo->parent_radio_pdo, irp ); + } + + iosb->Status = status; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return status; +} + + +NTSTATUS WINAPI bthenum_entry_point( DRIVER_OBJECT *driver, UNICODE_STRING *path ) +{ + TRACE( "(%p, %s)\n", driver, debugstr_w( path->Buffer ) ); + + driver_obj = driver; + driver->MajorFunction[IRP_MJ_PNP] = bthenum_pnp; + driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = bthenum_ioctl; + return STATUS_SUCCESS; +} diff --git a/dlls/winebth.sys/dbus.c b/dlls/winebth.sys/dbus.c index 4878d9dc8a4..1809cfc6cf5 100644 --- a/dlls/winebth.sys/dbus.c +++ b/dlls/winebth.sys/dbus.c @@ -75,6 +75,8 @@ const int bluez_timeout = -1; DBUS_TYPE_OBJECT_PATH_AS_STRING \ DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING
+#define DBUS_PROPERTIES_SIGNAL_PROPERTIESCHANGED "PropertiesChanged" + #define DBUS_PROPERTIES_CHANGED_SIGNATURE \ DBUS_TYPE_STRING_AS_STRING \ DBUS_TYPE_ARRAY_AS_STRING \ @@ -85,6 +87,7 @@ const int bluez_timeout = -1;
#define BLUEZ_DEST "org.bluez" #define BLUEZ_INTERFACE_ADAPTER "org.bluez.Adapter1" +#define BLUEZ_INTERFACE_DEVICE "org.bluez.Device1"
#define DO_FUNC( f ) typeof( f ) (*p_##f) DBUS_FUNCS; @@ -111,6 +114,11 @@ failed: return FALSE; }
+static inline int starts_with( const char *str, const char *prefix ) +{ + return !strncmp( str, prefix, strlen( prefix ) ); +} + static NTSTATUS bluez_dbus_error_to_ntstatus( const DBusError *error ) {
@@ -318,6 +326,271 @@ static void bluez_radio_prop_from_dict_entry( const char *prop_name, DBusMessage } }
+static void bluez_device_prop_from_dict_entry( const char *prop_name, DBusMessageIter *variant, + BOOL get_device_reported_name, + struct winebluetooth_device_properties *props, + winebluetooth_device_props_mask_t *props_mask, + winebluetooth_device_props_mask_t wanted_props_mask ) +{ + + TRACE_( dbus ) + ( "(%s, %p, %d, %p, %p, %#x)\n", debugstr_a( prop_name ), variant, get_device_reported_name, props, + props_mask, wanted_props_mask ); + + if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_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_DEVICE_PROPERTY_ADDRESS; + } + if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS_TYPE && + !strcmp( prop_name, "AddressType" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING ) + { + const char *address_type; + + p_dbus_message_iter_get_basic( variant, &address_type ); + props->address_type = WINEBLUETOOTH_DEVICE_ADDRESS_TYPE_UNKNOWN; + if (!strcmp( address_type, "random" )) + { + props->address_type = WINEBLUETOOTH_DEVICE_ADDRESS_TYPE_RANDOM; + } + if (!strcmp( address_type, "public" )) + { + props->address_type = WINEBLUETOOTH_DEVICE_ADDRESS_TYPE_PUBLIC; + } + *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS_TYPE; + } + else if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_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_DEVICE_PROPERTY_CLASS; + } + else if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED && + !strcmp( prop_name, "Connected" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN ) + { + dbus_bool_t connected; + + p_dbus_message_iter_get_basic( variant, &connected ); + props->connected = connected ? TRUE : FALSE; + *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED; + } + else if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_BONDED && + !strcmp( prop_name, "Bonded" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN ) + { + dbus_bool_t bonded; + + p_dbus_message_iter_get_basic( variant, &bonded ); + props->bonded = bonded ? TRUE : FALSE; + *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_BONDED; + } + else if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED && + !strcmp( prop_name, "Paired" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN ) + { + dbus_bool_t paired; + + p_dbus_message_iter_get_basic( variant, &paired ); + props->paired = paired ? TRUE : FALSE; + *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED; + } + else if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING_HINT && + !strcmp( prop_name, "LegacyPairing" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN ) + { + dbus_bool_t legacy_pairing; + + p_dbus_message_iter_get_basic( variant, &legacy_pairing ); + props->legacy_pairing_hint = legacy_pairing ? TRUE : FALSE; + *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING_HINT; + } + else if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED && + !strcmp( prop_name, "Trusted" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN ) + { + dbus_bool_t trusted; + + p_dbus_message_iter_get_basic( variant, &trusted ); + props->trusted = trusted ? TRUE : FALSE; + *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED; + } + else if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_NAME && + !get_device_reported_name && !strcmp( prop_name, "Alias" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING ) + { + const char *alias = NULL; + SIZE_T len; + + p_dbus_message_iter_get_basic( variant, &alias ); + assert( alias != NULL ); + len = strlen( alias ); + memcpy( props->name, alias, min( len + 1, ARRAYSIZE( props->name ) ) ); + props->name[ARRAYSIZE( props->name ) - 1] = '\0'; + *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_NAME; + } + else if ( wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_NAME && + get_device_reported_name && !strcmp( prop_name, "Name" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING ) + { + const char *name = NULL; + SIZE_T len; + + p_dbus_message_iter_get_basic( variant, &name ); + assert( name != NULL ); + len = strlen( name ); + memcpy( props->name, name, min( len + 1, ARRAYSIZE( props->name ) ) ); + props->name[ARRAYSIZE( props->name ) - 1] = '\0'; + *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_NAME; + } + else if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_PNPID && + !strcmp( prop_name, "Modalias" ) && + p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING ) + { + const char *modalias = NULL; + + p_dbus_message_iter_get_basic( variant, &modalias ); + assert( modalias != NULL ); + + if (strstr(modalias, "bluetooth:")) + { + props->pnpid.vid_source = 0x0001; + modalias += strlen("bluetooth:"); + } + else if (strstr(modalias, "usb:")) + { + props->pnpid.vid_source = 0x0002; + modalias += strlen("usb:"); + } + sscanf( modalias, "v%04hXp%04hXd%04hX", &props->pnpid.vid, &props->pnpid.pid, + &props->pnpid.product_version ); + *props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_PNPID; + } +} + +NTSTATUS +bluez_device_props_get_all_reply_to_params( + DBusMessage *reply, struct bluetooth_get_device_props_by_path_params *params ) +{ + DBusMessageIter dict, prop_iter, variant; + const char *prop_name; + + p_dbus_message_iter_init( reply, &dict ); + if (p_dbus_message_iter_get_arg_type( &dict ) != DBUS_TYPE_ARRAY) + { + ERR( "unexpected result type from GetAll\n" ); + p_dbus_message_unref( reply ); + return STATUS_INTERNAL_ERROR; + } + params->valid_props = 0; + + p_dbus_message_iter_recurse( &dict, &prop_iter ); + while ((prop_name = bluez_next_dict_entry( &prop_iter, &variant ))) + { + winebluetooth_device_props_mask_t prop = 0; + + bluez_device_prop_from_dict_entry( prop_name, &variant, params->get_device_reported_name, + ¶ms->props, &prop, + WINEBLUETOOTH_DEVICE_ALL_PROPERTIES ); + params->valid_props |= prop; + } + + p_dbus_message_unref( reply ); + return STATUS_SUCCESS; +} + +struct bluez_device_get_all_reply_cb_data +{ + struct bluetooth_get_device_props_by_path_params params; + void (*callback)( NTSTATUS status, struct bluetooth_get_device_props_by_path_params *params, void *data ); + void *user_data; +}; + +static void bluez_device_get_all_reply_callback( DBusPendingCall *pending, void *user_data ) +{ + struct bluez_device_get_all_reply_cb_data *cb_data; + DBusMessage *reply; + DBusError error; + NTSTATUS status; + + TRACE( "(%p, %p)\n", pending, user_data ); + + cb_data = user_data; + assert( cb_data != NULL ); + assert( cb_data->callback != NULL ); + + reply = p_dbus_pending_call_steal_reply( pending ); + p_dbus_error_init( &error ); + if (p_dbus_set_error_from_message(&error, reply )) + { + status = bluez_dbus_error_to_ntstatus( &error ); + p_dbus_error_free( &error ); + } + else + { + status = bluez_device_props_get_all_reply_to_params( reply, &cb_data->params ); + } + p_dbus_message_unref( reply ); + p_dbus_pending_call_unref( pending ); + cb_data->callback( status, &cb_data->params, cb_data->user_data ); +} + +static NTSTATUS bluez_device_get_props_by_path_async( + DBusConnection *connection, struct bluetooth_get_device_props_by_path_params *params, + DBusPendingCall **pending_call, + void ( *callback )( NTSTATUS status, struct bluetooth_get_device_props_by_path_params *params, + void *data ), + void *data ) +{ + DBusMessage *request; + DBusError error; + static const char *adapter_iface = BLUEZ_INTERFACE_DEVICE; + dbus_bool_t ret; + struct bluez_device_get_all_reply_cb_data *cb_data; + + request = p_dbus_message_new_method_call( BLUEZ_DEST, params->device_path->str, + DBUS_INTERFACE_PROPERTIES, "GetAll" ); + if (!request) return STATUS_NO_MEMORY; + + p_dbus_message_append_args( request, DBUS_TYPE_STRING, &adapter_iface, DBUS_TYPE_INVALID ); + p_dbus_error_init( &error ); + TRACE( "Getting all properties for device '%s'\n", params->device_path->str ); + ret = p_dbus_connection_send_with_reply( connection, request, pending_call, bluez_timeout ); + p_dbus_message_unref( request ); + if (!ret) return STATUS_NO_MEMORY; + if (*pending_call == NULL) return STATUS_INTERNAL_ERROR; + + if (callback != NULL) + { + cb_data = calloc( 1, sizeof( struct bluez_device_get_all_reply_cb_data ) ); + if (cb_data == NULL) return STATUS_NO_MEMORY; + + memcpy( &cb_data->params, params, + sizeof( struct bluetooth_get_device_props_by_path_params ) ); + cb_data->callback = callback; + cb_data->user_data = data; + + if (!p_dbus_pending_call_set_notify( *pending_call, bluez_device_get_all_reply_callback, + cb_data, free )) + { + free( cb_data ); + return STATUS_NO_MEMORY; + } + } + + return STATUS_SUCCESS; +} + struct bluez_pair_complete_params { void *user_data; @@ -374,6 +647,20 @@ void bluez_dbus_free( void *connection ) p_dbus_connection_unref( connection ); }
+static struct unix_name *bluez_device_path_get_adapter( const char *device_path ) +{ + static const char *adapter_prefix = "/org/bluez/hci"; + const char *adapter_end; + char buf[80]; + + if (!starts_with( device_path, "/org/bluez/hci" )) return NULL; + adapter_end = strchr( &device_path[strlen( adapter_prefix )-1], '/' ); + if (adapter_end == NULL) return NULL; + memcpy (buf, device_path, adapter_end - device_path); + buf[adapter_end - device_path] = '\0'; + return unix_name_get_or_create( buf ); +} + struct bluez_watcher_event_pending_call { struct list entry; @@ -427,6 +714,41 @@ static BOOL bluez_event_list_queue_new_event( struct list *event_list, return bluez_event_list_queue_new_event_with_call( event_list, event_type, event, NULL, NULL ); }
+static void bluez_filter_device_props_changed_callback( DBusPendingCall *call, void *user_data ) +{ + union winebluetooth_watcher_event_data *event = user_data; + struct winebluetooth_watcher_event_device_props_changed *changed = &event->device_props_changed; + const struct unix_name *device = event->device_props_changed.device.handle; + DBusMessage *reply = p_dbus_pending_call_steal_reply( call ); + DBusMessageIter dict, prop_iter, variant; + const char *prop_name; + DBusError error; + + TRACE( "call %p, device %s radio %s\n", call, debugstr_a(changed->device.handle), debugstr_a(changed->radio.handle) ); + + p_dbus_error_init( &error ); + if (p_dbus_set_error_from_message(&error, reply)) + { + ERR( "Failed to get device properties for '%s': '%s': '%s'\n", device->str, error.name, + error.message ); + p_dbus_error_free( &error ); + p_dbus_message_unref( reply ); + return; + } + p_dbus_error_free( &error ); + + p_dbus_message_iter_init( reply, &dict ); + p_dbus_message_iter_recurse( &dict, &prop_iter ); + while((prop_name = bluez_next_dict_entry( &prop_iter, &variant ))) + { + bluez_device_prop_from_dict_entry( prop_name, &variant, TRUE, &changed->prop, + &changed->changed_props_mask, + changed->invalid_props_mask ); + } + changed->invalid_props_mask &= ~changed->changed_props_mask; + p_dbus_message_unref( reply ); +} + static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, void *user_data ) { struct list *event_list; @@ -493,6 +815,64 @@ static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, v } } } + else if (strcmp( iface_name, BLUEZ_INTERFACE_DEVICE ) == 0) + { + struct winebluetooth_watcher_event_device_added device_added = {0}; + DBusMessageIter props_iter, variant; + const char *prop_name; + BOOL success = FALSE; + struct unix_name *radio = NULL; + + 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 ))) + { + + if (!strcmp( prop_name, "Adapter" ) + && p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_OBJECT_PATH) + { + const char *path; + p_dbus_message_iter_get_basic( &variant, &path ); + device_added.radio.handle = radio = unix_name_get_or_create( path ); + if (!device_added.radio.handle) + { + ERR( "failed to allocate memory for adapter path %s\n", path ); + break; + } + success = TRUE; + } + bluez_device_prop_from_dict_entry( + prop_name, &variant, TRUE, &device_added.props, + &device_added.known_props_mask, WINEBLUETOOTH_DEVICE_ALL_PROPERTIES ); + } + if (success) + { + struct unix_name *device; + + assert(radio != NULL); + device_added.device.handle = device = unix_name_get_or_create( object_path );; + if (!device) + { + + ERR( "failed to allocate memory for device path %s\n", object_path ); + unix_name_free( device ); + unix_name_free( radio ); + } + else + { + union winebluetooth_watcher_event_data event = { .device_added = device_added }; + TRACE( "New BlueZ %s object added at %s: %p\n", BLUEZ_INTERFACE_DEVICE, + object_path, device_added.device.handle ); + if (!bluez_event_list_queue_new_event( + event_list, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_ADDED, event )) + { + unix_name_free( device ); + unix_name_free( radio ); + } + } + } + } p_dbus_message_iter_next( &ifaces_iter ); } } @@ -538,9 +918,159 @@ static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, v unix_name_free(radio.handle); } } + else if (strcmp( interfaces[i], BLUEZ_INTERFACE_DEVICE ) == 0) + { + struct unix_name *radio; + struct unix_name *device = unix_name_get_or_create( object_path ); + struct winebluetooth_watcher_event_device_removed device_removed; + union winebluetooth_watcher_event_data event; + + if (!device) + { + ERR( "failed to allocate memory for device path %s\n", object_path ); + continue; + } + radio = bluez_device_path_get_adapter( object_path ); + if (!radio) + { + ERR( "failed to get associated radio for device path %s\n", object_path ); + unix_name_free( device ); + continue; + } + + device_removed.device.handle = device; + device_removed.radio.handle = radio; + event.device_removed = device_removed; + + if (!bluez_event_list_queue_new_event( + event_list, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_REMOVED, event )) + { + unix_name_free( device ); + unix_name_free( radio ); + } + } } p_dbus_free_string_array( interfaces ); } + else if (p_dbus_message_is_signal( msg, DBUS_INTERFACE_PROPERTIES, DBUS_PROPERTIES_SIGNAL_PROPERTIESCHANGED ) && + p_dbus_message_has_signature( msg, DBUS_PROPERTIES_CHANGED_SIGNATURE )) + { + DBusMessageIter iter; + const char *iface; + + p_dbus_message_iter_init( msg, &iter ); + p_dbus_message_iter_get_basic( &iter, &iface ); + assert( iface != NULL ); + + if (strcmp( iface, BLUEZ_INTERFACE_DEVICE ) == 0) + { + struct winebluetooth_watcher_event_device_props_changed props_changed = {0}; + struct unix_name *device, *radio; + DBusPendingCall *pending_call = NULL; + DBusMessageIter changed_props_iter, invalid_props_iter, variant; + const char *prop_name; + const static struct + { + const char *prop_str; + winebluetooth_device_props_mask_t prop_mask; + } device_props_mask_map[] = { + { "Address", WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS }, + { "AddressType", WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS_TYPE }, + { "Class", WINEBLUETOOTH_DEVICE_PROPERTY_CLASS }, + { "Connected", WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED }, + { "Trusted", WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED }, + { "Paired", WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED }, + { "Bonded", WINEBLUETOOTH_DEVICE_PROPERTY_BONDED }, + { "Name", WINEBLUETOOTH_DEVICE_PROPERTY_NAME }, + { "LegacyPairing", WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING_HINT }, + { "ServicesResolved", WINEBLUETOOTH_DEVICE_PROPERTY_SERVICES_RESOLVED }, + { "Modalias", WINEBLUETOOTH_DEVICE_PROPERTY_PNPID }, + }; + const char *object_path = p_dbus_message_get_path( msg ); + + device = unix_name_get_or_create( object_path ); + if (!device) + { + ERR( "failed to allocate memory for device path %s\n", object_path ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + radio = bluez_device_path_get_adapter( object_path ); + if (!radio) + { + ERR( "failed to get associated radio for device path %s\n", object_path ); + unix_name_free( device ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + p_dbus_message_iter_next( &iter ); + p_dbus_message_iter_recurse( &iter, &changed_props_iter ); + while ((prop_name = bluez_next_dict_entry( &changed_props_iter, &variant ))) + { + bluez_device_prop_from_dict_entry( prop_name, &variant, FALSE, &props_changed.prop, + &props_changed.changed_props_mask, + WINEBLUETOOTH_DEVICE_ALL_PROPERTIES ); + } + p_dbus_message_iter_next( &iter ); + p_dbus_message_iter_recurse( &iter, &invalid_props_iter ); + while (p_dbus_message_iter_has_next( &invalid_props_iter )) + { + const char *invalid_prop_name = NULL; + SIZE_T i; + + p_dbus_message_iter_get_basic( &invalid_props_iter, &invalid_prop_name ); + assert( invalid_prop_name != NULL ); + for (i = 0; i < ARRAY_SIZE( device_props_mask_map ); i++) + { + if (strcmp(device_props_mask_map[i].prop_str, invalid_prop_name) == 0) + props_changed.invalid_props_mask |= device_props_mask_map[i].prop_mask; + } + p_dbus_message_iter_next( &invalid_props_iter ); + } + + TRACE( "Properties changed for device %s radio %s, changed %#x, invalidated %#x\n", + debugstr_a( device->str ), debugstr_a( radio->str ), + props_changed.changed_props_mask, props_changed.invalid_props_mask ); + if ( props_changed.invalid_props_mask != 0 ) + { + struct bluetooth_get_device_props_by_path_params params = {0}; + union winebluetooth_watcher_event_data event = { .device_props_changed = props_changed }; + NTSTATUS status; + + params.device_path = device; + status = bluez_device_get_props_by_path_async( conn, ¶ms, &pending_call, NULL, + NULL ); + if (status != STATUS_SUCCESS) + { + ERR( "Failed to create async call to get device properties: %#x\n", (int)status ); + unix_name_free( radio ); + unix_name_free( device ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + if ( !bluez_event_list_queue_new_event_with_call( + event_list, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED, event, + pending_call, bluez_filter_device_props_changed_callback ) ) + { + unix_name_free( device ); + unix_name_free( radio ); + p_dbus_pending_call_cancel( pending_call ); + p_dbus_pending_call_unref( pending_call ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + else + { + union winebluetooth_watcher_event_data event = { .device_props_changed = props_changed }; + if (!bluez_event_list_queue_new_event( + event_list, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED, + event )) + { + unix_name_free( device ); + unix_name_free( radio ); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + } + }
final_count = list_count( event_list ); assert( final_count >= init_count ); @@ -621,6 +1151,7 @@ void bluez_watcher_close( void *connection, void *ctx ) struct bluez_init_entry { union { + struct winebluetooth_watcher_event_device_added device; struct winebluetooth_watcher_event_radio_added radio; } object; struct list entry; @@ -671,6 +1202,91 @@ static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct lis init_device->object.radio.radio.handle ); break; } + else if (!strcmp( iface, BLUEZ_INTERFACE_DEVICE )) + { + BOOL bredr = FALSE, le = FALSE; + const char *prop_name; + DBusMessageIter variant; + struct bluez_init_entry *init_device = calloc( 1, sizeof( *init_device ) ); + struct unix_name *device_name; + if ( !init_device ) + { + status = STATUS_NO_MEMORY; + goto done; + } + + init_device->object.device.device.handle = device_name = unix_name_get_or_create( path ); + if ( !device_name ) + { + free( init_device ); + status = STATUS_NO_MEMORY; + goto done; + } + + while((prop_name = bluez_next_dict_entry( &prop_iter, &variant ))) + { + if (!strcmp( prop_name, "Adapter" ) + && p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_OBJECT_PATH) + { + const char *path; + p_dbus_message_iter_get_basic( &variant, &path ); + init_device->object.device.radio.handle = unix_name_get_or_create( path ); + if (!init_device->object.device.radio.handle) + { + unix_name_free( device_name ); + free( init_device ); + status = STATUS_NO_MEMORY; + goto done; + } + continue; + } + bluez_device_prop_from_dict_entry( prop_name, &variant, TRUE, + &init_device->object.device.props, + &init_device->object.device.known_props_mask, + WINEBLUETOOTH_DEVICE_ALL_PROPERTIES ); + } + + if ( init_device->object.device.props.address_type == + WINEBLUETOOTH_DEVICE_ADDRESS_TYPE_RANDOM ) + le = TRUE; + else if (init_device->object.device.props.legacy_pairing_hint) + bredr = TRUE; + else + { + /* TODO: Devices with AddressType == "public" can be dual-mode or + * LE-only radios. However, BlueZ does not seem to have any way + * to determine the underlying connection type of the device. For now, + * we'll treat the device as being connected over both BREDR and LE.*/ + FIXME( "Could not determine the connection type for device %s, assuming it's " + "both BR/EDR and LE\n", + path ); + bredr = le = TRUE; + } + assert( le || bredr ); + + init_device->object.device.connection_type = + le ? ( bredr ? WINEBLUETOOTH_DEVICE_CONNECTION_TYPE_DUAL + : WINEBLUETOOTH_DEVICE_CONNECTION_TYPE_LE ) + : WINEBLUETOOTH_DEVICE_CONNECTION_TYPE_LE; + if (le) + { + TRACE( "Found BlueZ LE %s object '%s': (device=%p, radio=%p)\n", + BLUEZ_INTERFACE_DEVICE, device_name->str, + init_device->object.device.device.handle, + init_device->object.device.radio.handle ); + + } + if (bredr) + { + TRACE( "Found BlueZ BR/EDR LE %s object '%s' (device=%p, radio=%p)\n", + BLUEZ_INTERFACE_DEVICE, device_name->str, + init_device->object.device.device.handle, + init_device->object.device.radio.handle ); + } + list_add_tail( device_list, &init_device->entry ); + + break; + } } }
@@ -718,6 +1334,9 @@ NTSTATUS bluez_dbus_loop( void *c, void *watcher, { struct bluez_watcher_event *bluez_event;
+ /* The first two conditionals can probably be nested within a single boolean like + * 'init_objects_enumerated', but I have decided to not do that for now to keep this state + * machine simple to understand. */ if (!list_empty( &watcher_ctx->initial_radio_list )) { struct bluez_init_entry *radio = @@ -731,6 +1350,19 @@ NTSTATUS bluez_dbus_loop( void *c, void *watcher, p_dbus_connection_unref( connection ); return STATUS_PENDING; } + else if (!list_empty( &watcher_ctx->initial_device_list )) + { + struct bluez_init_entry *device = + bluez_init_entries_list_pop( &watcher_ctx->initial_device_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_DEVICE_ADDED; + watcher_event->event_data.device_added = device->object.device; + free( device ); + 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; diff --git a/dlls/winebth.sys/unixlib.c b/dlls/winebth.sys/unixlib.c index 56c51f5d76f..9deaa848975 100644 --- a/dlls/winebth.sys/unixlib.c +++ b/dlls/winebth.sys/unixlib.c @@ -139,6 +139,13 @@ static NTSTATUS bluetooth_adapter_get_unique_name( void *args ) return get_unique_name( params->adapter, params->buf, ¶ms->buf_size ); }
+static NTSTATUS bluetooth_device_free( void *args ) +{ + struct bluetooth_device_free_params *params = args; + unix_name_free( params->device ); + return STATUS_SUCCESS; +} + static NTSTATUS bluetooth_adapter_free( void *args ) { struct bluetooth_adapter_free_params *params = args; @@ -179,6 +186,8 @@ const unixlib_entry_t __wine_unix_call_funcs[] = { bluetooth_adapter_get_unique_name, bluetooth_adapter_free,
+ bluetooth_device_free, + bluetooth_watcher_init, bluetooth_watcher_close,
diff --git a/dlls/winebth.sys/unixlib.h b/dlls/winebth.sys/unixlib.h index 177faf4e855..e3e60411d3b 100644 --- a/dlls/winebth.sys/unixlib.h +++ b/dlls/winebth.sys/unixlib.h @@ -70,12 +70,20 @@ struct bluetooth_adapter_get_unique_name_params SIZE_T buf_size; };
-struct bluetooth_device_get_unique_name_params +struct bluetooth_get_device_props_by_path_params { - unix_name_t device; + unix_name_t device_path; + /* If true, get the device reported name instead of the device alias, which can be overridden by + * the user. */ + BOOL get_device_reported_name;
- char *buf; - SIZE_T buf_size; + winebluetooth_device_props_mask_t valid_props; + struct winebluetooth_device_properties props; +}; + +struct bluetooth_device_free_params +{ + unix_name_t device; };
struct bluetooth_watcher_init_params @@ -103,6 +111,8 @@ enum bluetoothapis_funcs unix_bluetooth_adapter_get_unique_name, unix_bluetooth_adapter_free,
+ unix_bluetooth_device_free, + unix_bluetooth_watcher_init, unix_bluetooth_watcher_close,
diff --git a/dlls/winebth.sys/winebluetooth.c b/dlls/winebth.sys/winebluetooth.c index 509595b5f84..cd94d3c6f78 100644 --- a/dlls/winebth.sys/winebluetooth.c +++ b/dlls/winebth.sys/winebluetooth.c @@ -64,6 +64,15 @@ void winebluetooth_radio_free( winebluetooth_radio_t radio ) UNIX_BLUETOOTH_CALL( bluetooth_adapter_free, &args ); }
+void winebluetooth_device_free( winebluetooth_device_t device ) +{ + struct bluetooth_device_free_params params = {0}; + TRACE( "(%p)\n", (void *)device.handle ); + + params.device = device.handle; + UNIX_BLUETOOTH_CALL( bluetooth_device_free, ¶ms ); +} + NTSTATUS winebluetooth_watcher_init( winebluetooth_watcher_t *watcher ) { struct bluetooth_watcher_init_params args = { 0 }; diff --git a/dlls/winebth.sys/winebluetooth.h b/dlls/winebth.sys/winebluetooth.h index 0cf90620ba8..97d978b9e0c 100644 --- a/dlls/winebth.sys/winebluetooth.h +++ b/dlls/winebth.sys/winebluetooth.h @@ -80,6 +80,53 @@ struct winebluetooth_radio_properties struct winebluetooth_bluetooth_version version; };
+enum winebluetooth_device_address_type +{ + WINEBLUETOOTH_DEVICE_ADDRESS_TYPE_UNKNOWN, + WINEBLUETOOTH_DEVICE_ADDRESS_TYPE_RANDOM, + WINEBLUETOOTH_DEVICE_ADDRESS_TYPE_PUBLIC +}; + +struct winebluetooth_device_pnpid +{ + BYTE vid_source; + UINT16 vid; + UINT16 pid; + UINT16 product_version; +}; + +struct winebluetooth_device_properties +{ + BLUETOOTH_ADDRESS address; + enum winebluetooth_device_address_type address_type; + ULONG class; + BOOL connected; + BOOL trusted; + BOOL paired; + BOOL bonded; + char name[BLUETOOTH_MAX_NAME_SIZE]; + BOOL legacy_pairing_hint; + BOOL services_resolved; + struct winebluetooth_device_pnpid pnpid; +}; + +enum winebluetooth_discovery_transport +{ + WINEBLUETOOTH_DISCOVERY_TRANSPORT_AUTO, + WINEBLUETOOTH_DISCOVERY_TRANSPORT_BR_EDR, + WINEBLUETOOTH_DISCOVERY_TRANSPORT_LE +}; + +typedef struct +{ +#ifdef WINE_UNIX_LIB + void *handle; +#else + UINT_PTR handle; +#endif +} winebluetooth_device_t; + + NTSTATUS winebluetooth_radio_get_unique_name( winebluetooth_radio_t radio, char *name, SIZE_T *size ); void winebluetooth_radio_free( winebluetooth_radio_t radio ); @@ -88,6 +135,13 @@ static inline BOOL winebluetooth_radio_equal( winebluetooth_radio_t r1, wineblue return r1.handle == r2.handle; }
+void winebluetooth_device_free( winebluetooth_device_t device ); +static inline BOOL winebluetooth_device_equal( winebluetooth_device_t d1, + winebluetooth_device_t d2 ) +{ + return d1.handle == d2.handle; +} + typedef struct { #ifdef WINE_UNIX_LIB @@ -102,6 +156,63 @@ enum winebluetooth_watcher_event_type BLUETOOTH_WATCHER_EVENT_TYPE_NONE, BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED, + BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_ADDED, + BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_REMOVED, + BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED, +}; + +#define WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS (1) +#define WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS_TYPE (1 << 2) +#define WINEBLUETOOTH_DEVICE_PROPERTY_CLASS (1 << 3) +#define WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED (1 << 4) +#define WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED (1 << 5) +#define WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED (1 << 6) +#define WINEBLUETOOTH_DEVICE_PROPERTY_BONDED (1 << 7) +#define WINEBLUETOOTH_DEVICE_PROPERTY_NAME (1 << 8) +#define WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING_HINT (1 << 9) +#define WINEBLUETOOTH_DEVICE_PROPERTY_SERVICES_RESOLVED (1 << 10) +#define WINEBLUETOOTH_DEVICE_PROPERTY_PNPID (1 << 11) + +#define WINEBLUETOOTH_DEVICE_ALL_PROPERTIES \ + ( WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS | WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS_TYPE | \ + WINEBLUETOOTH_DEVICE_PROPERTY_CLASS | WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED | \ + WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED | WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED | \ + WINEBLUETOOTH_DEVICE_PROPERTY_BONDED | WINEBLUETOOTH_DEVICE_PROPERTY_NAME | \ + WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING_HINT | \ + WINEBLUETOOTH_DEVICE_PROPERTY_SERVICES_RESOLVED | WINEBLUETOOTH_DEVICE_PROPERTY_PNPID ) + +enum winebluetooth_device_connection_type +{ + WINEBLUETOOTH_DEVICE_CONNECTION_TYPE_BREDR, + WINEBLUETOOTH_DEVICE_CONNECTION_TYPE_LE, + WINEBLUETOOTH_DEVICE_CONNECTION_TYPE_DUAL, +}; + +typedef UINT16 winebluetooth_device_props_mask_t; + +struct winebluetooth_watcher_event_device_added +{ + winebluetooth_device_props_mask_t known_props_mask; + struct winebluetooth_device_properties props; + enum winebluetooth_device_connection_type connection_type; + winebluetooth_device_t device; + winebluetooth_radio_t radio; +}; + +struct winebluetooth_watcher_event_device_removed +{ + winebluetooth_radio_t radio; + winebluetooth_device_t device; +}; + +struct winebluetooth_watcher_event_device_props_changed +{ + winebluetooth_device_props_mask_t changed_props_mask; + struct winebluetooth_device_properties prop; + + winebluetooth_device_props_mask_t invalid_props_mask; + winebluetooth_device_t device; + winebluetooth_radio_t radio; };
struct winebluetooth_watcher_event_radio_added @@ -115,6 +226,9 @@ union winebluetooth_watcher_event_data { struct winebluetooth_watcher_event_radio_added radio_added; winebluetooth_radio_t radio_removed; + struct winebluetooth_watcher_event_device_added device_added; + struct winebluetooth_watcher_event_device_removed device_removed; + struct winebluetooth_watcher_event_device_props_changed device_props_changed; };
struct winebluetooth_watcher_event diff --git a/dlls/winebth.sys/winebth.c b/dlls/winebth.sys/winebth.c index 48d7da23308..49688a18b60 100644 --- a/dlls/winebth.sys/winebth.c +++ b/dlls/winebth.sys/winebth.c @@ -22,14 +22,18 @@ #include <stdlib.h> #include <assert.h>
+#define WINE_BTH_EXTENSIONS #include <ntstatus.h> #define WIN32_NO_STATUS #include <windef.h> #include <winbase.h> #include <winternl.h> +#include <bthsdpdef.h> +#include <bluetoothapis.h> #include <initguid.h> #include <devpkey.h> #include <bthdef.h> +#include <bthioctl.h> #include <winioctl.h> #include <ddk/wdm.h> #include <ddk/bthguid.h> @@ -79,6 +83,19 @@ struct bluetooth_radio LIST_ENTRY irp_list; };
+static NTSTATUS WINAPI dispatch_bluetooth(DEVICE_OBJECT *device, IRP *irp ) +{ + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation( irp ); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + NTSTATUS status = irp->IoStatus.Status; + + TRACE( "device %p irp %p code %s\n", device, irp, debugstr_IoControlCode( code ) ); + + irp->IoStatus.Status = status; + IoCompleteRequest( irp, IO_NO_INCREMENT ); + return status; +} + void WINAPIV append_id(struct string_buffer *buffer, const WCHAR *format, ...) { va_list args; @@ -150,6 +167,7 @@ static NTSTATUS radio_get_hw_name_w( winebluetooth_radio_t radio, WCHAR **name ) free( name_a ); return STATUS_SUCCESS; } + static void add_bluetooth_radio( struct winebluetooth_watcher_event_radio_added event ) { struct bluetooth_radio *device; @@ -193,6 +211,7 @@ static void add_bluetooth_radio( struct winebluetooth_watcher_event_radio_added list_add_tail( &device_list, &device->entry ); LeaveCriticalSection( &device_list_cs );
+ bthenum_create_fdo_bus( device_obj, &device->device_fdo_bus ); IoInvalidateDeviceRelations( bus_pdo, BusRelations ); }
@@ -218,6 +237,55 @@ static void remove_bluetooth_radio( winebluetooth_radio_t radio ) winebluetooth_radio_free( radio ); }
+static void add_remote_bluetooth_device( struct winebluetooth_watcher_event_device_added event ) +{ + struct bluetooth_radio *device; + + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY( device, &device_list, struct bluetooth_radio, entry ) + { + if (winebluetooth_radio_equal( device->radio, event.radio )) + { + bthenum_device_added( device->device_fdo_bus, event ); + break; + } + } + LeaveCriticalSection( &device_list_cs ); +} + +static void +remove_remote_bluetooth_device( struct winebluetooth_watcher_event_device_removed event ) +{ + struct bluetooth_radio *device; + + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY( device, &device_list, struct bluetooth_radio, entry ) + { + if (winebluetooth_radio_equal( device->radio, event.radio )) + { + bthenum_device_removed( device->device_fdo_bus, event.device ); + break; + } + } + LeaveCriticalSection( &device_list_cs ); +} + +static void remote_bluetooth_device_props_changed( + struct winebluetooth_watcher_event_device_props_changed event ) +{ + struct bluetooth_radio *device; + EnterCriticalSection( &device_list_cs ); + LIST_FOR_EACH_ENTRY(device, &device_list, struct bluetooth_radio, entry) + { + if (winebluetooth_radio_equal( device->radio, event.radio )) + { + bthenum_device_props_changed( device->device_fdo_bus, event ); + break; + } + } + LeaveCriticalSection( &device_list_cs ); +} + static void remove_pending_irps(struct bluetooth_radio *device) { LIST_ENTRY *entry; @@ -255,6 +323,16 @@ static DWORD CALLBACK bluetooth_event_loop_thread_proc( void *arg ) case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED: remove_bluetooth_radio( event->event_data.radio_removed ); break; + case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_ADDED: + add_remote_bluetooth_device( event->event_data.device_added ); + break; + case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_REMOVED: + remove_remote_bluetooth_device( event->event_data.device_removed ); + break; + case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED: + remote_bluetooth_device_props_changed( + event->event_data.device_props_changed ); + break; default: FIXME( "Unknown bluetooth watcher event code: %#x\n", event->event_type ); } @@ -541,6 +619,8 @@ static void WINAPI driver_unload( DRIVER_OBJECT *driver ) NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path ) { NTSTATUS status; + UNICODE_STRING driver_bthenum = RTL_CONSTANT_STRING( L"\Driver\BthEnum" ); + TRACE( "(%p, %s)\n", driver, debugstr_w( path->Buffer ) );
status = winebluetooth_init(); @@ -559,5 +639,7 @@ NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *path ) driver->DriverExtension->AddDevice = driver_add_device; driver->DriverUnload = driver_unload; driver->MajorFunction[IRP_MJ_PNP] = bluetooth_pnp; + driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = dispatch_bluetooth; + IoCreateDriver( &driver_bthenum, bthenum_entry_point ); return STATUS_SUCCESS; } diff --git a/dlls/winebth.sys/winebth_priv.h b/dlls/winebth.sys/winebth_priv.h index e73586fead3..7ea7f03108d 100644 --- a/dlls/winebth.sys/winebth_priv.h +++ b/dlls/winebth.sys/winebth_priv.h @@ -21,6 +21,8 @@ #ifndef __WINE_WINEBTH_WINEBTH_H_ #define __WINE_WINEBTH_WINEBTH_H_
+#include "winebluetooth.h" + #ifdef __ASM_USE_FASTCALL_WRAPPER extern void * WINAPI wrap_fastcall_func1(void *func, const void *a); __ASM_STDCALL_FUNC(wrap_fastcall_func1, 8, @@ -32,11 +34,24 @@ __ASM_STDCALL_FUNC(wrap_fastcall_func1, 8, #else #define call_fastcall_func1(func,a) func(a) #endif +extern NTSTATUS WINAPI bthenum_device_added( + DEVICE_OBJECT *fdo_bus, struct winebluetooth_watcher_event_device_added event ); +extern NTSTATUS WINAPI bthenum_device_removed( DEVICE_OBJECT *fdo_bus, + const winebluetooth_device_t device_handle ); +extern NTSTATUS WINAPI bthenum_device_props_changed( + DEVICE_OBJECT *fdo_bus, struct winebluetooth_watcher_event_device_props_changed event ); +extern NTSTATUS WINAPI + bthenum_create_fdo_bus( DEVICE_OBJECT *pdo, DEVICE_OBJECT **fdo_bus ); +extern NTSTATUS WINAPI bthenum_entry_point( DRIVER_OBJECT *driver, UNICODE_STRING *path ); + struct string_buffer { WCHAR *string; size_t len; }; +extern void WINAPIV append_id( struct string_buffer *buffer, const WCHAR *format, ... ) + __WINE_PRINTF_ATTR( 2, 3 ); +
#define XX(i) case (i): return #i
@@ -87,7 +102,57 @@ static inline const char *debugstr_minor_function_code( UCHAR code ) return wine_dbg_sprintf( "(unknown %#x)", code ); } } + +static inline const char *debugstr_IoControlCode( ULONG code ) +{ + switch (code) + { + XX(IOCTL_BTH_DISCONNECT_DEVICE); + XX(IOCTL_BTH_GET_DEVICE_INFO); + XX(IOCTL_BTH_GET_LOCAL_INFO); + XX(IOCTL_BTH_GET_RADIO_INFO); + XX(IOCTL_BTH_SDP_ATTRIBUTE_SEARCH); + XX(IOCTL_BTH_SDP_CONNECT); + XX(IOCTL_BTH_SDP_DISCONNECT); + XX(IOCTL_BTH_SDP_REMOVE_RECORD); + XX(IOCTL_BTH_SDP_SERVICE_ATTRIBUTE_SEARCH); + XX(IOCTL_BTH_SDP_SERVICE_SEARCH); + XX(IOCTL_BTH_SDP_SUBMIT_RECORD); + XX(IOCTL_BTH_SDP_SUBMIT_RECORD_WITH_INFO); + default: + return wine_dbg_sprintf( "(unknown %#x)", code ); + } +} + #undef XX
+static inline const char *debugstr_radio_flags( UINT32 flags ) +{ + const UINT32 all_flags = LOCAL_RADIO_CONNECTABLE | LOCAL_RADIO_DISCOVERABLE; + static const UINT32 masks[] = {LOCAL_RADIO_CONNECTABLE, LOCAL_RADIO_DISCOVERABLE}; + static const char *masks_str[] = {"LOCAL_RADIO_CONNECTABLE", "LOCAL_RADIO_DISCOVERABLE"}; + char buf[80] = ""; + BOOL prev = FALSE; + SIZE_T i; + + for (i = 0; i < ARRAY_SIZE( masks ); i++) + { + if (flags & masks[i]) + { + if (prev) strcat( buf, "|" ); + strcat( buf, masks_str[i] ); + prev = TRUE; + } + } + + flags &= ~all_flags; + if (flags) + { + return wine_dbg_sprintf( "{%s (unknown %#x)}", flags ); + } + + return wine_dbg_sprintf("{%s}", buf); +} +
#endif /* __WINE_WINEBTH_WINEBTH_H_ */ diff --git a/include/Makefile.in b/include/Makefile.in index a62a24d327f..76494191f51 100644 --- a/include/Makefile.in +++ b/include/Makefile.in @@ -59,6 +59,7 @@ SOURCES = \ bitsmsg.h \ bluetoothapis.h \ bthdef.h \ + bthioctl.h \ bthsdpdef.h \ cderr.h \ cdosys.idl \ diff --git a/include/bthdef.h b/include/bthdef.h index 9b65cc8d48f..d431de929c9 100644 --- a/include/bthdef.h +++ b/include/bthdef.h @@ -23,6 +23,29 @@ extern "C" { #endif
+typedef ULONG BTH_COD; +#define BTH_MAX_NAME_SIZE (248) +#define MAX_UUIDS_IN_QUERY (12) + +#define BDIF_ADDRESS 0x00000001 +#define BDIF_COD 0x00000002 +#define BDIF_NAME 0x00000004 +#define BDIF_PAIRED 0x00000008 +#define BDIF_PERSONAL 0x00000010 +#define BDIF_CONNECTED 0x00000020 + +#define BDIF_SSP_SUPPORTED 0x00000100 +#define BDIF_SSP_PAIRED 0x00000200 +#define BDIF_SSP_MITM_PROTECTED 0x00000200 + +typedef struct _BTH_DEVICE_INFO +{ + ULONG flags; + BTH_ADDR address; + BTH_COD classOfDevice; + CHAR name[BTH_MAX_NAME_SIZE]; +} BTH_DEVICE_INFO, *PBTH_DEVICE_INFO; + DEFINE_GUID( GUID_BTHPORT_DEVICE_INTERFACE, 0x850302a, 0xb344, 0x4fda, 0x9b, 0xe9, 0x90, 0x57, 0x6b, 0x8d, 0x46, 0xf0 );
diff --git a/include/bthioctl.h b/include/bthioctl.h new file mode 100644 index 00000000000..773ea54f154 --- /dev/null +++ b/include/bthioctl.h @@ -0,0 +1,71 @@ +/* + * IOCTL definitions for interfacing with winebth.sys + * + * Copyright 2024 Vibhav Pant + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + */ + +#ifndef __WINE_BTHIOCTL_H_ +#define __WINE_BTHIOCTL_H_ + +#include <winioctl.h> + +#define IOCTL_BTH_GET_LOCAL_INFO CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x00, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_GET_RADIO_INFO CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x01, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_GET_DEVICE_INFO CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x02, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_DISCONNECT_DEVICE CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x03, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_SDP_CONNECT CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x80, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_SDP_DISCONNECT CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x81, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_SDP_SERVICE_SEARCH CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x82, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_SDP_ATTRIBUTE_SEARCH CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x83, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_SDP_SERVICE_ATTRIBUTE_SEARCH CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x84, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_SDP_SUBMIT_RECORD CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x85, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_SDP_REMOVE_RECORD CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x86, METHOD_BUFFERED, FILE_ANY_ACCESS) +#define IOCTL_BTH_SDP_SUBMIT_RECORD_WITH_INFO CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x87, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_INTERNAL_BTH_SUBMIT_BRB CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x00, METHOD_NEITHER, FILE_ANY_ACCESS) +#define IOCTL_INTERNAL_BTHENUM_GET_ENUMINFO CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x01, METHOD_NEITHER, FILE_ANY_ACCESS) +#define IOCTL_INTERNAL_BTHENUM_GET_DEVINFO CTL_CODE(FILE_DEVICE_BLUETOOTH, 0x02, METHOD_NEITHER, FILE_ANY_ACCESS) + +#define LOCAL_RADIO_DISCOVERABLE (0x0001) +#define LOCAL_RADIO_CONNECTABLE (0x0002) + +typedef struct _BTH_RADIO_INFO +{ + ULONGLONG lmpSupportedFeatures; + USHORT mfg; + USHORT lmpSubversion; + UCHAR lmpVersion; +} BTH_RADIO_INFO, *PBTH_RADIO_INFO; + +typedef struct _BTH_LOCAL_RADIO_INFO +{ + BTH_DEVICE_INFO localInfo; + ULONG flags; + USHORT hciRevision; + UCHAR hciVerison; + BTH_RADIO_INFO radioInfo; +} BTH_LOCAL_RADIO_INFO, *PBTH_LOCAL_RADIO_INFO; + +typedef struct _BTH_DEVICE_INFO_LIST +{ + ULONG numOfDevices; + BTH_DEVICE_INFO deviceList[1]; +} BTH_DEVICE_INFO_LIST, *PBTH_DEVICE_INFO_LIST; + +#endif /* __WINE_BTHIOCTL_H_ */ + diff --git a/include/ddk/bthguid.h b/include/ddk/bthguid.h index 067e9cac59a..ab6e6a7e112 100644 --- a/include/ddk/bthguid.h +++ b/include/ddk/bthguid.h @@ -27,6 +27,26 @@ DEFINE_GUID( GUID_BLUETOOTH_RADIO_INTERFACE, 0x92383b0e, 0xf90e, 0x4ac9, 0x8d, 0x44, 0x8c, 0x2d, 0x0d, 0x0e, 0xbd, 0xa2 );
+DEFINE_GUID( GUID_BTH_DEVICE_INTERFACE, 0x00f40965, 0xe89d, 0x4487, 0x98, 0x90, 0x87, 0xc3, 0xab, + 0xb2, 0x11, 0xf4 ); + +DEFINE_GUID( GUID_BLUETOOTHLE_DEVICE_INTERFACE, 0x781aee18, 0x7733, 0x4ce4, 0xad, 0xd0, 0x91, 0xf4, + 0x1c, 0x67, 0xb5, 0x92 ); + +#define DEFINE_BTH_DEVICE_DEVPROPKEY( d, i ) \ + DEFINE_DEVPROPKEY( DEVPKEY_Bluetooth_##d, 0x2bd67d8b, 0x8beb, 0x48d5, 0x87, 0xe0, 0x6c, 0xda, \ + 0x34, 0x28, 0x04, 0x0a, ( i ) ) + +DEFINE_BTH_DEVICE_DEVPROPKEY( DeviceAddress, 1 ); /* DEVPROP_TYPE_STRING */ +DEFINE_BTH_DEVICE_DEVPROPKEY( ServiceGUID, 2 ); /* DEVPROP_TYPE_GUID */ +DEFINE_BTH_DEVICE_DEVPROPKEY( DeviceFlags, 3 ); /* DEVPROP_TYPE_UINT32 */ +DEFINE_BTH_DEVICE_DEVPROPKEY( DeviceManufacturer, 4 ); /* DEVPROP_TYPE_UINT32 */ +DEFINE_BTH_DEVICE_DEVPROPKEY( DeviceVIDSource, 6 ); /* DEVPROP_TYPE_BYTE */ +DEFINE_BTH_DEVICE_DEVPROPKEY( DeviceVID, 7 ); /* DEVPROP_TYPE_UINT16 */ +DEFINE_BTH_DEVICE_DEVPROPKEY( DevicePID, 8 ); /* DEVPROP_TYPE_UINT16 */ +DEFINE_BTH_DEVICE_DEVPROPKEY( DeviceProductVersion, 9 ); /* DEVPROP_TYPE_UINT16 */ +DEFINE_BTH_DEVICE_DEVPROPKEY( ClassOfDevice, 10 ); /* DEVPROP_TYPE_UINT32 */ + #define DEFINE_BTH_RADIO_DEVPROPKEY( d, i ) \ DEFINE_DEVPROPKEY( DEVPKEY_BluetoothRadio_##d, 0xa92f26ca, 0xeda7, 0x4b1d, 0x9d, 0xb2, 0x27, \ 0xb6, 0x8a, 0xa5, 0xa2, 0xeb, ( i ) ) @@ -44,10 +64,17 @@ DEFINE_BTH_RADIO_DEVPROPKEY( LELocalSupportedFeatures, 22 ); /* DEVPROP_TY DEFINE_GUID( GUID_BLUETOOTH_WINE_AUTH_REQUEST, 0x1a188a9b, 0x2d33, 0x4269, 0x85, 0xea, 0xd5, 0x3d, 0x69, 0x18, 0xdc, 0xeb );
+#define DEFINE_WINEBTH_DEVICE_DEVPROPKEY( d, i ) \ + DEFINE_DEVPROPKEY( DEVPKEY_WineBluetooth_Device_##d, 0xa919542e, 0xf061, 0x4237, 0x8d, 0x61, \ + 0xe2, 0x3e, 0x73, 0x44, 0x58, 0x6f, (i) ) + #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) )
+/* Name, as reported by the device. DEVPROP_TYPE_STRING */ +DEFINE_WINEBTH_DEVICE_DEVPROPKEY( Name, 1 ); + /* Authentication I/O capability as currently advertised by the radio. DEVPROP_TYPE_BYTE */ DEFINE_WINEBTH_RADIO_DEVPROPKEY( Capability, 1 ); #endif