From 4f8ccb86425b76bebf15f448403088d11ca32b23 Mon Sep 17 00:00:00 2001 From: parport0 Date: Wed, 24 Jun 2020 02:51:42 +0300 Subject: [PATCH] Add bluez bluetooth driver --- Makefile.common | 6 + bluetooth/bluetooth_driver.h | 1 + bluetooth/drivers/bluez.c | 587 +++++++++++++++++++++++++++++++++++ configuration.c | 7 + griffin/griffin.c | 3 + retroarch.c | 3 + 6 files changed, 607 insertions(+) create mode 100644 bluetooth/drivers/bluez.c diff --git a/Makefile.common b/Makefile.common index 1e3bfd7038..60f99ef8bb 100644 --- a/Makefile.common +++ b/Makefile.common @@ -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 diff --git a/bluetooth/bluetooth_driver.h b/bluetooth/bluetooth_driver.h index bd086f2260..a6e4b76442 100644 --- a/bluetooth/bluetooth_driver.h +++ b/bluetooth/bluetooth_driver.h @@ -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: diff --git a/bluetooth/drivers/bluez.c b/bluetooth/drivers/bluez.c new file mode 100644 index 0000000000..18379c3517 --- /dev/null +++ b/bluetooth/drivers/bluez.c @@ -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 . + */ + +#include +#include +#include +#include + +#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 could’t 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", +}; diff --git a/configuration.c b/configuration.c index ce9098a91d..982bf294fa 100644 --- a/configuration.c +++ b/configuration.c @@ -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; } diff --git a/griffin/griffin.c b/griffin/griffin.c index 5b2e3acc52..1ad799e485 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1157,6 +1157,9 @@ BLUETOOTH ============================================================ */ #ifdef HAVE_BLUETOOTH #include "../bluetooth/drivers/bluetoothctl.c" +#ifdef HAVE_DBUS +#include "../bluetooth/drivers/bluez.c" +#endif #endif /*============================================================ diff --git a/retroarch.c b/retroarch.c index a04933c51f..c3a78b7def 100644 --- a/retroarch.c +++ b/retroarch.c @@ -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,