mirror of
https://gitlab.winehq.org/wine/wine.git
synced 2025-08-29 02:33:58 +02:00
2538 lines
103 KiB
C
2538 lines
103 KiB
C
/*
|
|
* Support for communicating with BlueZ over DBus.
|
|
*
|
|
* Copyright 2024 Vibhav Pant
|
|
* Copyright 2025 Vibhav Pant
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* 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>
|
|
#include <semaphore.h>
|
|
#include <errno.h>
|
|
#include <string.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 <bthledef.h>
|
|
#include <bthdef.h>
|
|
#include <wine/winebth.h>
|
|
|
|
#include <wine/debug.h>
|
|
|
|
#include "winebth_priv.h"
|
|
|
|
#include "unixlib.h"
|
|
#include "unixlib_priv.h"
|
|
#include "dbus.h"
|
|
|
|
#ifdef SONAME_LIBDBUS_1
|
|
|
|
/* BlueZ is the userspace Bluetooth management daemon for Linux systems, which provides an
|
|
* object-based API for interacting with Bluetooth adapters and devices on top of DBus. Every DBus
|
|
* object has one or more "interfaces", with each interface have its own set of methods and signals
|
|
* that we can call or listen for, respectively. Objects are identified by their "path", which
|
|
* provide a way to organize objects under a hierarchy. For instance, a bluetooth adapter/radio will
|
|
* have a path of the form "/org/bluez/hciX", with any remote devices discovered by or bonded to it
|
|
* having the path "/org/bluez/hciX/XX:XX:XX:XX:XX:XX". Similarly, any LE GATT services,
|
|
* characteristics or descriptors specific to this remote device are represented as objects under
|
|
* the device's path. Among BlueZ's own interfaces, the other important interfaces we rely on are:
|
|
*
|
|
* org.freedesktop.DBus.Properties: Allows us to read and (optionally) modify properties specific
|
|
* to an object's interface using the "Get" and "Set" methods. Additionally, some objects may
|
|
* broadcast a "PropertiesChanged" signal if any of their properties change, which gets used by
|
|
* bluez_filter to let the driver know to appropriately update the equivalent PDO. The "GetAll"
|
|
* method provides a convenient way to get all properties for an interface.
|
|
*
|
|
* org.freedesktop.DBus.ObjectManager: This contains the "GetManagedObjects" method, that returns a
|
|
* list of all known objects under the object's path namespace, with the interfaces they implement
|
|
* (and any properties those interfaces might have). This is used to build a list of initially known
|
|
* devices in bluez_build_initial_device_lists. The "InterfacesAdded" and "InterfacesRemoved"
|
|
* signals are used to detect when a bluetooth adapter or device has been addeed/removed in
|
|
* bluez_filter, which we relay to the bluetooth driver to add/remove the corresponding PDO.
|
|
*/
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL( winebth );
|
|
WINE_DECLARE_DEBUG_CHANNEL( dbus );
|
|
|
|
const int bluez_timeout = -1;
|
|
|
|
#define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager"
|
|
#define DBUS_OBJECTMANAGER_SIGNAL_INTERFACESADDED "InterfacesAdded"
|
|
#define DBUS_OBJECTMANAGER_SIGNAL_INTERFACESREMOVED "InterfacesRemoved"
|
|
#define DBUS_PROPERTIES_SIGNAL_PROPERTIESCHANGED "PropertiesChanged"
|
|
|
|
#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 BLUEZ_INTERFACE_DEVICE "org.bluez.Device1"
|
|
#define BLUEZ_INTERFACE_AGENT_MANAGER "org.bluez.AgentManager1"
|
|
#define BLUEZ_INTERFACE_AGENT "org.bluez.Agent1"
|
|
#define BLUEZ_INTERFACE_GATT_SERVICE "org.bluez.GattService1"
|
|
#define BLUEZ_INTERFACE_GATT_CHARACTERISTICS "org.bluez.GattCharacteristic1"
|
|
|
|
#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;
|
|
}
|
|
|
|
/* Adds an entry to a dict iterator of type {sv} */
|
|
static BOOL bluez_variant_dict_add_entry( DBusMessageIter *dict, const char *key, int value_type,
|
|
const char *value_type_str, const void *value )
|
|
{
|
|
DBusMessageIter entry, variant;
|
|
|
|
if (!p_dbus_message_iter_open_container( dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry )) return FALSE;
|
|
if (!p_dbus_message_iter_append_basic( &entry, DBUS_TYPE_STRING, &key ))
|
|
{
|
|
p_dbus_message_iter_abandon_container( dict, &entry );
|
|
return FALSE;
|
|
}
|
|
|
|
if (!p_dbus_message_iter_open_container( &entry, DBUS_TYPE_VARIANT, value_type_str, &variant ))
|
|
{
|
|
p_dbus_message_iter_abandon_container( dict, &entry );
|
|
return FALSE;
|
|
}
|
|
if (!p_dbus_message_iter_append_basic( &variant, value_type, value ))
|
|
{
|
|
p_dbus_message_iter_abandon_container( &entry, &variant );
|
|
p_dbus_message_iter_abandon_container( dict, &entry );
|
|
return FALSE;
|
|
}
|
|
if (!p_dbus_message_iter_close_container( &entry, &variant ))
|
|
{
|
|
p_dbus_message_iter_abandon_container( dict, &entry );
|
|
return FALSE;
|
|
}
|
|
|
|
return !!p_dbus_message_iter_close_container( dict, &entry );
|
|
}
|
|
|
|
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 BOOL parse_uuid( GUID *guid, const char *str )
|
|
{
|
|
int ret;
|
|
if (strlen( str ) != 36)
|
|
return FALSE;
|
|
ret = sscanf( str, "%08x-%hx-%hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", &guid->Data1, &guid->Data2, &guid->Data3,
|
|
&guid->Data4[0], &guid->Data4[1], &guid->Data4[2], &guid->Data4[3], &guid->Data4[4], &guid->Data4[5],
|
|
&guid->Data4[6], &guid->Data4[7] );
|
|
if (ret != 11)
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
static void bluez_dbus_wait_for_reply_callback( DBusPendingCall *pending_call, void *wait )
|
|
{
|
|
sem_post( wait );
|
|
}
|
|
|
|
static NTSTATUS bluez_dbus_pending_call_wait( DBusPendingCall *pending_call, DBusMessage **reply, DBusError *error )
|
|
{
|
|
sem_t wait;
|
|
|
|
sem_init( &wait, 0, 0 );
|
|
if (!p_dbus_pending_call_set_notify( pending_call, bluez_dbus_wait_for_reply_callback, &wait, NULL ))
|
|
{
|
|
sem_destroy( &wait );
|
|
p_dbus_pending_call_unref( pending_call );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
for (;;)
|
|
{
|
|
int ret = sem_wait( &wait );
|
|
if (!ret)
|
|
{
|
|
*reply = p_dbus_pending_call_steal_reply( pending_call );
|
|
if (p_dbus_set_error_from_message( error, *reply ))
|
|
{
|
|
p_dbus_message_unref( *reply );
|
|
*reply = NULL;
|
|
}
|
|
p_dbus_pending_call_unref( pending_call );
|
|
sem_destroy( &wait );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
if (errno == EINTR)
|
|
continue;
|
|
|
|
ERR( "Failed to wait for DBus method reply: %s\n", debugstr_a( strerror( errno ) ) );
|
|
sem_destroy( &wait );
|
|
p_dbus_pending_call_cancel( pending_call );
|
|
p_dbus_pending_call_unref( pending_call );
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Like dbus_connection_send_with_reply_and_block, but it does not acquire a lock on the connection, instead relying on
|
|
* the main loop in bluez_dbus_loop. This is faster than send_with_reply_and_block.
|
|
* This takes ownership of the request, so there is no need to unref it. */
|
|
static NTSTATUS bluez_dbus_send_and_wait_for_reply( DBusConnection *connection, DBusMessage *request, DBusMessage **reply,
|
|
DBusError *error )
|
|
{
|
|
DBusPendingCall *pending_call;
|
|
dbus_bool_t success;
|
|
|
|
success = p_dbus_connection_send_with_reply( connection, request, &pending_call, bluez_timeout );
|
|
p_dbus_message_unref( request );
|
|
|
|
if (!success)
|
|
return STATUS_NO_MEMORY;
|
|
return bluez_dbus_pending_call_wait( pending_call, reply, error );
|
|
}
|
|
|
|
static NTSTATUS bluez_adapter_set_discovery_filter( void *connection, const char *adapter_path,
|
|
const char *transport_str )
|
|
{
|
|
DBusMessage *request, *reply;
|
|
DBusMessageIter iter, dict_iter;
|
|
DBusError error;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "(%p, %s, %s)\n", connection, debugstr_a( adapter_path ), debugstr_a( transport_str ) );
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, adapter_path, BLUEZ_INTERFACE_ADAPTER,
|
|
"SetDiscoveryFilter" );
|
|
if (!request) return STATUS_NO_MEMORY;
|
|
|
|
p_dbus_message_iter_init_append( request, &iter );
|
|
if (!p_dbus_message_iter_open_container( &iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter ))
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
if (!bluez_variant_dict_add_entry( &dict_iter, "Transport", DBUS_TYPE_STRING,
|
|
DBUS_TYPE_STRING_AS_STRING, &transport_str ))
|
|
{
|
|
p_dbus_message_iter_abandon_container( &iter, &dict_iter );
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
if (!p_dbus_message_iter_close_container( &iter, &dict_iter ))
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
p_dbus_error_init ( &error );
|
|
|
|
TRACE( "Setting discovery filter on %s to use transport %s\n", debugstr_a( adapter_path ),
|
|
debugstr_a( transport_str ) );
|
|
status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
|
|
if (status)
|
|
{
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
if (!reply)
|
|
{
|
|
WARN( "Failed to set discovery filter: %s: %s\n", debugstr_a( error.name ), debugstr_a( error.message ) );
|
|
status = bluez_dbus_error_to_ntstatus( &error );
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
p_dbus_message_unref( reply );
|
|
p_dbus_error_free( &error );
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
NTSTATUS bluez_adapter_start_discovery( void *connection, const char *adapter_path )
|
|
{
|
|
DBusMessage *request, *reply;
|
|
DBusError error;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "(%p, %s)\n", connection, debugstr_a( adapter_path ) );
|
|
|
|
status = bluez_adapter_set_discovery_filter( connection, adapter_path, "bredr" );
|
|
if (status != STATUS_SUCCESS) return status;
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, adapter_path, BLUEZ_INTERFACE_ADAPTER,
|
|
"StartDiscovery" );
|
|
if (!request) return STATUS_NO_MEMORY;
|
|
|
|
TRACE( "Starting discovery on %s\n", debugstr_a( adapter_path ) );
|
|
p_dbus_error_init( &error );
|
|
status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
|
|
if (status)
|
|
{
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
if (!reply)
|
|
{
|
|
ERR( "Failed to start discovery on adapter %s: %s: %s", debugstr_a( adapter_path ),
|
|
debugstr_a( error.message ), debugstr_a( error.name ) );
|
|
status = bluez_dbus_error_to_ntstatus( &error );
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
p_dbus_error_free( &error );
|
|
p_dbus_message_unref( reply );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS bluez_adapter_stop_discovery( void *connection, const char *adapter_path )
|
|
{
|
|
DBusMessage *request, *reply;
|
|
DBusError error;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "(%p, %s)\n", connection, debugstr_a( adapter_path ) );
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, adapter_path, BLUEZ_INTERFACE_ADAPTER,
|
|
"StopDiscovery" );
|
|
if (!request) return STATUS_NO_MEMORY;
|
|
|
|
TRACE( "Stopping discovery on %s\n", debugstr_a( adapter_path ) );
|
|
p_dbus_error_init( &error );
|
|
status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
|
|
if (status)
|
|
{
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
if (!reply)
|
|
{
|
|
ERR( "Failed to stop discovery on adapter %s: %s: %s", debugstr_a( adapter_path ),
|
|
debugstr_a( error.message ), debugstr_a( error.name ) );
|
|
status = bluez_dbus_error_to_ntstatus( &error );
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
p_dbus_error_free( &error );
|
|
p_dbus_message_unref( reply );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS bluez_adapter_remove_device( void *connection, const char *adapter_path, const char *device_path )
|
|
{
|
|
DBusMessage *request, *reply = NULL;
|
|
DBusError error;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "(%p, %s, %s)\n", connection, debugstr_a( adapter_path ), debugstr_a( device_path ) );
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, adapter_path, BLUEZ_INTERFACE_ADAPTER, "RemoveDevice" );
|
|
if (!request)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
if (!p_dbus_message_append_args( request, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_INVALID ))
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
p_dbus_error_init( &error );
|
|
status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
|
|
if (status)
|
|
{
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
if (!reply)
|
|
{
|
|
ERR( "Failed to remove device %s on adapter %s: %s: %s\n", debugstr_a( device_path ),
|
|
debugstr_a( adapter_path ), debugstr_a( error.name ), debugstr_a( error.message ) );
|
|
status = bluez_dbus_error_to_ntstatus( &error );
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
p_dbus_message_unref( reply );
|
|
p_dbus_error_free( &error );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS bluez_adapter_set_prop( void *connection, struct bluetooth_adapter_set_prop_params *params )
|
|
{
|
|
DBusMessage *request, *reply;
|
|
DBusMessageIter iter, sub_iter;
|
|
DBusError error;
|
|
DBusBasicValue val;
|
|
int val_type;
|
|
static const char *adapter_iface = BLUEZ_INTERFACE_ADAPTER;
|
|
const char *prop_name;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "(%p, %p)\n", connection, params );
|
|
|
|
switch (params->prop_flag)
|
|
{
|
|
case LOCAL_RADIO_CONNECTABLE:
|
|
prop_name = "Connectable";
|
|
val.bool_val = params->prop->boolean;
|
|
val_type = DBUS_TYPE_BOOLEAN;
|
|
break;
|
|
case LOCAL_RADIO_DISCOVERABLE:
|
|
prop_name = "Discoverable";
|
|
val.bool_val = params->prop->boolean;
|
|
val_type = DBUS_TYPE_BOOLEAN;
|
|
break;
|
|
default:
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
TRACE( "Setting property %s for adapter %s\n", debugstr_a( prop_name ), debugstr_a( params->adapter->str ) );
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, params->adapter->str,
|
|
DBUS_INTERFACE_PROPERTIES, "Set" );
|
|
if (!request) return STATUS_NO_MEMORY;
|
|
|
|
p_dbus_message_iter_init_append( request, &iter );
|
|
if (!p_dbus_message_iter_append_basic( &iter, DBUS_TYPE_STRING, &adapter_iface ))
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
if (!p_dbus_message_iter_append_basic( &iter, DBUS_TYPE_STRING, &prop_name ))
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
if (!p_dbus_message_iter_open_container( &iter, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &sub_iter ))
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
if (!p_dbus_message_iter_append_basic( &sub_iter, val_type, &val ))
|
|
{
|
|
p_dbus_message_iter_abandon_container( &iter, &sub_iter );
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
if (!p_dbus_message_iter_close_container( &iter, &sub_iter ))
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
p_dbus_error_init( &error );
|
|
status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
|
|
if (status)
|
|
{
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
if (!reply)
|
|
{
|
|
ERR( "Failed to set property %s for adapter %s: %s: %s\n", debugstr_a( prop_name ),
|
|
debugstr_a( params->adapter->str ), debugstr_a( error.name ), debugstr_a( error.message ) );
|
|
status = bluez_dbus_error_to_ntstatus( &error );
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
p_dbus_error_free( &error );
|
|
p_dbus_message_unref( reply );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static void bluez_radio_prop_from_dict_entry( const char *prop_name, DBusMessageIter *variant,
|
|
struct winebluetooth_radio_properties *props,
|
|
winebluetooth_radio_props_mask_t *props_mask,
|
|
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_NAME &&
|
|
!strcmp( prop_name, "Name" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ))
|
|
{
|
|
const char *name_str;
|
|
SIZE_T len;
|
|
p_dbus_message_iter_get_basic( variant, &name_str );
|
|
len = strlen( name_str );
|
|
memcpy( props->name, name_str, min( len + 1, ARRAYSIZE( props->name )));
|
|
props->name[ARRAYSIZE( props->name ) - 1] = '\0';
|
|
*props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_NAME;
|
|
}
|
|
if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS &&
|
|
!strcmp( prop_name, "Address" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING)
|
|
{
|
|
const char *addr_str;
|
|
p_dbus_message_iter_get_basic( variant, &addr_str );
|
|
parse_mac_address( addr_str, props->address.rgBytes );
|
|
*props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS;
|
|
}
|
|
else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_CLASS &&
|
|
!strcmp( prop_name, "Class" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT32)
|
|
{
|
|
dbus_uint32_t class;
|
|
p_dbus_message_iter_get_basic( variant, &class );
|
|
props->class = class;
|
|
*props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_CLASS;
|
|
}
|
|
else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER &&
|
|
!strcmp( prop_name, "Manufacturer" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT16)
|
|
{
|
|
dbus_uint16_t manufacturer;
|
|
p_dbus_message_iter_get_basic( variant, &manufacturer );
|
|
props->manufacturer = manufacturer;
|
|
*props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER;
|
|
}
|
|
else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE &&
|
|
!strcmp( prop_name, "Connectable" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
|
|
{
|
|
dbus_bool_t connectable;
|
|
p_dbus_message_iter_get_basic( variant, &connectable );
|
|
props->connectable = connectable != 0;
|
|
*props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE;
|
|
}
|
|
else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE &&
|
|
!strcmp( prop_name, "Discoverable" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
|
|
{
|
|
dbus_bool_t discoverable;
|
|
p_dbus_message_iter_get_basic( variant, &discoverable );
|
|
props->discoverable = discoverable != 0;
|
|
*props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE;
|
|
}
|
|
else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING &&
|
|
!strcmp( prop_name, "Discovering") &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
|
|
{
|
|
dbus_bool_t discovering;
|
|
p_dbus_message_iter_get_basic( variant, &discovering );
|
|
props->discovering = discovering != 0;
|
|
*props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING;
|
|
}
|
|
else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE &&
|
|
!strcmp( prop_name, "Pairable" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
|
|
{
|
|
dbus_bool_t pairable;
|
|
p_dbus_message_iter_get_basic( variant, &pairable );
|
|
props->pairable = pairable != 0;
|
|
*props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE;
|
|
}
|
|
else if (wanted_props_mask & WINEBLUETOOTH_RADIO_PROPERTY_VERSION &&
|
|
!strcmp( prop_name, "Version" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BYTE)
|
|
{
|
|
p_dbus_message_iter_get_basic( variant, &props->version );
|
|
*props_mask |= WINEBLUETOOTH_RADIO_PROPERTY_VERSION;
|
|
}
|
|
}
|
|
|
|
static void bluez_device_prop_from_dict_entry( const char *prop_name, DBusMessageIter *variant,
|
|
struct winebluetooth_device_properties *props,
|
|
winebluetooth_device_props_mask_t *props_mask,
|
|
winebluetooth_device_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_DEVICE_PROPERTY_NAME &&
|
|
!strcmp( prop_name, "Name" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_STRING)
|
|
{
|
|
const char *name_str;
|
|
SIZE_T len;
|
|
|
|
p_dbus_message_iter_get_basic( variant, &name_str );
|
|
len = strlen( name_str );
|
|
memcpy( props->name, name_str, 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_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;
|
|
}
|
|
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;
|
|
*props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED;
|
|
}
|
|
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;
|
|
*props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED;
|
|
}
|
|
else if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING &&
|
|
!strcmp( prop_name, "LegacyPairing" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_BOOLEAN)
|
|
{
|
|
dbus_bool_t legacy;
|
|
|
|
p_dbus_message_iter_get_basic( variant, &legacy );
|
|
props->legacy_pairing = !!legacy;
|
|
*props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING;
|
|
}
|
|
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;
|
|
*props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED;
|
|
}
|
|
else if (wanted_props_mask & WINEBLUETOOTH_DEVICE_PROPERTY_CLASS &&
|
|
!strcmp( prop_name, "Class" ) &&
|
|
p_dbus_message_iter_get_arg_type( variant ) == DBUS_TYPE_UINT32)
|
|
{
|
|
p_dbus_message_iter_get_basic( variant, &props->class );
|
|
*props_mask |= WINEBLUETOOTH_DEVICE_PROPERTY_CLASS;
|
|
}
|
|
}
|
|
|
|
static NTSTATUS bluez_adapter_get_props_async( void *connection, const char *radio_object_path,
|
|
DBusPendingCall **call )
|
|
{
|
|
DBusMessage *request;
|
|
static const char *adapter_iface = BLUEZ_INTERFACE_ADAPTER;
|
|
dbus_bool_t success;
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, radio_object_path,
|
|
DBUS_INTERFACE_PROPERTIES, "GetAll" );
|
|
if (!request) return STATUS_NO_MEMORY;
|
|
|
|
p_dbus_message_append_args( request, DBUS_TYPE_STRING, &adapter_iface, DBUS_TYPE_INVALID );
|
|
|
|
success = p_dbus_connection_send_with_reply( connection, request, call, bluez_timeout );
|
|
p_dbus_message_unref( request );
|
|
if (!success)
|
|
return STATUS_NO_MEMORY;
|
|
if (!*call)
|
|
return STATUS_INTERNAL_ERROR;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS bluez_device_get_props_by_path_async( DBusConnection *connection,
|
|
const char *device_path,
|
|
DBusPendingCall **pending_call )
|
|
{
|
|
DBusMessage *request;
|
|
static const char *adapter_iface = BLUEZ_INTERFACE_DEVICE;
|
|
dbus_bool_t ret;
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, device_path,
|
|
DBUS_INTERFACE_PROPERTIES, "GetAll" );
|
|
if (!request) return STATUS_NO_MEMORY;
|
|
|
|
p_dbus_message_append_args( request, DBUS_TYPE_STRING, &adapter_iface, DBUS_TYPE_INVALID );
|
|
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)
|
|
return STATUS_INTERNAL_ERROR;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
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_init_entry */
|
|
struct list initial_gatt_service_list;
|
|
/* struct bluez_init_entry */
|
|
struct list initial_gatt_chars_list;
|
|
|
|
/* struct bluez_watcher_event */
|
|
struct list event_list;
|
|
};
|
|
|
|
struct bluez_init_entry
|
|
{
|
|
union {
|
|
struct winebluetooth_watcher_event_radio_added radio;
|
|
struct winebluetooth_watcher_event_device_added device;
|
|
struct winebluetooth_watcher_event_gatt_service_added service;
|
|
struct winebluetooth_watcher_event_gatt_characteristic_added characteristic;
|
|
} object;
|
|
struct list entry;
|
|
};
|
|
|
|
/* The status of a pairing session initiated by BlueZ. */
|
|
enum bluez_pairing_session_status
|
|
{
|
|
/* No pairing session is active. */
|
|
BLUEZ_PAIRING_SESSION_NONE,
|
|
/* We have received an authentication request from BlueZ, which should be queued to the PE driver. */
|
|
BLUEZ_PAIRING_SESSION_INCOMING,
|
|
/* The authentication request has been relayed to the driver, and we're waiting for a response from userspace. */
|
|
BLUEZ_PAIRING_SESSION_PENDING_REPLY,
|
|
/* The authentication request has been cancelled by BlueZ. */
|
|
BLUEZ_PAIRING_SESSION_CANCELLED,
|
|
};
|
|
|
|
struct bluez_auth_agent_ctx
|
|
{
|
|
LONG refcnt;
|
|
|
|
pthread_mutex_t lock; /* Guards all fields below. */
|
|
enum bluez_pairing_session_status status;
|
|
struct unix_name *device;
|
|
BLUETOOTH_AUTHENTICATION_METHOD method;
|
|
UINT32 passkey;
|
|
/* The auth request we received from BlueZ. */
|
|
DBusMessage *auth_request;
|
|
DBusPreallocatedSend *preallocate_send;
|
|
DBusConnection *connection;
|
|
};
|
|
|
|
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)
|
|
{
|
|
WARN( "Failed to get system dbus connection: %s: %s\n", debugstr_a( error.name ), debugstr_a( error.message ) );
|
|
p_dbus_error_free( &error );
|
|
return NULL;
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
static struct bluez_auth_agent_ctx *bluez_auth_agent_ctx_incref( struct bluez_auth_agent_ctx *ctx )
|
|
{
|
|
InterlockedIncrement( &ctx->refcnt );
|
|
return ctx;
|
|
}
|
|
|
|
static void bluez_auth_agent_ctx_decref( struct bluez_auth_agent_ctx *ctx )
|
|
{
|
|
if (InterlockedDecrement( &ctx->refcnt ))
|
|
return;
|
|
|
|
if (ctx->status != BLUEZ_PAIRING_SESSION_NONE)
|
|
{
|
|
unix_name_free( ctx->device );
|
|
if (ctx->status != BLUEZ_PAIRING_SESSION_CANCELLED)
|
|
{
|
|
p_dbus_connection_free_preallocated_send( ctx->connection, ctx->preallocate_send );
|
|
p_dbus_message_unref( ctx->auth_request );
|
|
p_dbus_connection_unref( ctx->connection );
|
|
}
|
|
}
|
|
pthread_mutex_destroy( &ctx->lock );
|
|
free( ctx );
|
|
}
|
|
|
|
static DBusHandlerResult bluez_auth_agent_vtable_message_handler( DBusConnection *connection, DBusMessage *message,
|
|
void *data )
|
|
{
|
|
struct bluez_auth_agent_ctx *ctx = data;
|
|
DBusPreallocatedSend *prealloc_send;
|
|
DBusMessage *reply;
|
|
|
|
TRACE_(dbus)( "(%s, %s, %p)\n", dbgstr_dbus_connection( connection ), dbgstr_dbus_message( message ),
|
|
data );
|
|
|
|
prealloc_send = p_dbus_connection_preallocate_send( connection );
|
|
if (!prealloc_send)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
if (p_dbus_message_is_method_call( message, BLUEZ_INTERFACE_AGENT, "RequestConfirmation" ))
|
|
{
|
|
struct unix_name *device;
|
|
const char *device_path;
|
|
dbus_uint32_t passkey;
|
|
DBusError error;
|
|
|
|
p_dbus_error_init( &error );
|
|
if (!p_dbus_message_get_args( message, &error, DBUS_TYPE_OBJECT_PATH, &device_path, DBUS_TYPE_UINT32, &passkey,
|
|
DBUS_TYPE_INVALID ))
|
|
{
|
|
ERR( "Failed to get message args: %s: %s\n", debugstr_a( error.name ), debugstr_a( error.message ) );
|
|
p_dbus_error_free( &error );
|
|
p_dbus_connection_free_preallocated_send( connection, prealloc_send );
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
p_dbus_error_free( &error );
|
|
device = unix_name_get_or_create( device_path );
|
|
if (!device)
|
|
{
|
|
ERR( "Failed to allocate memory for device path %s\n", device_path );
|
|
p_dbus_connection_free_preallocated_send( connection, prealloc_send );
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
}
|
|
|
|
TRACE( "Received a numeric confirmation request for device %s\n", debugstr_a( device_path ) );
|
|
pthread_mutex_lock( &ctx->lock );
|
|
ctx->status = BLUEZ_PAIRING_SESSION_INCOMING;
|
|
ctx->device = device;
|
|
ctx->method = BLUETOOTH_AUTHENTICATION_METHOD_NUMERIC_COMPARISON;
|
|
ctx->passkey = passkey;
|
|
ctx->auth_request = p_dbus_message_ref( message );
|
|
ctx->preallocate_send = prealloc_send;
|
|
ctx->connection = p_dbus_connection_ref( connection );
|
|
pthread_mutex_unlock( &ctx->lock );
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
if (p_dbus_message_is_method_call( message, BLUEZ_INTERFACE_AGENT, "AuthorizeService" ))
|
|
/* Services are handled by BlueZ and other services, so we can just allow them here, as
|
|
* this method only gets called for authenticated devices. */
|
|
reply = p_dbus_message_new_method_return( message );
|
|
else if (p_dbus_message_is_method_call( message, BLUEZ_INTERFACE_AGENT, "Cancel" ))
|
|
{
|
|
pthread_mutex_lock( &ctx->lock );
|
|
if (ctx->status == BLUEZ_PAIRING_SESSION_INCOMING || ctx->status == BLUEZ_PAIRING_SESSION_PENDING_REPLY)
|
|
{
|
|
TRACE( "Cancelling authentication request from device %s\n", debugstr_a( ctx->device->str ) );
|
|
ctx->status = BLUEZ_PAIRING_SESSION_CANCELLED;
|
|
unix_name_free( ctx->device );
|
|
p_dbus_message_unref( ctx->auth_request );
|
|
p_dbus_connection_free_preallocated_send( ctx->connection, ctx->preallocate_send );
|
|
p_dbus_connection_unref( ctx->connection );
|
|
}
|
|
pthread_mutex_unlock( &ctx->lock );
|
|
reply = p_dbus_message_new_method_return( message );
|
|
}
|
|
else
|
|
{
|
|
FIXME( "Unsupported method call: %s\n", debugstr_a( p_dbus_message_get_member( message ) ) );
|
|
reply = p_dbus_message_new_error( message, "org.bluez.Error.Rejected", "" );
|
|
}
|
|
|
|
if (!reply)
|
|
{
|
|
p_dbus_connection_free_preallocated_send( connection, prealloc_send );
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
}
|
|
p_dbus_connection_send_preallocated( connection, prealloc_send, reply, NULL );
|
|
p_dbus_message_unref( reply );
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
const static struct DBusObjectPathVTable bluez_auth_agent_object_vtable = {
|
|
.message_function = bluez_auth_agent_vtable_message_handler };
|
|
|
|
#define WINE_BLUEZ_AUTH_AGENT_PATH "/org/winehq/wine/winebth/AuthAgent"
|
|
|
|
NTSTATUS bluez_auth_agent_start( void *connection, void **auth_agent_ctx )
|
|
{
|
|
static const char *wine_bluez_auth_agent_path = WINE_BLUEZ_AUTH_AGENT_PATH;
|
|
static const char *capability = "KeyboardDisplay";
|
|
struct bluez_auth_agent_ctx *ctx;
|
|
DBusMessage *request;
|
|
dbus_bool_t success;
|
|
DBusError error;
|
|
NTSTATUS status;
|
|
|
|
TRACE( "(%s, %p)\n", dbgstr_dbus_connection( connection ), auth_agent_ctx );
|
|
|
|
ctx = malloc( sizeof( *ctx ) );
|
|
if (!ctx)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
pthread_mutex_init( &ctx->lock, NULL );
|
|
ctx->status = BLUEZ_PAIRING_SESSION_NONE;
|
|
ctx->refcnt = 1;
|
|
|
|
p_dbus_error_init( &error );
|
|
TRACE_(dbus)( "Registering an org.bluez.Agent1 object at %s\n", WINE_BLUEZ_AUTH_AGENT_PATH );
|
|
/* No need to increase the reference count on the auth agent context here, as the vtable handlers only get called
|
|
* inside bluez_dbus_loop, where the reference count gets incremented at start. */
|
|
success = p_dbus_connection_try_register_object_path( connection, WINE_BLUEZ_AUTH_AGENT_PATH,
|
|
&bluez_auth_agent_object_vtable, ctx, &error );
|
|
if (!success)
|
|
{
|
|
ERR_(dbus)( "Failed to register object: %s: %s\n", debugstr_a( error.name ),
|
|
debugstr_a( error.message ) );
|
|
status = bluez_dbus_error_to_ntstatus( &error );
|
|
bluez_auth_agent_ctx_decref( ctx );
|
|
goto done;
|
|
}
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, "/org/bluez", BLUEZ_INTERFACE_AGENT_MANAGER,
|
|
"RegisterAgent" );
|
|
if (!request)
|
|
{
|
|
status = STATUS_NO_MEMORY;
|
|
goto failure;
|
|
}
|
|
|
|
success = p_dbus_message_append_args( request, DBUS_TYPE_OBJECT_PATH, &wine_bluez_auth_agent_path, DBUS_TYPE_STRING,
|
|
&capability, DBUS_TYPE_INVALID );
|
|
if (!success)
|
|
{
|
|
status = STATUS_NO_MEMORY;
|
|
goto failure;
|
|
}
|
|
|
|
success = p_dbus_connection_send( connection, request, NULL );
|
|
p_dbus_message_unref( request );
|
|
if (!success)
|
|
{
|
|
status = STATUS_NO_MEMORY;
|
|
goto failure;
|
|
}
|
|
status = STATUS_SUCCESS;
|
|
*auth_agent_ctx = ctx;
|
|
goto done;
|
|
|
|
failure:
|
|
p_dbus_connection_unregister_object_path( connection, WINE_BLUEZ_AUTH_AGENT_PATH );
|
|
bluez_auth_agent_ctx_decref( ctx );
|
|
done:
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS bluez_auth_agent_stop( void *connection, void *auth_agent_ctx )
|
|
{
|
|
static const char *wine_bluez_auth_agent_path = WINE_BLUEZ_AUTH_AGENT_PATH;
|
|
struct bluez_auth_agent_ctx *ctx = auth_agent_ctx;
|
|
DBusMessage *request;
|
|
dbus_bool_t success;
|
|
|
|
TRACE( "(%s, %p)\n", dbgstr_dbus_connection( connection ), auth_agent_ctx );
|
|
|
|
bluez_auth_agent_ctx_decref( ctx );
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, "/org/bluez", BLUEZ_INTERFACE_AGENT_MANAGER,
|
|
"UnregisterAgent" );
|
|
if (!request)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
success = p_dbus_message_append_args( request, DBUS_TYPE_OBJECT_PATH, &wine_bluez_auth_agent_path,
|
|
DBUS_TYPE_INVALID );
|
|
if (!success)
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
success = p_dbus_connection_send( connection, request, NULL );
|
|
p_dbus_message_unref( request );
|
|
if (!success)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
success = p_dbus_connection_unregister_object_path( connection, WINE_BLUEZ_AUTH_AGENT_PATH );
|
|
return success ? STATUS_SUCCESS : STATUS_NO_MEMORY;
|
|
}
|
|
|
|
NTSTATUS bluez_auth_agent_request_default( void *connection )
|
|
{
|
|
static const char *wine_bluez_auth_agent_path = WINE_BLUEZ_AUTH_AGENT_PATH;
|
|
DBusMessage *request, *reply;
|
|
dbus_bool_t success;
|
|
NTSTATUS status;
|
|
DBusError error;
|
|
|
|
TRACE( "(%p)\n", connection );
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, "/org/bluez", BLUEZ_INTERFACE_AGENT_MANAGER,
|
|
"RequestDefaultAgent" );
|
|
if (!request)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
success = p_dbus_message_append_args( request, DBUS_TYPE_OBJECT_PATH, &wine_bluez_auth_agent_path,
|
|
DBUS_TYPE_INVALID );
|
|
if (!success)
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
p_dbus_error_init( &error );
|
|
status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
|
|
if (status)
|
|
{
|
|
p_dbus_message_unref( request );
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
if (!reply)
|
|
{
|
|
status = bluez_dbus_error_to_ntstatus( &error );
|
|
ERR( "RequestDefaultAgent failed: %s: %s\n", debugstr_a( error.name ), debugstr_a( error.message ) );
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
p_dbus_error_free( &error );
|
|
p_dbus_message_unref( reply );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS bluez_auth_agent_send_response( void *auth_agent, struct unix_name *device,
|
|
BLUETOOTH_AUTHENTICATION_METHOD method, UINT32 numeric_or_passkey,
|
|
BOOL negative, BOOL *authenticated )
|
|
{
|
|
struct bluez_auth_agent_ctx *ctx = auth_agent;
|
|
NTSTATUS ret;
|
|
|
|
TRACE( "auth_agent=%p device=%s negative=%d\n", auth_agent, debugstr_a( device->str ), negative );
|
|
|
|
pthread_mutex_lock( &ctx->lock );
|
|
switch (ctx->status)
|
|
{
|
|
case BLUEZ_PAIRING_SESSION_PENDING_REPLY:
|
|
{
|
|
DBusMessage *reply;
|
|
if (device != ctx->device)
|
|
{
|
|
ret = STATUS_DEVICE_NOT_CONNECTED;
|
|
goto done;
|
|
}
|
|
if (numeric_or_passkey != ctx->passkey || method != ctx->method)
|
|
negative = TRUE;
|
|
|
|
reply = negative ? p_dbus_message_new_error( ctx->auth_request, "org.bluez.Rejected", "" )
|
|
: p_dbus_message_new_method_return( ctx->auth_request );
|
|
if (!reply)
|
|
{
|
|
ret = STATUS_NO_MEMORY;
|
|
goto done;
|
|
}
|
|
p_dbus_connection_send_preallocated( ctx->connection, ctx->preallocate_send, reply, NULL );
|
|
|
|
unix_name_free( ctx->device );
|
|
p_dbus_message_unref( ctx->auth_request );
|
|
p_dbus_connection_unref( ctx->connection );
|
|
ctx->status = BLUEZ_PAIRING_SESSION_NONE;
|
|
*authenticated = !negative;
|
|
ret = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
case BLUEZ_PAIRING_SESSION_CANCELLED:
|
|
ret = STATUS_CANCELLED;
|
|
break;
|
|
default:
|
|
ret = STATUS_DEVICE_NOT_READY;
|
|
break;
|
|
}
|
|
done:
|
|
pthread_mutex_unlock( &ctx->lock );
|
|
return ret;
|
|
}
|
|
|
|
NTSTATUS bluez_device_disconnect( void *connection, const char *device_path )
|
|
{
|
|
DBusMessage *request, *reply = NULL;
|
|
NTSTATUS status;
|
|
DBusError error;
|
|
|
|
TRACE( "(%p, %s)\n", connection, debugstr_a( device_path ) );
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, device_path, BLUEZ_INTERFACE_DEVICE, "Disconnect" );
|
|
if (!request)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
p_dbus_error_init( &error );
|
|
status = bluez_dbus_send_and_wait_for_reply( connection, request, &reply, &error );
|
|
if (status)
|
|
{
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
if (!reply)
|
|
{
|
|
ERR( "Failed to disconnect device %s: %s: %s\n", debugstr_a( device_path ), debugstr_a( error.name ),
|
|
debugstr_a( error.message ) );
|
|
status = bluez_dbus_error_to_ntstatus( &error );
|
|
p_dbus_error_free( &error );
|
|
return status;
|
|
}
|
|
p_dbus_message_unref( reply );
|
|
p_dbus_error_free( &error );
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static BOOL bluez_event_list_queue_new_event( struct list *event_list,
|
|
enum winebluetooth_watcher_event_type event_type,
|
|
union winebluetooth_watcher_event_data event );
|
|
struct bluez_device_pair_data
|
|
{
|
|
IRP *irp;
|
|
struct bluez_watcher_ctx *watcher_ctx;
|
|
};
|
|
|
|
static void bluez_device_pair_callback( DBusPendingCall *pending, void *param )
|
|
{
|
|
struct bluez_device_pair_data *data = param;
|
|
DBusMessage *reply;
|
|
DBusError error;
|
|
union winebluetooth_watcher_event_data event = {0};
|
|
|
|
event.pairing_finished.irp = data->irp;
|
|
reply = p_dbus_pending_call_steal_reply( pending );
|
|
p_dbus_error_init( &error );
|
|
if (p_dbus_set_error_from_message( &error, reply ))
|
|
{
|
|
event.pairing_finished.result = bluez_dbus_error_to_ntstatus( &error );
|
|
ERR( "Failed to pair: %s: %s\n", debugstr_a( error.name ), debugstr_a( error.message ) );
|
|
}
|
|
p_dbus_error_free( &error );
|
|
|
|
bluez_event_list_queue_new_event( &data->watcher_ctx->event_list,
|
|
BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED, event );
|
|
p_dbus_message_unref( reply );
|
|
}
|
|
|
|
NTSTATUS bluez_device_start_pairing( void *connection, void *watcher_ctx, struct unix_name *device, IRP *irp )
|
|
{
|
|
DBusMessage *request;
|
|
DBusPendingCall *pending_call = NULL;
|
|
struct bluez_device_pair_data *data;
|
|
dbus_bool_t success;
|
|
|
|
TRACE( "(%p, %p, %s, %p)\n", connection, watcher_ctx, debugstr_a( device->str ), irp );
|
|
|
|
request = p_dbus_message_new_method_call( BLUEZ_DEST, device->str, BLUEZ_INTERFACE_DEVICE, "Pair" );
|
|
if (!request)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
data = malloc( sizeof( *data ) );
|
|
if (!data)
|
|
{
|
|
p_dbus_message_unref( request );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
data->irp = irp;
|
|
data->watcher_ctx = watcher_ctx;
|
|
success = p_dbus_connection_send_with_reply( connection, request, &pending_call, bluez_timeout );
|
|
p_dbus_message_unref( request );
|
|
if (!success)
|
|
return STATUS_NO_MEMORY;
|
|
if (!pending_call)
|
|
return STATUS_INTERNAL_ERROR;
|
|
if (!p_dbus_pending_call_set_notify( pending_call, bluez_device_pair_callback, data, free ))
|
|
{
|
|
p_dbus_pending_call_cancel( pending_call );
|
|
p_dbus_pending_call_unref( pending_call );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
p_dbus_pending_call_unref( pending_call );
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
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 (!(event_entry = calloc(1, sizeof( *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 && callback)
|
|
p_dbus_pending_call_set_notify( call, callback, &event_entry->event, NULL );
|
|
list_add_tail( event_list, &event_entry->entry );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL bluez_event_list_queue_new_event( struct list *event_list,
|
|
enum winebluetooth_watcher_event_type event_type,
|
|
union winebluetooth_watcher_event_data event )
|
|
{
|
|
return bluez_event_list_queue_new_event_with_call( event_list, event_type, event, NULL, NULL );
|
|
}
|
|
|
|
static void bluez_filter_radio_props_changed_callback( DBusPendingCall *call, void *user_data )
|
|
{
|
|
union winebluetooth_watcher_event_data *event = user_data;
|
|
struct winebluetooth_watcher_event_radio_props_changed *changed = &event->radio_props_changed;
|
|
const struct unix_name *radio = (struct unix_name *)event->radio_props_changed.radio.handle;
|
|
DBusMessage *reply;
|
|
DBusMessageIter dict, prop_iter, variant;
|
|
const char *prop_name;
|
|
DBusError error;
|
|
|
|
TRACE( "call %p, radio %s\n", call, debugstr_a( radio->str ) );
|
|
|
|
reply = p_dbus_pending_call_steal_reply( call );
|
|
p_dbus_error_init( &error );
|
|
if (p_dbus_set_error_from_message( &error, reply ))
|
|
{
|
|
ERR( "Failed to get adapter properties for %s: %s: %s\n", debugstr_a( radio->str ),
|
|
debugstr_a( error.name ), debugstr_a( error.message ) );
|
|
p_dbus_error_free( &error );
|
|
p_dbus_message_unref( reply );
|
|
return;
|
|
}
|
|
p_dbus_error_free( &error );
|
|
|
|
p_dbus_message_iter_init( reply, &dict );
|
|
p_dbus_message_iter_recurse( &dict, &prop_iter );
|
|
while((prop_name = bluez_next_dict_entry( &prop_iter, &variant )))
|
|
{
|
|
bluez_radio_prop_from_dict_entry( prop_name, &variant, &changed->props,
|
|
&changed->changed_props_mask, changed->invalid_props_mask );
|
|
}
|
|
changed->invalid_props_mask &= ~changed->changed_props_mask;
|
|
p_dbus_message_unref( reply );
|
|
}
|
|
|
|
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 = (struct unix_name *)event->device_props_changed.device.handle;
|
|
DBusMessage *reply;
|
|
DBusMessageIter dict, prop_iter, variant;
|
|
const char *prop_name;
|
|
DBusError error;
|
|
|
|
TRACE( "call %p, device %s\n", call, debugstr_a( device->str ) );
|
|
|
|
reply = p_dbus_pending_call_steal_reply( call );
|
|
p_dbus_error_init( &error );
|
|
if (p_dbus_set_error_from_message( &error, reply ))
|
|
{
|
|
ERR( "Failed to get device properties for %s: %s: %s\n", debugstr_a( device->str ), debugstr_a( error.name ),
|
|
debugstr_a( error.message ) );
|
|
p_dbus_error_free( &error );
|
|
p_dbus_message_unref( reply );
|
|
return;
|
|
}
|
|
p_dbus_error_free( &error );
|
|
|
|
p_dbus_message_iter_init( reply, &dict );
|
|
p_dbus_message_iter_recurse( &dict, &prop_iter );
|
|
while((prop_name = bluez_next_dict_entry( &prop_iter, &variant )))
|
|
{
|
|
bluez_device_prop_from_dict_entry( prop_name, &variant, &changed->props,
|
|
&changed->changed_props_mask,
|
|
changed->invalid_props_mask );
|
|
}
|
|
changed->invalid_props_mask &= ~changed->changed_props_mask;
|
|
p_dbus_message_unref( reply );
|
|
}
|
|
|
|
struct bluez_object_property_masks
|
|
{
|
|
const char *prop_name;
|
|
UINT16 mask;
|
|
};
|
|
|
|
static UINT16 bluez_dbus_get_invalidated_properties_from_iter(
|
|
DBusMessageIter *invalid_prop_iter, const struct bluez_object_property_masks *prop_masks,
|
|
SIZE_T len )
|
|
{
|
|
UINT16 mask = 0;
|
|
|
|
while (p_dbus_message_iter_has_next( invalid_prop_iter ))
|
|
{
|
|
const char *prop_name;
|
|
SIZE_T i;
|
|
|
|
assert( p_dbus_message_iter_get_arg_type( invalid_prop_iter ) == DBUS_TYPE_STRING );
|
|
p_dbus_message_iter_get_basic( invalid_prop_iter, &prop_name );
|
|
for (i = 0; i < len; i++)
|
|
{
|
|
if (strcmp( prop_masks[i].prop_name, prop_name ) == 0)
|
|
mask |= prop_masks[i].mask;
|
|
}
|
|
|
|
p_dbus_message_iter_next( invalid_prop_iter );
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
static DBusHandlerResult bluez_filter( DBusConnection *conn, DBusMessage *msg, void *user_data )
|
|
{
|
|
struct list *event_list;
|
|
|
|
if (TRACE_ON( dbus ))
|
|
TRACE_( dbus )( "(%s, %s, %p)\n", dbgstr_dbus_connection( conn ), dbgstr_dbus_message( msg ), user_data );
|
|
|
|
event_list = &((struct bluez_watcher_ctx *)user_data)->event_list;
|
|
|
|
if (p_dbus_message_is_signal( msg, DBUS_INTERFACE_OBJECTMANAGER, DBUS_OBJECTMANAGER_SIGNAL_INTERFACESADDED )
|
|
&& p_dbus_message_has_signature( msg, DBUS_INTERFACES_ADDED_SIGNATURE ))
|
|
{
|
|
DBusMessageIter iter, ifaces_iter;
|
|
const char *object_path;
|
|
|
|
p_dbus_message_iter_init( msg, &iter );
|
|
p_dbus_message_iter_get_basic( &iter, &object_path );
|
|
p_dbus_message_iter_next( &iter );
|
|
p_dbus_message_iter_recurse( &iter, &ifaces_iter );
|
|
while (p_dbus_message_iter_has_next( &ifaces_iter ))
|
|
{
|
|
DBusMessageIter iface_entry;
|
|
const char *iface_name;
|
|
|
|
p_dbus_message_iter_recurse( &ifaces_iter, &iface_entry );
|
|
p_dbus_message_iter_get_basic( &iface_entry, &iface_name );
|
|
if (!strcmp( iface_name, BLUEZ_INTERFACE_ADAPTER ))
|
|
{
|
|
struct winebluetooth_watcher_event_radio_added radio_added = {0};
|
|
struct unix_name *radio;
|
|
DBusMessageIter props_iter, variant;
|
|
const char *prop_name;
|
|
|
|
p_dbus_message_iter_next( &iface_entry );
|
|
p_dbus_message_iter_recurse( &iface_entry, &props_iter );
|
|
|
|
while((prop_name = bluez_next_dict_entry( &props_iter, &variant )))
|
|
{
|
|
bluez_radio_prop_from_dict_entry( prop_name, &variant, &radio_added.props,
|
|
&radio_added.props_mask,
|
|
WINEBLUETOOTH_RADIO_ALL_PROPERTIES );
|
|
}
|
|
|
|
radio = unix_name_get_or_create( object_path );
|
|
radio_added.radio.handle = (UINT_PTR)radio;
|
|
if (!radio_added.radio.handle)
|
|
{
|
|
ERR( "failed to allocate memory for adapter path %s\n", debugstr_a( object_path ) );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
union winebluetooth_watcher_event_data event = { .radio_added = radio_added };
|
|
TRACE( "New BlueZ org.bluez.Adapter1 object added at %s: %p\n",
|
|
debugstr_a( object_path ), radio );
|
|
if (!bluez_event_list_queue_new_event(
|
|
event_list, BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED, event ))
|
|
unix_name_free( radio );
|
|
}
|
|
}
|
|
else if (!strcmp( iface_name, BLUEZ_INTERFACE_DEVICE ))
|
|
{
|
|
struct winebluetooth_watcher_event_device_added device_added = {0};
|
|
struct unix_name *device_name, *radio_name = NULL;
|
|
DBusMessageIter props_iter, variant;
|
|
const char *prop_name;
|
|
|
|
device_name = unix_name_get_or_create( object_path );
|
|
device_added.device.handle = (UINT_PTR)device_name;
|
|
if (!device_name)
|
|
{
|
|
ERR("Failed to allocate memory for device path %s\n", debugstr_a( object_path ));
|
|
break;
|
|
}
|
|
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 );
|
|
radio_name = unix_name_get_or_create( path );
|
|
if (!radio_name)
|
|
{
|
|
unix_name_free( device_name );
|
|
ERR("Failed to allocate memory for radio path %s\n", debugstr_a( path ));
|
|
break;
|
|
}
|
|
device_added.radio.handle = (UINT_PTR)radio_name;
|
|
}
|
|
else
|
|
bluez_device_prop_from_dict_entry( prop_name, &variant, &device_added.props,
|
|
&device_added.known_props_mask,
|
|
WINEBLUETOOTH_DEVICE_ALL_PROPERTIES );
|
|
}
|
|
|
|
if (!radio_name)
|
|
{
|
|
unix_name_free( device_name );
|
|
ERR( "Could not find the associated adapter for device %s\n", debugstr_a( object_path ) );
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
union winebluetooth_watcher_event_data event = { .device_added = device_added };
|
|
TRACE( "New BlueZ org.bluez.Device1 object added at %s: %p\n", debugstr_a( object_path ),
|
|
device_name );
|
|
if (!bluez_event_list_queue_new_event( event_list, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_ADDED, event ))
|
|
{
|
|
unix_name_free( device_name );
|
|
unix_name_free( radio_name );
|
|
}
|
|
}
|
|
}
|
|
p_dbus_message_iter_next( &ifaces_iter );
|
|
}
|
|
}
|
|
else if (p_dbus_message_is_signal( msg, DBUS_INTERFACE_OBJECTMANAGER, DBUS_OBJECTMANAGER_SIGNAL_INTERFACESREMOVED )
|
|
&& p_dbus_message_has_signature( msg, DBUS_INTERFACES_REMOVED_SIGNATURE ))
|
|
{
|
|
const char *object_path;
|
|
char **interfaces;
|
|
int n_interfaces, i;
|
|
DBusError error;
|
|
dbus_bool_t success;
|
|
|
|
p_dbus_error_init( &error );
|
|
success = p_dbus_message_get_args( msg, &error, DBUS_TYPE_OBJECT_PATH, &object_path,
|
|
DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &interfaces,
|
|
&n_interfaces, DBUS_TYPE_INVALID );
|
|
if (!success)
|
|
{
|
|
ERR( "error getting arguments from message: %s: %s\n", debugstr_a( error.name ),
|
|
debugstr_a( error.message ) );
|
|
p_dbus_error_free( &error );
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
p_dbus_error_free( &error );
|
|
for (i = 0; i < n_interfaces; i++)
|
|
{
|
|
if (!strcmp( interfaces[i], BLUEZ_INTERFACE_ADAPTER ))
|
|
{
|
|
winebluetooth_radio_t radio;
|
|
struct unix_name *radio_name;
|
|
union winebluetooth_watcher_event_data event;
|
|
|
|
radio_name = unix_name_get_or_create( object_path );
|
|
if (!radio_name)
|
|
{
|
|
ERR( "failed to allocate memory for adapter path %s\n", object_path );
|
|
continue;
|
|
}
|
|
radio.handle = (UINT_PTR)radio_name;
|
|
event.radio_removed = radio;
|
|
if (!bluez_event_list_queue_new_event(
|
|
event_list, BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED, event ))
|
|
unix_name_free( radio_name );
|
|
}
|
|
else if (!strcmp( interfaces[i], BLUEZ_INTERFACE_DEVICE ))
|
|
{
|
|
struct unix_name *device;
|
|
union winebluetooth_watcher_event_data event;
|
|
|
|
device = unix_name_get_or_create( object_path );
|
|
if (!device)
|
|
{
|
|
ERR( "Failed to allocate memory for adapter path %s\n", object_path );
|
|
continue;
|
|
}
|
|
event.device_removed.device.handle = (UINT_PTR)device;
|
|
if (!bluez_event_list_queue_new_event( event_list, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_REMOVED,
|
|
event ))
|
|
unix_name_free( device );
|
|
}
|
|
else if (!strcmp( interfaces[i], BLUEZ_INTERFACE_GATT_SERVICE ))
|
|
{
|
|
struct unix_name *service;
|
|
union winebluetooth_watcher_event_data event;
|
|
|
|
service = unix_name_get_or_create( object_path );
|
|
if (!service)
|
|
{
|
|
ERR( "Failed to allocate memory for GATT service path %s\n", debugstr_a( object_path ) );
|
|
continue;
|
|
}
|
|
event.gatt_service_removed.handle = (UINT_PTR)service;
|
|
if (!bluez_event_list_queue_new_event( event_list, BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED,
|
|
event ))
|
|
unix_name_free( service );
|
|
}
|
|
else if (!strcmp( interfaces[i], BLUEZ_INTERFACE_GATT_CHARACTERISTICS ))
|
|
{
|
|
struct unix_name *chrc;
|
|
union winebluetooth_watcher_event_data event;
|
|
|
|
chrc = unix_name_get_or_create( object_path );
|
|
if (!chrc)
|
|
{
|
|
ERR( "Failed to allocate memory for GATT characteristic path %s\n", debugstr_a( object_path ) );
|
|
continue;
|
|
}
|
|
event.gatt_characterisic_removed.handle = (UINT_PTR)chrc;
|
|
if (!bluez_event_list_queue_new_event( event_list, BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED,
|
|
event ))
|
|
unix_name_free( chrc );
|
|
}
|
|
}
|
|
p_dbus_free_string_array( interfaces );
|
|
}
|
|
else if (p_dbus_message_is_signal( msg, DBUS_INTERFACE_PROPERTIES, DBUS_PROPERTIES_SIGNAL_PROPERTIESCHANGED ) &&
|
|
p_dbus_message_has_signature( msg, DBUS_PROPERTIES_CHANGED_SIGNATURE ))
|
|
{
|
|
DBusMessageIter iter;
|
|
const char *iface;
|
|
|
|
p_dbus_message_iter_init( msg, &iter );
|
|
p_dbus_message_iter_get_basic( &iter, &iface );
|
|
|
|
if (!strcmp( iface, BLUEZ_INTERFACE_ADAPTER ))
|
|
{
|
|
struct winebluetooth_watcher_event_radio_props_changed props_changed = {0};
|
|
struct unix_name *radio;
|
|
DBusMessageIter changed_props_iter, invalid_props_iter, variant;
|
|
const char *prop_name;
|
|
const static struct bluez_object_property_masks radio_prop_masks[] = {
|
|
{ "Name", WINEBLUETOOTH_RADIO_PROPERTY_NAME },
|
|
{ "Address", WINEBLUETOOTH_RADIO_PROPERTY_ADDRESS },
|
|
{ "Discoverable", WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERABLE },
|
|
{ "Connectable", WINEBLUETOOTH_RADIO_PROPERTY_CONNECTABLE },
|
|
{ "Class", WINEBLUETOOTH_RADIO_PROPERTY_CLASS },
|
|
{ "Manufacturer", WINEBLUETOOTH_RADIO_PROPERTY_MANUFACTURER },
|
|
{ "Version", WINEBLUETOOTH_RADIO_PROPERTY_VERSION },
|
|
{ "Discovering", WINEBLUETOOTH_RADIO_PROPERTY_DISCOVERING },
|
|
{ "Pairable", WINEBLUETOOTH_RADIO_PROPERTY_PAIRABLE },
|
|
};
|
|
const char *object_path;
|
|
|
|
p_dbus_message_iter_next( &iter );
|
|
p_dbus_message_iter_recurse( &iter, &changed_props_iter );
|
|
while ((prop_name = bluez_next_dict_entry( &changed_props_iter, &variant )))
|
|
{
|
|
bluez_radio_prop_from_dict_entry( prop_name, &variant, &props_changed.props,
|
|
&props_changed.changed_props_mask,
|
|
WINEBLUETOOTH_RADIO_ALL_PROPERTIES );
|
|
}
|
|
|
|
p_dbus_message_iter_next( &iter );
|
|
p_dbus_message_iter_recurse( &iter, &invalid_props_iter );
|
|
props_changed.invalid_props_mask = bluez_dbus_get_invalidated_properties_from_iter(
|
|
&invalid_props_iter, radio_prop_masks, ARRAY_SIZE( radio_prop_masks ) );
|
|
if (!props_changed.changed_props_mask && !props_changed.invalid_props_mask)
|
|
/* No properties that are of any interest to us have changed or been invalidated,
|
|
* no need to generate an event. */
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
object_path = p_dbus_message_get_path( msg );
|
|
radio = unix_name_get_or_create( object_path );
|
|
if (!radio)
|
|
{
|
|
ERR( "failed to allocate memory for adapter path %s\n", debugstr_a( object_path ) );
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
props_changed.radio.handle = (UINT_PTR)radio;
|
|
TRACE( "Properties changed for radio %s, changed %#x, invalid %#x\n",
|
|
debugstr_a( radio->str ), props_changed.changed_props_mask,
|
|
props_changed.invalid_props_mask );
|
|
if (props_changed.invalid_props_mask != 0)
|
|
{
|
|
DBusPendingCall *pending_call = NULL;
|
|
union winebluetooth_watcher_event_data event = { .radio_props_changed = props_changed };
|
|
NTSTATUS status = bluez_adapter_get_props_async( conn, radio->str, &pending_call );
|
|
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
ERR( "Failed to create async call to get adapter properties: %#x\n",
|
|
status );
|
|
unix_name_free( radio );
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
if (!bluez_event_list_queue_new_event_with_call( event_list,
|
|
BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED,
|
|
event, pending_call,
|
|
bluez_filter_radio_props_changed_callback ))
|
|
{
|
|
unix_name_free( radio );
|
|
p_dbus_pending_call_cancel( pending_call );
|
|
p_dbus_pending_call_unref( pending_call );
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
union winebluetooth_watcher_event_data event = { .radio_props_changed = props_changed };
|
|
if (!bluez_event_list_queue_new_event( event_list,
|
|
BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED,
|
|
event ))
|
|
{
|
|
unix_name_free( radio );
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
}
|
|
}
|
|
else if (strcmp( iface, BLUEZ_INTERFACE_DEVICE ) == 0)
|
|
{
|
|
struct winebluetooth_watcher_event_device_props_changed props_changed = {0};
|
|
struct unix_name *device;
|
|
const char *prop_name, *object_path;
|
|
union winebluetooth_watcher_event_data event;
|
|
DBusMessageIter changed_props_iter, invalid_props_iter, variant;
|
|
const static struct bluez_object_property_masks device_prop_masks[] = {
|
|
{ "Name", WINEBLUETOOTH_DEVICE_PROPERTY_NAME },
|
|
{ "Address", WINEBLUETOOTH_DEVICE_PROPERTY_ADDRESS },
|
|
{ "Connected", WINEBLUETOOTH_DEVICE_PROPERTY_CONNECTED },
|
|
{ "Paired", WINEBLUETOOTH_DEVICE_PROPERTY_PAIRED },
|
|
{ "LegacyPairing", WINEBLUETOOTH_DEVICE_PROPERTY_LEGACY_PAIRING },
|
|
{ "Trusted", WINEBLUETOOTH_DEVICE_PROPERTY_TRUSTED },
|
|
{ "Class", WINEBLUETOOTH_DEVICE_PROPERTY_CLASS }
|
|
};
|
|
|
|
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, &props_changed.props,
|
|
&props_changed.changed_props_mask,
|
|
WINEBLUETOOTH_DEVICE_ALL_PROPERTIES );
|
|
}
|
|
p_dbus_message_iter_next( &iter );
|
|
p_dbus_message_iter_recurse( &iter, &invalid_props_iter );
|
|
props_changed.invalid_props_mask = bluez_dbus_get_invalidated_properties_from_iter(
|
|
&invalid_props_iter, device_prop_masks, ARRAY_SIZE( device_prop_masks ) );
|
|
/* No properties that we're interested in have changed or been invalidated. */
|
|
if (!props_changed.changed_props_mask && !props_changed.invalid_props_mask)
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
|
|
object_path = p_dbus_message_get_path( msg );
|
|
TRACE( "Properties changed for device %s, changed %#x, invalidated %#x\n", debugstr_a( object_path ),
|
|
props_changed.changed_props_mask, props_changed.invalid_props_mask );
|
|
|
|
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;
|
|
}
|
|
props_changed.device.handle = (UINT_PTR)device;
|
|
event.device_props_changed = props_changed;
|
|
|
|
if (props_changed.invalid_props_mask != 0)
|
|
{
|
|
DBusPendingCall *pending_call = NULL;
|
|
NTSTATUS status;
|
|
|
|
status = bluez_device_get_props_by_path_async( conn, device->str, &pending_call );
|
|
if (status)
|
|
{
|
|
ERR( "Failed to create async call to get device properties: %#x\n", status );
|
|
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 );
|
|
p_dbus_pending_call_cancel( pending_call );
|
|
p_dbus_pending_call_unref( pending_call );
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
}
|
|
else if (!bluez_event_list_queue_new_event( event_list,
|
|
BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED,
|
|
event ))
|
|
{
|
|
|
|
unix_name_free( device );
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
}
|
|
}
|
|
|
|
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_PROPERTIES[] = "type='signal',"
|
|
"interface='"DBUS_INTERFACE_PROPERTIES"',"
|
|
"member='PropertiesChanged',"
|
|
"sender='"BLUEZ_DEST"',";
|
|
|
|
static const char *BLUEZ_MATCH_RULES[] = { BLUEZ_MATCH_OBJECTMANAGER, BLUEZ_MATCH_PROPERTIES };
|
|
|
|
/* Free up the watcher alongside any remaining events and initial devices and other associated resources. */
|
|
static void bluez_watcher_free( struct bluez_watcher_ctx *watcher )
|
|
{
|
|
struct bluez_watcher_event *event1, *event2;
|
|
struct bluez_init_entry *entry1, *entry2;
|
|
|
|
if (watcher->init_device_list_call)
|
|
{
|
|
p_dbus_pending_call_cancel( watcher->init_device_list_call );
|
|
p_dbus_pending_call_unref( watcher->init_device_list_call );
|
|
}
|
|
|
|
LIST_FOR_EACH_ENTRY_SAFE( entry1, entry2, &watcher->initial_radio_list, struct bluez_init_entry, entry )
|
|
{
|
|
list_remove( &entry1->entry );
|
|
unix_name_free( (struct unix_name *)entry1->object.radio.radio.handle );
|
|
free( entry1 );
|
|
}
|
|
|
|
LIST_FOR_EACH_ENTRY_SAFE( event1, event2, &watcher->event_list, struct bluez_watcher_event, entry )
|
|
{
|
|
list_remove( &event1->entry );
|
|
switch (event1->event_type)
|
|
{
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED:
|
|
unix_name_free( (struct unix_name *)event1->event.radio_added.radio.handle );
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_REMOVED:
|
|
unix_name_free( (struct unix_name *)event1->event.radio_removed.handle );
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_PROPERTIES_CHANGED:
|
|
unix_name_free( (struct unix_name *)event1->event.radio_props_changed.radio.handle );
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_ADDED:
|
|
unix_name_free( (struct unix_name *)event1->event.device_added.radio.handle );
|
|
unix_name_free( (struct unix_name *)event1->event.device_added.device.handle );
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_REMOVED:
|
|
unix_name_free( (struct unix_name *)event1->event.device_removed.device.handle );
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_PROPERTIES_CHANGED:
|
|
unix_name_free( (struct unix_name *)event1->event.device_props_changed.device.handle );
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_PAIRING_FINISHED:
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED:
|
|
unix_name_free( (struct unix_name *)event1->event.gatt_service_added.device.handle );
|
|
unix_name_free( (struct unix_name *)event1->event.gatt_service_added.service.handle );
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_REMOVED:
|
|
unix_name_free( (struct unix_name *)event1->event.gatt_service_removed.handle );
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED:
|
|
unix_name_free( (struct unix_name *)event1->event.gatt_characteristic_added.characteristic.handle );
|
|
unix_name_free( (struct unix_name *)event1->event.gatt_characteristic_added.service.handle );
|
|
break;
|
|
case BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_REMOVED:
|
|
unix_name_free( (struct unix_name *)event1->event.gatt_characterisic_removed.handle );
|
|
break;
|
|
}
|
|
free( event1 );
|
|
}
|
|
|
|
free( watcher );
|
|
}
|
|
|
|
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", 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->initial_gatt_service_list );
|
|
list_init( &watcher_ctx->initial_gatt_chars_list );
|
|
list_init( &watcher_ctx->event_list );
|
|
|
|
/* The bluez_dbus_loop thread will free up the watcher when the disconnect message is processed (i.e,
|
|
* dbus_connection_read_write_dispatch returns false). Using a free-function with dbus_connection_add_filter
|
|
* is racy as the filter is removed from a different thread. */
|
|
if (!p_dbus_connection_add_filter( connection, bluez_filter, watcher_ctx, NULL ))
|
|
{
|
|
p_dbus_pending_call_cancel( call );
|
|
p_dbus_pending_call_unref( call );
|
|
free( watcher_ctx );
|
|
ERR( "Could not add DBus filter\n" );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
p_dbus_error_init( &err );
|
|
for (i = 0; i < ARRAY_SIZE( BLUEZ_MATCH_RULES ); i++)
|
|
{
|
|
TRACE( "Adding DBus match rule %s\n", debugstr_a( BLUEZ_MATCH_RULES[i] ) );
|
|
|
|
p_dbus_bus_add_match( connection, BLUEZ_MATCH_RULES[i], &err );
|
|
if (p_dbus_error_is_set( &err ))
|
|
{
|
|
NTSTATUS status = bluez_dbus_error_to_ntstatus( &err );
|
|
ERR( "Could not add DBus match %s: %s: %s\n", debugstr_a( BLUEZ_MATCH_RULES[i] ), debugstr_a( err.name ),
|
|
debugstr_a( err.message ) );
|
|
p_dbus_pending_call_cancel( call );
|
|
p_dbus_pending_call_unref( call );
|
|
p_dbus_error_free( &err );
|
|
free( watcher_ctx );
|
|
return status;
|
|
}
|
|
}
|
|
p_dbus_error_free( &err );
|
|
*ctx = watcher_ctx;
|
|
TRACE( "ctx=%p\n", ctx );
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
void bluez_watcher_close( void *connection, void *ctx )
|
|
{
|
|
SIZE_T i;
|
|
for (i = 0; i < ARRAY_SIZE( BLUEZ_MATCH_RULES ); i++)
|
|
{
|
|
DBusError error;
|
|
|
|
p_dbus_error_init( &error );
|
|
p_dbus_bus_remove_match( connection, BLUEZ_MATCH_RULES[i], &error );
|
|
if (p_dbus_error_is_set( &error ))
|
|
ERR( "Could not remove DBus match %s: %s: %s", BLUEZ_MATCH_RULES[i],
|
|
debugstr_a( error.name ), debugstr_a( error.message ) );
|
|
p_dbus_error_free( &error );
|
|
}
|
|
p_dbus_connection_remove_filter( connection, bluez_filter, ctx );
|
|
}
|
|
|
|
static NTSTATUS bluez_build_initial_device_lists( DBusMessage *reply, struct list *adapter_list,
|
|
struct list *device_list, struct list *gatt_service_list,
|
|
struct list *gatt_chars_list )
|
|
{
|
|
DBusMessageIter dict, paths_iter, iface_iter, prop_iter;
|
|
const char *path;
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
if (!p_dbus_message_has_signature( reply,
|
|
DBUS_OBJECTMANAGER_METHOD_GETMANAGEDOBJECTS_RETURN_SIGNATURE ))
|
|
{
|
|
ERR( "Unexpected signature in GetManagedObjects reply: %s\n",
|
|
debugstr_a( p_dbus_message_get_signature( reply ) ) );
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
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;
|
|
}
|
|
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 );
|
|
}
|
|
init_device->object.radio.radio.handle = (UINT_PTR)radio_name;
|
|
list_add_tail( adapter_list, &init_device->entry );
|
|
TRACE( "Found BlueZ org.bluez.Adapter1 object %s: %p\n",
|
|
debugstr_a( radio_name->str ), radio_name );
|
|
break;
|
|
}
|
|
else if (!strcmp( iface, BLUEZ_INTERFACE_DEVICE ))
|
|
{
|
|
const char *prop_name;
|
|
DBusMessageIter variant;
|
|
struct bluez_init_entry *init_device;
|
|
struct unix_name *device_name, *radio_name = NULL;
|
|
|
|
init_device = calloc( 1, sizeof( *init_device ) );
|
|
if (!init_device)
|
|
{
|
|
status = STATUS_NO_MEMORY;
|
|
goto done;
|
|
}
|
|
device_name = unix_name_get_or_create( path );
|
|
if (!device_name)
|
|
{
|
|
free( init_device );
|
|
status = STATUS_NO_MEMORY;
|
|
goto done;
|
|
}
|
|
init_device->object.device.device.handle = (UINT_PTR)device_name;
|
|
init_device->object.device.init_entry = TRUE;
|
|
|
|
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 );
|
|
radio_name = unix_name_get_or_create( path );
|
|
if (!radio_name)
|
|
{
|
|
unix_name_free( device_name );
|
|
free( init_device );
|
|
status = STATUS_NO_MEMORY;
|
|
goto done;
|
|
}
|
|
init_device->object.device.radio.handle = (UINT_PTR)radio_name;
|
|
}
|
|
else
|
|
bluez_device_prop_from_dict_entry( prop_name, &variant, &init_device->object.device.props,
|
|
&init_device->object.device.known_props_mask,
|
|
WINEBLUETOOTH_DEVICE_ALL_PROPERTIES );
|
|
}
|
|
if (!init_device->object.device.radio.handle)
|
|
{
|
|
unix_name_free( device_name );
|
|
free( init_device );
|
|
ERR( "Could not find the associated adapter for device %s\n", debugstr_a( path ) );
|
|
break;
|
|
}
|
|
list_add_tail( device_list, &init_device->entry );
|
|
TRACE( "Found BlueZ org.bluez.Device1 object %s: %p\n", debugstr_a( path ), device_name );
|
|
break;
|
|
}
|
|
else if (!strcmp( iface, BLUEZ_INTERFACE_GATT_SERVICE ))
|
|
{
|
|
struct unix_name *service_name, *device_name = NULL;
|
|
struct bluez_init_entry *init_device;
|
|
DBusMessageIter variant;
|
|
const char *prop_name;
|
|
|
|
init_device = calloc( 1, sizeof( *init_device ) );
|
|
if (!init_device)
|
|
{
|
|
status = STATUS_NO_MEMORY;
|
|
goto done;
|
|
}
|
|
|
|
service_name = unix_name_get_or_create( path );
|
|
if (!service_name)
|
|
{
|
|
ERR( "Failed to allocate memory for service path %s\n", debugstr_a( path ) );
|
|
break;
|
|
}
|
|
init_device->object.service.service.handle = (UINT_PTR)service_name;
|
|
|
|
while ((prop_name = bluez_next_dict_entry( &prop_iter, &variant )))
|
|
{
|
|
if (!strcmp( prop_name, "Device" )
|
|
&& p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_OBJECT_PATH )
|
|
{
|
|
const char *device_path;
|
|
|
|
p_dbus_message_iter_get_basic( &variant, &device_path );
|
|
device_name = unix_name_get_or_create( device_path );
|
|
if (!device_name)
|
|
{
|
|
unix_name_free( service_name );
|
|
free( init_device );
|
|
ERR( "Failed to allocate memory for device path %s\n", debugstr_a( device_path ));
|
|
status = STATUS_NO_MEMORY;
|
|
goto done;
|
|
}
|
|
init_device->object.service.device.handle = (UINT_PTR)device_name;
|
|
}
|
|
else if (!strcmp( prop_name, "Handle" )
|
|
&& p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_UINT16)
|
|
p_dbus_message_iter_get_basic( &variant, &init_device->object.service.attr_handle );
|
|
else if (!strcmp( prop_name, "Primary" )
|
|
&& p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_BOOLEAN)
|
|
{
|
|
dbus_bool_t primary;
|
|
p_dbus_message_iter_get_basic( &variant, &primary );
|
|
init_device->object.service.is_primary = !!primary;
|
|
}
|
|
else if (!strcmp( prop_name, "UUID" )
|
|
&& p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_STRING)
|
|
{
|
|
const char *uuid_str;
|
|
p_dbus_message_iter_get_basic( &variant, &uuid_str );
|
|
if (!parse_uuid( &init_device->object.service.uuid, uuid_str ))
|
|
ERR("Failed to parse UUID %s for GATT service %s\n", debugstr_a( uuid_str ), path );
|
|
}
|
|
}
|
|
if (!device_name)
|
|
{
|
|
unix_name_free( service_name );
|
|
free( init_device );
|
|
ERR( "Could not find the associated device for the GATT service %s\n", debugstr_a( path ) );
|
|
break;
|
|
}
|
|
list_add_tail( gatt_service_list, &init_device->entry );
|
|
TRACE( "Found BlueZ org.bluez.GattService1 object %s %p\n", debugstr_a( path ), service_name );
|
|
break;
|
|
}
|
|
else if (!strcmp( iface, BLUEZ_INTERFACE_GATT_CHARACTERISTICS ))
|
|
{
|
|
struct unix_name *service_name = NULL, *char_name;
|
|
struct bluez_init_entry *init_entry;
|
|
BTH_LE_GATT_CHARACTERISTIC *props;
|
|
DBusMessageIter variant;
|
|
const char *prop_name;
|
|
|
|
init_entry = calloc( 1, sizeof( *init_entry ) );
|
|
if (!init_entry)
|
|
{
|
|
status = STATUS_NO_MEMORY;
|
|
goto done;
|
|
}
|
|
char_name = unix_name_get_or_create( path );
|
|
if (!char_name)
|
|
{
|
|
ERR("Failed to allocate memory for characteristic path %s\n", debugstr_a( path ));
|
|
free( init_entry );
|
|
goto done;
|
|
}
|
|
|
|
props = &init_entry->object.characteristic.props;
|
|
init_entry->object.characteristic.characteristic.handle = (UINT_PTR)char_name;
|
|
while ((prop_name = bluez_next_dict_entry( &prop_iter, &variant )))
|
|
{
|
|
if (!strcmp( prop_name, "Flags" )
|
|
&& p_dbus_message_iter_get_arg_type ( &variant ) == DBUS_TYPE_ARRAY
|
|
&& p_dbus_message_iter_get_element_type ( &variant ) == DBUS_TYPE_STRING)
|
|
{
|
|
DBusMessageIter flags_iter;
|
|
const struct {
|
|
const char *name;
|
|
BOOLEAN *flag;
|
|
} flags[] = {
|
|
{ "broadcast", &props->IsBroadcastable },
|
|
{ "read", &props->IsReadable },
|
|
{ "write", &props->IsWritable },
|
|
{ "write-without-response", &props->IsWritableWithoutResponse },
|
|
{ "authenticate-signed-writes", &props->IsSignedWritable },
|
|
{ "notify", &props->IsNotifiable },
|
|
{ "indicate", &props->IsIndicatable },
|
|
{ "extended-properties", &props->HasExtendedProperties },
|
|
};
|
|
|
|
p_dbus_message_iter_recurse( &variant, &flags_iter );
|
|
while (p_dbus_message_iter_get_arg_type( &flags_iter ) != DBUS_TYPE_INVALID)
|
|
{
|
|
const char *flag_name;
|
|
SIZE_T i;
|
|
|
|
p_dbus_message_iter_get_basic( &flags_iter, &flag_name );
|
|
for (i = 0; i < ARRAY_SIZE( flags ); i++)
|
|
{
|
|
if (!strcmp( flags[i].name, flag_name ))
|
|
*flags[i].flag = TRUE;
|
|
}
|
|
p_dbus_message_iter_next( &flags_iter );
|
|
}
|
|
}
|
|
else if (!strcmp( prop_name, "Service" )
|
|
&& p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_OBJECT_PATH)
|
|
{
|
|
const char *path;
|
|
|
|
p_dbus_message_iter_get_basic( &variant, &path );
|
|
service_name = unix_name_get_or_create( path );
|
|
}
|
|
else if (!strcmp( prop_name, "UUID" )
|
|
&& p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_STRING)
|
|
{
|
|
const char *uuid_str;
|
|
GUID uuid;
|
|
|
|
p_dbus_message_iter_get_basic( &variant, &uuid_str );
|
|
if (parse_uuid( &uuid, uuid_str ))
|
|
uuid_to_le( &uuid, &props->CharacteristicUuid );
|
|
else
|
|
ERR( "Failed to parse UUID %s for GATT characteristic %s\n", debugstr_a( uuid_str ), path );
|
|
}
|
|
else if (!strcmp( prop_name, "Handle" )
|
|
&& p_dbus_message_iter_get_arg_type( &variant ) == DBUS_TYPE_UINT16)
|
|
p_dbus_message_iter_get_basic( &variant, &props->AttributeHandle );
|
|
}
|
|
if (!service_name)
|
|
{
|
|
unix_name_free( char_name );
|
|
free( init_entry );
|
|
ERR( "Could not find the associated service for the GATT charcteristic %s\n", debugstr_a( path ) );
|
|
break;
|
|
}
|
|
init_entry->object.characteristic.service.handle = (UINT_PTR)service_name;
|
|
|
|
list_add_tail( gatt_chars_list, &init_entry->entry );
|
|
TRACE( "Found Bluez org.bluez.GattCharacteristic1 object %s %p\n", debugstr_a( path ), char_name );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACE( "Initial device list: radios: %d, devices: %d, GATT services: %d, characteristics: %d \n",
|
|
list_count( adapter_list ), list_count( device_list ), list_count( gatt_service_list ),
|
|
list_count( gatt_chars_list ) );
|
|
done:
|
|
return status;
|
|
}
|
|
|
|
static BOOL bluez_watcher_event_queue_ready( struct bluez_watcher_ctx *ctx, struct winebluetooth_watcher_event *event )
|
|
{
|
|
if (!list_empty( &ctx->initial_radio_list ))
|
|
{
|
|
struct bluez_init_entry *radio;
|
|
|
|
radio = LIST_ENTRY( list_head( &ctx->initial_radio_list ), struct bluez_init_entry, entry );
|
|
event->event_type = BLUETOOTH_WATCHER_EVENT_TYPE_RADIO_ADDED;
|
|
event->event_data.radio_added = radio->object.radio;
|
|
list_remove( &radio->entry );
|
|
free( radio );
|
|
return TRUE;
|
|
}
|
|
if (!list_empty( &ctx->initial_device_list ))
|
|
{
|
|
struct bluez_init_entry *device;
|
|
|
|
device = LIST_ENTRY( list_head( &ctx->initial_device_list ), struct bluez_init_entry, entry );
|
|
event->event_type = BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_ADDED;
|
|
event->event_data.device_added = device->object.device;
|
|
list_remove( &device->entry );
|
|
free( device );
|
|
return TRUE;
|
|
}
|
|
if (!list_empty( &ctx->initial_gatt_service_list ))
|
|
{
|
|
struct bluez_init_entry *service;
|
|
|
|
service = LIST_ENTRY( list_head( &ctx->initial_gatt_service_list ), struct bluez_init_entry, entry );
|
|
event->event_type = BLUETOOTH_WATCHER_EVENT_TYPE_DEVICE_GATT_SERVICE_ADDED;
|
|
event->event_data.gatt_service_added = service->object.service;
|
|
list_remove( &service->entry );
|
|
free( service );
|
|
return TRUE;
|
|
}
|
|
if (!list_empty( &ctx->initial_gatt_chars_list ))
|
|
{
|
|
struct bluez_init_entry *characteristic;
|
|
characteristic = LIST_ENTRY( list_head( &ctx->initial_gatt_chars_list ), struct bluez_init_entry, entry );
|
|
event->event_type = BLUETOOTH_WATCHER_EVENT_TYPE_GATT_CHARACTERISTIC_ADDED;
|
|
event->event_data.gatt_characteristic_added = characteristic->object.characteristic;
|
|
list_remove( &characteristic->entry );
|
|
free( characteristic );
|
|
return TRUE;
|
|
}
|
|
if (!list_empty( &ctx->event_list ))
|
|
{
|
|
struct bluez_watcher_event *watcher_event =
|
|
LIST_ENTRY( list_head( &ctx->event_list ), struct bluez_watcher_event, entry );
|
|
|
|
if (watcher_event->pending_call && !p_dbus_pending_call_get_completed( watcher_event->pending_call ))
|
|
return FALSE;
|
|
|
|
event->event_type = watcher_event->event_type;
|
|
event->event_data = watcher_event->event;
|
|
list_remove( &watcher_event->entry );
|
|
if (watcher_event->pending_call)
|
|
p_dbus_pending_call_unref( watcher_event->pending_call );
|
|
free( watcher_event );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL bluez_auth_agent_ctx_have_event( struct bluez_auth_agent_ctx *ctx,
|
|
struct winebluetooth_auth_event *event )
|
|
{
|
|
BOOL have_event = FALSE;
|
|
|
|
pthread_mutex_lock( &ctx->lock );
|
|
if (ctx->status == BLUEZ_PAIRING_SESSION_INCOMING)
|
|
{
|
|
event->device.handle = (UINT_PTR)unix_name_dup( ctx->device );
|
|
event->method = ctx->method;
|
|
event->numeric_value_or_passkey = ctx->passkey;
|
|
|
|
ctx->status = BLUEZ_PAIRING_SESSION_PENDING_REPLY;
|
|
have_event = TRUE;
|
|
}
|
|
pthread_mutex_unlock( &ctx->lock );
|
|
|
|
return have_event;
|
|
}
|
|
|
|
NTSTATUS bluez_dbus_loop( void *c, void *watcher, void *auth_agent,
|
|
struct winebluetooth_event *result )
|
|
{
|
|
DBusConnection *connection;
|
|
struct bluez_watcher_ctx *watcher_ctx = watcher;
|
|
auth_agent = bluez_auth_agent_ctx_incref( auth_agent );
|
|
|
|
TRACE( "(%p, %p, %p)\n", c, watcher, result );
|
|
connection = p_dbus_connection_ref( c );
|
|
|
|
while (TRUE)
|
|
{
|
|
if (bluez_watcher_event_queue_ready( watcher_ctx, &result->data.watcher_event ))
|
|
{
|
|
result->status = WINEBLUETOOTH_EVENT_WATCHER_EVENT;
|
|
p_dbus_connection_unref( connection );
|
|
bluez_auth_agent_ctx_decref( auth_agent );
|
|
return STATUS_PENDING;
|
|
}
|
|
else if (bluez_auth_agent_ctx_have_event( auth_agent, &result->data.auth_event ))
|
|
{
|
|
result->status = WINEBLUETOOTH_EVENT_AUTH_EVENT;
|
|
p_dbus_connection_unref( connection );
|
|
bluez_auth_agent_ctx_decref( auth_agent );
|
|
return STATUS_PENDING;
|
|
}
|
|
else if (!p_dbus_connection_read_write_dispatch( connection, 100 ))
|
|
{
|
|
bluez_watcher_free( watcher_ctx );
|
|
bluez_auth_agent_ctx_decref( auth_agent );
|
|
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;
|
|
|
|
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 ))
|
|
{
|
|
WARN( "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 );
|
|
bluez_auth_agent_ctx_decref( auth_agent );
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
status = bluez_build_initial_device_lists( reply, &watcher_ctx->initial_radio_list,
|
|
&watcher_ctx->initial_device_list,
|
|
&watcher_ctx->initial_gatt_service_list,
|
|
&watcher_ctx->initial_gatt_chars_list );
|
|
p_dbus_message_unref( reply );
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
WARN( "Error building initial bluetooth devices list: %#x\n", status );
|
|
p_dbus_connection_unref( connection );
|
|
bluez_auth_agent_ctx_decref( auth_agent );
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
|
|
void *bluez_dbus_init( void ) { return NULL; }
|
|
void bluez_dbus_close( void *connection ) {}
|
|
void bluez_dbus_free( void *connection ) {}
|
|
NTSTATUS bluez_watcher_init( void *connection, void **ctx ) { return STATUS_NOT_SUPPORTED; }
|
|
void bluez_watcher_close( void *connection, void *ctx ) {}
|
|
NTSTATUS bluez_dbus_loop( void *c, void *watcher, void *auth_agent, struct winebluetooth_event *result )
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
NTSTATUS bluez_adapter_set_prop( void *connection, struct bluetooth_adapter_set_prop_params *params )
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
NTSTATUS bluez_adapter_start_discovery( void *connection, const char *adapter_path )
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
NTSTATUS bluez_adapter_stop_discovery( void *connection, const char *adapter_path )
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
NTSTATUS bluez_adapter_remove_device( void *connection, const char *adapter_path, const char *device_path )
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
NTSTATUS bluez_auth_agent_start( void *connection, void **ctx ) { return STATUS_NOT_SUPPORTED; }
|
|
NTSTATUS bluez_auth_agent_stop( void *connection, void *ctx ) { return STATUS_NOT_SUPPORTED; }
|
|
NTSTATUS bluez_auth_agent_request_default( void *connection ) { return STATUS_NOT_SUPPORTED; }
|
|
NTSTATUS bluez_auth_agent_send_response( void *auth_agent, struct unix_name *device,
|
|
BLUETOOTH_AUTHENTICATION_METHOD method, UINT32 numeric_or_passkey,
|
|
BOOL negative, BOOL *authenticated )
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
NTSTATUS bluez_device_disconnect( void *connection, const char *device_path )
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
NTSTATUS bluez_device_start_pairing( void *connection, void *watcher_ctx, struct unix_name *device, IRP *irp )
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
#endif /* SONAME_LIBDBUS_1 */
|