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,