1
0
mirror of https://github.com/libretro/RetroArch synced 2024-07-03 00:38:44 +00:00

Add bluez bluetooth driver

This commit is contained in:
parport0 2020-06-24 02:51:42 +03:00
parent 23c08ad9b0
commit 4f8ccb8642
6 changed files with 607 additions and 0 deletions

View File

@ -571,6 +571,12 @@ ifeq ($(HAVE_BLUETOOTH), 1)
OBJ += bluetooth/drivers/bluetoothctl.o
endif
ifeq ($(HAVE_BLUETOOTH), 1)
ifeq ($(HAVE_DBUS), 1)
OBJ += bluetooth/drivers/bluez.o
endif
endif
ifeq ($(HAVE_LAKKA), 1)
OBJ += wifi/drivers/connmanctl.o
endif

View File

@ -56,6 +56,7 @@ typedef struct bluetooth_driver
} bluetooth_driver_t;
extern bluetooth_driver_t bluetooth_bluetoothctl;
extern bluetooth_driver_t bluetooth_bluez;
/**
* config_get_bluetooth_driver_options:

587
bluetooth/drivers/bluez.c Normal file
View File

@ -0,0 +1,587 @@
/* RetroArch - A frontend for libretro.
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <dbus/dbus.h>
#include <compat/strl.h>
#include <configuration.h>
#include <retro_timers.h>
#include "../bluetooth_driver.h"
#include "../../retroarch.h"
typedef struct {
/* object path. usually looks like /org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF
* technically unlimited, but should be enough */
char path[128];
/* for display purposes 64 bytes should be enough */
char name[64];
/* MAC address, 17 bytes */
char address[18];
/* freedesktop.org icon name
* See bluez/src/dbus-common.c
* Can be NULL */
char icon[64];
int connected;
int paired;
int trusted;
} device_info_t;
#define VECTOR_LIST_TYPE device_info_t
#define VECTOR_LIST_NAME device_info
#include "../../libretro-common/lists/vector_list.c"
#undef VECTOR_LIST_TYPE
#undef VECTOR_LIST_NAME
static struct device_info_vector_list *devices = NULL;
static char adapter[256] = {0};
static DBusConnection* dbus_connection = NULL;
static bool bluez_cache[256] = {0};
static int bluez_cache_counter[256] = {0};
static void *bluez_init (void)
{
return (void*)-1;
}
static void bluez_free (void *data)
{
(void)data;
}
static bool bluez_start (void *data)
{
(void)data;
return true;
}
static void bluez_stop (void *data)
{
(void)data;
}
static int
set_bool_property (
const char *path,
const char *arg_adapter,
const char *arg_property,
int value)
{
DBusMessage *message, *reply;
DBusError err;
dbus_error_init(&err);
message = dbus_message_new_method_call(
"org.bluez",
path,
"org.freedesktop.DBus.Properties",
"Set"
);
if (!message)
return 1;
DBusMessageIter req_iter, req_subiter;
dbus_message_iter_init_append(message, &req_iter);
if (!dbus_message_iter_append_basic(&req_iter, DBUS_TYPE_STRING, &arg_adapter))
goto fault;
if (!dbus_message_iter_append_basic(&req_iter, DBUS_TYPE_STRING, &arg_property))
goto fault;
if (!dbus_message_iter_open_container(&req_iter, DBUS_TYPE_VARIANT,
DBUS_TYPE_BOOLEAN_AS_STRING, &req_subiter))
{
goto fault;
}
if (!dbus_message_iter_append_basic(&req_subiter, DBUS_TYPE_BOOLEAN, &value))
goto fault;
if (!dbus_message_iter_close_container(&req_iter, &req_subiter))
goto fault;
reply = dbus_connection_send_with_reply_and_block(dbus_connection,
message, 1000, &err);
if (!reply)
goto fault;
dbus_message_unref(reply);
dbus_message_unref(message);
return 0;
fault:
dbus_message_iter_abandon_container_if_open(&req_iter, &req_subiter);
dbus_message_unref(message);
return 1;
}
static int
get_bool_property (
const char *path,
const char *arg_adapter,
const char *arg_property,
int *value)
{
DBusMessage *message, *reply;
DBusError err;
DBusMessageIter root_iter, variant_iter;
dbus_error_init(&err);
message = dbus_message_new_method_call( "org.bluez", path,
"org.freedesktop.DBus.Properties", "Get");
if (!message)
return 1;
if (!dbus_message_append_args(message,
DBUS_TYPE_STRING, &arg_adapter,
DBUS_TYPE_STRING, &arg_property,
DBUS_TYPE_INVALID))
{
return 1;
}
reply = dbus_connection_send_with_reply_and_block(dbus_connection,
message, 1000, &err);
dbus_message_unref(message);
if (!reply)
return 1;
if (!dbus_message_iter_init(reply, &root_iter))
return 1;
if (DBUS_TYPE_VARIANT != dbus_message_iter_get_arg_type(&root_iter))
return 1;
dbus_message_iter_recurse(&root_iter, &variant_iter);
dbus_message_iter_get_basic(&variant_iter, value);
dbus_message_unref(reply);
return 0;
}
static int
adapter_discovery (const char *method)
{
DBusMessage *message;
message = dbus_message_new_method_call( "org.bluez", adapter,
"org.bluez.Adapter1", method);
if (!message)
return 1;
if (!dbus_connection_send(dbus_connection, message, NULL))
return 1;
dbus_connection_flush(dbus_connection);
dbus_message_unref(message);
return 0;
}
static int
get_managed_objects (DBusMessage **reply)
{
DBusMessage *message;
DBusError err;
dbus_error_init(&err);
message = dbus_message_new_method_call( "org.bluez", "/",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
if (!message)
return 1;
*reply = dbus_connection_send_with_reply_and_block(dbus_connection,
message, -1, &err);
/* if (!reply) is done by the caller in this one */
dbus_message_unref(message);
return 0;
}
static int
device_method (const char *path, const char *method)
{
DBusMessage *message, *reply;
DBusError err;
dbus_error_init(&err);
message = dbus_message_new_method_call( "org.bluez", path,
"org.bluez.Device1", method);
if (!message)
return 1;
reply = dbus_connection_send_with_reply_and_block(dbus_connection,
message, 10000, &err);
if (!reply)
return 1;
dbus_connection_flush(dbus_connection);
dbus_message_unref(message);
return 0;
}
static int
device_remove (const char *path)
{
DBusMessage *message, *reply;
DBusError err;
dbus_error_init(&err);
message = dbus_message_new_method_call( "org.bluez", adapter,
"org.bluez.Adapter11", "RemoveDevice");
if (!message)
return 1;
if (!dbus_message_append_args(message,
DBUS_TYPE_OBJECT_PATH, &path,
DBUS_TYPE_INVALID))
{
return 1;
}
reply = dbus_connection_send_with_reply_and_block(dbus_connection,
message, 10000, &err);
if (!reply)
return 1;
dbus_connection_flush(dbus_connection);
dbus_message_unref(message);
return 0;
}
static int
get_default_adapter (DBusMessage *reply)
{
/* "...an application would discover the available adapters by
* performing a ObjectManager.GetManagedObjects call and look for any
* returned objects with an org.bluez.Adapter1 interface.
* The concept of a default adapter was always a bit fuzzy and the
* value couldt be changed, so if applications need something like it
* they could e.g. just pick the first adapter they encounter in the
* GetManagedObjects reply."
* -- http://www.bluez.org/bluez-5-api-introduction-and-porting-guide/
*/
DBusMessageIter root_iter;
DBusMessageIter dict_1_iter, dict_2_iter;
DBusMessageIter array_1_iter, array_2_iter;
char *obj_path, *interface_name;
/* a{oa{sa{sv}}} */
if (!dbus_message_iter_init(reply, &root_iter))
return 1;
/* a */
if (DBUS_TYPE_ARRAY != dbus_message_iter_get_arg_type(&root_iter))
return 1;
dbus_message_iter_recurse(&root_iter, &array_1_iter);
do {
/* a{...} */
if (DBUS_TYPE_DICT_ENTRY != dbus_message_iter_get_arg_type(&array_1_iter))
return 1;
dbus_message_iter_recurse(&array_1_iter, &dict_1_iter);
/* a{o...} */
if (DBUS_TYPE_OBJECT_PATH != dbus_message_iter_get_arg_type(&dict_1_iter))
return 1;
dbus_message_iter_get_basic(&dict_1_iter, &obj_path);
if (!dbus_message_iter_next(&dict_1_iter))
return 1;
/* a{oa} */
if (DBUS_TYPE_ARRAY != dbus_message_iter_get_arg_type(&dict_1_iter))
return 1;
dbus_message_iter_recurse(&dict_1_iter, &array_2_iter);
do {
/* empty array? */
if (DBUS_TYPE_INVALID == dbus_message_iter_get_arg_type(&array_2_iter))
continue;
/* a{oa{...}} */
if (DBUS_TYPE_DICT_ENTRY != dbus_message_iter_get_arg_type(&array_2_iter))
return 1;
dbus_message_iter_recurse(&array_2_iter, &dict_2_iter);
/* a{oa{s...}} */
if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&dict_2_iter))
return 1;
dbus_message_iter_get_basic(&dict_2_iter, &interface_name);
if (strcmp(interface_name, "org.bluez.Adapter1") == 0) {
strlcpy(adapter, obj_path, 256);
return 0;
}
} while (dbus_message_iter_next(&array_2_iter));
} while (dbus_message_iter_next(&array_1_iter));
/* Couldn't find an adapter */
return 1;
}
static int
read_scanned_devices (DBusMessage *reply)
{
DBusMessageIter root_iter;
DBusMessageIter dict_1_iter, dict_2_iter, dict_3_iter;
DBusMessageIter array_1_iter, array_2_iter, array_3_iter;
DBusMessageIter variant_iter;
device_info_t device;
char *obj_path, *interface_name, *interface_property_name;
char *found_device_address, *found_device_name, *found_device_icon;
/* a{oa{sa{sv}}} */
if (!dbus_message_iter_init(reply, &root_iter))
return 1;
/* a */
if (DBUS_TYPE_ARRAY != dbus_message_iter_get_arg_type(&root_iter))
return 1;
dbus_message_iter_recurse(&root_iter, &array_1_iter);
do {
/* a{...} */
if (DBUS_TYPE_DICT_ENTRY != dbus_message_iter_get_arg_type(&array_1_iter))
return 1;
dbus_message_iter_recurse(&array_1_iter, &dict_1_iter);
/* a{o...} */
if (DBUS_TYPE_OBJECT_PATH != dbus_message_iter_get_arg_type(&dict_1_iter))
return 1;
dbus_message_iter_get_basic(&dict_1_iter, &obj_path);
if (!dbus_message_iter_next(&dict_1_iter))
return 1;
/* a{oa} */
if (DBUS_TYPE_ARRAY != dbus_message_iter_get_arg_type(&dict_1_iter))
return 1;
dbus_message_iter_recurse(&dict_1_iter, &array_2_iter);
do {
/* empty array? */
if (DBUS_TYPE_INVALID == dbus_message_iter_get_arg_type(&array_2_iter))
continue;
/* a{oa{...}} */
if (DBUS_TYPE_DICT_ENTRY != dbus_message_iter_get_arg_type(&array_2_iter))
return 1;
dbus_message_iter_recurse(&array_2_iter, &dict_2_iter);
/* a{oa{s...}} */
if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&dict_2_iter))
return 1;
dbus_message_iter_get_basic(&dict_2_iter, &interface_name);
if (strcmp(interface_name, "org.bluez.Device1") != 0)
continue;
memset(&device, 0, sizeof(device));
strlcpy(device.path, obj_path, 128);
if (!dbus_message_iter_next(&dict_2_iter))
return 1;
/* a{oa{sa}} */
if (DBUS_TYPE_ARRAY != dbus_message_iter_get_arg_type(&dict_2_iter))
return 1;
dbus_message_iter_recurse(&dict_2_iter, &array_3_iter);
do {
/* empty array? */
if (DBUS_TYPE_INVALID == dbus_message_iter_get_arg_type(&array_3_iter))
continue;
/* a{oa{sa{...}}} */
if (DBUS_TYPE_DICT_ENTRY != dbus_message_iter_get_arg_type(&array_3_iter))
return 1;
dbus_message_iter_recurse(&array_3_iter, &dict_3_iter);
/* a{oa{sa{s...}}} */
if (DBUS_TYPE_STRING != dbus_message_iter_get_arg_type(&dict_3_iter))
return 1;
dbus_message_iter_get_basic(&dict_3_iter, &interface_property_name);
if (!dbus_message_iter_next(&dict_3_iter))
return 1;
/* a{oa{sa{sv}}} */
if (DBUS_TYPE_VARIANT != dbus_message_iter_get_arg_type(&dict_3_iter))
return 1;
/* Below, "Alias" property is used instead of "Name".
* "This value ("Name") is only present for
* completeness. It is better to always use
* the Alias property when displaying the
* devices name."
* -- bluez/doc/device-api.txt
*/
/* DBUS_TYPE_VARIANT is a container type */
dbus_message_iter_recurse(&dict_3_iter, &variant_iter);
if (strcmp(interface_property_name, "Address") == 0) {
dbus_message_iter_get_basic(&variant_iter, &found_device_address);
strlcpy(device.address, found_device_address, 18);
} else if (strcmp(interface_property_name, "Alias") == 0) {
dbus_message_iter_get_basic(&variant_iter, &found_device_name);
strlcpy(device.name, found_device_name, 64);
} else if (strcmp(interface_property_name, "Icon") == 0) {
dbus_message_iter_get_basic(&variant_iter, &found_device_icon);
strlcpy(device.icon, found_device_icon, 64);
} else if (strcmp(interface_property_name, "Connected") == 0) {
dbus_message_iter_get_basic(&variant_iter, &device.connected);
} else if (strcmp(interface_property_name, "Paired") == 0) {
dbus_message_iter_get_basic(&variant_iter, &device.paired);
} else if (strcmp(interface_property_name, "Trusted") == 0) {
dbus_message_iter_get_basic(&variant_iter, &device.trusted);
}
} while (dbus_message_iter_next(&array_3_iter));
if (!device_info_vector_list_append(devices, device))
return 1;
} while (dbus_message_iter_next(&array_2_iter));
} while (dbus_message_iter_next(&array_1_iter));
return 0;
}
static void bluez_dbus_connect (void)
{
DBusError err;
dbus_error_init(&err);
dbus_connection = dbus_bus_get_private(DBUS_BUS_SYSTEM, &err);
}
static void bluez_dbus_disconnect (void)
{
if (!dbus_connection)
return;
dbus_connection_close(dbus_connection);
dbus_connection_unref(dbus_connection);
dbus_connection = NULL;
}
static void bluez_scan (void)
{
DBusError err;
DBusMessage *reply;
bluez_dbus_connect();
if (get_managed_objects(&reply))
return;
if (!reply)
return;
/* Get default adapter */
if (get_default_adapter(reply))
return;
dbus_message_unref(reply);
/* Power device on */
if (set_bool_property(adapter, "org.bluez.Adapter1", "Powered", 1))
return;
/* Start discovery */
if (adapter_discovery("StartDiscovery"))
return;
retro_sleep(10000);
/* Stop discovery */
if (adapter_discovery("StopDiscovery"))
return;
/* Get scanned devices */
if (get_managed_objects(&reply))
return;
if (!reply)
return;
if (devices)
device_info_vector_list_free(devices);
devices = device_info_vector_list_new();
read_scanned_devices(reply);
dbus_message_unref(reply);
bluez_dbus_disconnect();
}
static void bluez_get_devices (struct string_list* devices_string_list)
{
unsigned i;
union string_list_elem_attr attr;
attr.i = 0;
if (!devices)
return;
for (i = 0; i < devices->count; i++)
{
char device[64];
snprintf(device, 64, "%s %s", devices->data[i].address, devices->data[i].name);
string_list_append(devices_string_list, device, attr);
}
}
static bool bluez_device_is_connected (unsigned i)
{
int value;
if (bluez_cache_counter[i] == 60) {
bluez_cache_counter[i] = 0;
bluez_dbus_connect();
get_bool_property(devices->data[i].path, "org.bluez.Device1",
"Connected", &value);
bluez_dbus_disconnect();
bluez_cache[i] = value;
return value;
} else {
bluez_cache_counter[i]++;
return bluez_cache[i];
}
}
static bool bluez_connect_device (unsigned i)
{
bluez_dbus_connect();
/* Remove the device */
device_remove(devices->data[i].path);
/* Trust the device */
if (set_bool_property(devices->data[i].path, "org.bluez.Device1", "Trusted", 1))
return false;
/* Pair the device */
if (device_method(devices->data[i].path, "Pair"))
return false;
/* Connect the device */
if (device_method(devices->data[i].path, "Connect"))
return false;
bluez_dbus_disconnect();
bluez_cache_counter[i] = 0;
return true;
}
bluetooth_driver_t bluetooth_bluez = {
bluez_init,
bluez_free,
bluez_start,
bluez_stop,
bluez_scan,
bluez_get_devices,
bluez_device_is_connected,
bluez_connect_device,
"bluez",
};

View File

@ -274,6 +274,7 @@ enum camera_driver_enum
enum bluetooth_driver_enum
{
BLUETOOTH_BLUETOOTHCTL = CAMERA_NULL + 1,
BLUETOOTH_BLUEZ,
BLUETOOTH_NULL
};
@ -566,7 +567,11 @@ static const enum camera_driver_enum CAMERA_DEFAULT_DRIVER = CAMERA_NULL;
#endif
#if defined(HAVE_BLUETOOTH)
# if defined(HAVE_DBUS)
static const enum bluetooth_driver_enum BLUETOOTH_DEFAULT_DRIVER = BLUETOOTH_BLUEZ;
# else
static const enum bluetooth_driver_enum BLUETOOTH_DEFAULT_DRIVER = BLUETOOTH_BLUETOOTHCTL;
# endif
#else
static const enum bluetooth_driver_enum BLUETOOTH_DEFAULT_DRIVER = BLUETOOTH_NULL;
#endif
@ -1041,6 +1046,8 @@ const char *config_get_default_bluetooth(void)
{
case BLUETOOTH_BLUETOOTHCTL:
return "bluetoothctl";
case BLUETOOTH_BLUEZ:
return "bluez";
case BLUETOOTH_NULL:
break;
}

View File

@ -1157,6 +1157,9 @@ BLUETOOTH
============================================================ */
#ifdef HAVE_BLUETOOTH
#include "../bluetooth/drivers/bluetoothctl.c"
#ifdef HAVE_DBUS
#include "../bluetooth/drivers/bluez.c"
#endif
#endif
/*============================================================

View File

@ -873,6 +873,9 @@ static bluetooth_driver_t bluetooth_null = {
static const bluetooth_driver_t *bluetooth_drivers[] = {
#ifdef HAVE_BLUETOOTH
&bluetooth_bluetoothctl,
#ifdef HAVE_DBUS
&bluetooth_bluez,
#endif
#endif
&bluetooth_null,
NULL,