aboutsummaryrefslogtreecommitdiffstats
path: root/src/api.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/api.c')
-rw-r--r--src/api.c1543
1 files changed, 1543 insertions, 0 deletions
diff --git a/src/api.c b/src/api.c
new file mode 100644
index 0000000..970deab
--- /dev/null
+++ b/src/api.c
@@ -0,0 +1,1543 @@
+/*
+ * Copyright 2018-2021 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ * Author: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pthread.h>
+#include <semaphore.h>
+
+#include <glib.h>
+#include <stdlib.h>
+#include <gio/gio.h>
+#include <glib-object.h>
+
+#include "bluez-glib.h"
+#include "common.h"
+#include "conf.h"
+#include "call_work.h"
+#include "bluez-call.h"
+#include "bluez-agent.h"
+
+typedef struct bluez_signal_callback_list_entry_t {
+ gpointer callback;
+ gpointer user_data;
+} callback_list_entry_t;
+
+typedef struct {
+ GMutex mutex;
+ GSList *list;
+} callback_list_t;
+
+callback_list_t bluez_adapter_callbacks;
+callback_list_t bluez_device_callbacks;
+callback_list_t bluez_media_control_callbacks;
+callback_list_t bluez_media_player_callbacks;
+
+// The global handler thread and BlueZ state
+static GThread *g_bluez_thread;
+static struct bluez_state *g_bluez_state;
+
+// Global log level
+static bluez_log_level_t g_bluez_log_level = BLUEZ_LOG_LEVEL_DEFAULT;
+
+static const char *g_bluez_log_level_names[BLUEZ_LOG_LEVEL_DEBUG + 1] = {
+ "ERROR",
+ "WARNING",
+ "INFO",
+ "DEBUG"
+};
+
+// Wrappers to hedge possible future abstractions
+static void bluez_set_state(struct bluez_state *ns)
+{
+ g_bluez_state = ns;
+}
+
+static struct bluez_state *bluez_get_state(void)
+{
+ return g_bluez_state;
+}
+
+EXPORT void bluez_set_log_level(bluez_log_level_t level)
+{
+ g_bluez_log_level = level;
+}
+
+void bluez_log(bluez_log_level_t level, const char *func, const char *format, ...)
+{
+ FILE *out = stdout;
+
+ if (level > g_bluez_log_level)
+ return;
+
+ if (level == BLUEZ_LOG_LEVEL_ERROR)
+ out = stderr;
+
+ va_list args;
+ va_start(args, format);
+ fprintf(out, "%s: %s: ", g_bluez_log_level_names[level], func);
+ gchar *format_line = g_strconcat(format, "\n", NULL);
+ vfprintf(out, format_line, args);
+ va_end(args);
+ g_free(format_line);
+}
+
+static void callback_add(callback_list_t *callbacks, gpointer callback, gpointer user_data)
+{
+ callback_list_entry_t *entry = NULL;
+
+ if(!callbacks)
+ return;
+
+ g_mutex_lock(&callbacks->mutex);
+ entry = g_malloc0(sizeof(*entry));
+ entry->callback = callback;
+ entry->user_data = user_data;
+ callbacks->list = g_slist_append(callbacks->list, entry);
+ g_mutex_unlock(&callbacks->mutex);
+}
+
+static void callback_remove(callback_list_t *callbacks, gpointer callback)
+{
+ callback_list_entry_t *entry = NULL;
+ GSList *list;
+
+ if(!(callbacks && callbacks->list))
+ return;
+
+ g_mutex_lock(&callbacks->mutex);
+ for (list = callbacks->list; list; list = g_slist_next(list)) {
+ entry = list->data;
+ if (entry->callback == callback)
+ break;
+ entry = NULL;
+ }
+ if (entry) {
+ callbacks->list = g_slist_remove(callbacks->list, entry);
+ g_free(entry);
+ }
+ g_mutex_unlock(&callbacks->mutex);
+}
+
+static void run_callbacks(callback_list_t *callbacks,
+ gchar *adapter,
+ gchar *device,
+ bluez_event_t event,
+ GVariant *properties)
+{
+ GSList *list;
+
+ if (!(adapter || device))
+ return;
+
+ g_mutex_lock(&callbacks->mutex);
+ for (list = callbacks->list; list; list = g_slist_next(list)) {
+ callback_list_entry_t *entry = list->data;
+ if (entry->callback) {
+ if (device) {
+ bluez_device_event_cb_t cb = (bluez_device_event_cb_t) entry->callback;
+ (*cb)(adapter, device, event, properties, entry->user_data);
+ } else {
+ bluez_adapter_event_cb_t cb = (bluez_adapter_event_cb_t) entry->callback;
+ (*cb)(adapter, event, properties, entry->user_data);
+ }
+ }
+ }
+ g_mutex_unlock(&callbacks->mutex);
+}
+
+static void run_media_callbacks(callback_list_t *callbacks,
+ gchar *adapter,
+ gchar *device,
+ gchar *endpoint,
+ gchar *player,
+ bluez_event_t event,
+ GVariant *properties)
+{
+ GSList *list;
+
+ if (!(endpoint || player))
+ return;
+
+ g_mutex_lock(&callbacks->mutex);
+ for (list = callbacks->list; list; list = g_slist_next(list)) {
+ callback_list_entry_t *entry = list->data;
+ if (entry->callback) {
+ if (player) {
+ bluez_media_player_event_cb_t cb = (bluez_media_player_event_cb_t) entry->callback;
+ (*cb)(adapter, device, player, event, properties, entry->user_data);
+ } else {
+ bluez_media_control_event_cb_t cb = (bluez_media_control_event_cb_t) entry->callback;
+ (*cb)(adapter, device, endpoint, event, properties, entry->user_data);
+ }
+ }
+ }
+ g_mutex_unlock(&callbacks->mutex);
+}
+
+EXPORT void bluez_add_adapter_event_callback(bluez_adapter_event_cb_t cb, gpointer user_data)
+{
+ if (!cb)
+ return;
+
+ callback_add(&bluez_adapter_callbacks, cb, user_data);
+}
+
+EXPORT void bluez_add_device_event_callback(bluez_device_event_cb_t cb, gpointer user_data)
+{
+ if (!cb)
+ return;
+
+ callback_add(&bluez_device_callbacks, cb, user_data);
+}
+
+EXPORT void bluez_add_media_control_event_callback(bluez_media_control_event_cb_t cb, gpointer user_data)
+{
+ if (!cb)
+ return;
+
+ callback_add(&bluez_media_control_callbacks, cb, user_data);
+}
+
+EXPORT void bluez_add_media_player_event_callback(bluez_media_player_event_cb_t cb, gpointer user_data)
+{
+ if (!cb)
+ return;
+
+ callback_add(&bluez_media_player_callbacks, cb, user_data);
+}
+
+static void mediaplayer1_set_path(struct bluez_state *ns, const char *path)
+{
+ if (ns->mediaplayer_path)
+ g_free(ns->mediaplayer_path);
+ ns->mediaplayer_path = g_strdup(path);
+}
+
+static void bluez_devices_signal_callback(GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ const gchar *path = NULL;
+ GVariantIter *array = NULL;
+ GVariant *var = NULL;
+ const gchar *key = NULL;
+
+#ifdef BLUEZ_GLIB_DEBUG
+ INFO("sender=%s", sender_name);
+ INFO("object_path=%s", object_path);
+ INFO("interface=%s", interface_name);
+ INFO("signal=%s", signal_name);
+ DEBUG("parameters: %s", g_variant_print(parameters, TRUE));
+#endif
+
+ if (!g_strcmp0(signal_name, "InterfacesAdded")) {
+
+ g_variant_get(parameters, "(&oa{sa{sv}})", &path, &array);
+
+ // no adapter or device in path
+ if (!g_strcmp0(path, BLUEZ_PATH))
+ return;
+
+ while (g_variant_iter_next(array, "{&s@a{sv}}", &key, &var)) {
+ if (!g_strcmp0(key, BLUEZ_DEVICE_INTERFACE)) {
+ DEBUG("device added!");
+ gchar *adapter = bluez_return_adapter(path);
+ gchar *device = bluez_return_device(path);
+ run_callbacks(&bluez_device_callbacks, adapter, device, BLUEZ_EVENT_ADD, var);
+ g_free(adapter);
+ g_free(device);
+
+ } else if (!g_strcmp0(key, BLUEZ_ADAPTER_INTERFACE)) {
+ DEBUG("adapter added!");
+ gchar *adapter = bluez_return_adapter(path);
+ run_callbacks(&bluez_adapter_callbacks, adapter, NULL, BLUEZ_EVENT_ADD, var);
+ g_free(adapter);
+
+ } else if (!g_strcmp0(key, BLUEZ_MEDIATRANSPORT_INTERFACE)) {
+ gchar *adapter = bluez_return_adapter(path);
+ gchar *device = bluez_return_device(path);
+ gchar *endpoint = bluez_return_endpoint(path);
+ DEBUG("media endpoint added!");
+ DEBUG("media endpoint path %s, key %s, endpoint %s",
+ path, key, endpoint);
+ run_media_callbacks(&bluez_media_control_callbacks,
+ adapter,
+ device,
+ endpoint,
+ NULL,
+ BLUEZ_EVENT_ADD,
+ var);
+ g_free(adapter);
+ g_free(device);
+ g_free(endpoint);
+
+ } else if (!g_strcmp0(key, BLUEZ_MEDIAPLAYER_INTERFACE)) {
+ gchar *adapter = bluez_return_adapter(path);
+ gchar *device = bluez_return_device(path);
+ gchar *player = find_index(path, 5);
+ DEBUG("media player removed!");
+ DEBUG("media player = %s", player);
+ run_media_callbacks(&bluez_media_player_callbacks,
+ adapter,
+ device,
+ NULL,
+ player,
+ BLUEZ_EVENT_ADD,
+ var);
+ g_free(adapter);
+ g_free(device);
+ g_free(player);
+ }
+ g_variant_unref(var);
+ }
+ g_variant_iter_free(array);
+
+ } else if (!g_strcmp0(signal_name, "InterfacesRemoved")) {
+
+ g_variant_get(parameters, "(&o@as)", &path, NULL);
+
+ if (is_mediatransport1_interface(path)) {
+ gchar *adapter = bluez_return_adapter(path);
+ gchar *device = bluez_return_device(path);
+ gchar *endpoint = bluez_return_endpoint(path);
+ DEBUG("media endpoint removed!");
+ DEBUG("media endpoint = %s", endpoint);
+ run_media_callbacks(&bluez_media_control_callbacks,
+ adapter,
+ device,
+ endpoint,
+ NULL,
+ BLUEZ_EVENT_REMOVE,
+ NULL);
+ g_free(adapter);
+ g_free(device);
+ g_free(endpoint);
+
+ } else if (is_mediaplayer1_interface(path)) {
+ gchar *adapter = bluez_return_adapter(path);
+ gchar *device = bluez_return_device(path);
+ gchar *player = find_index(path, 5);
+ DEBUG("media player removed!");
+ DEBUG("media player = %s", player);
+ run_media_callbacks(&bluez_media_player_callbacks,
+ adapter,
+ device,
+ NULL,
+ player,
+ BLUEZ_EVENT_REMOVE,
+ NULL);
+ g_free(adapter);
+ g_free(device);
+ g_free(player);
+
+ } else if (split_length(path) == 4) {
+ /* adapter removal */
+ DEBUG("adapter removed!");
+ gchar *adapter = bluez_return_adapter(path);
+ run_callbacks(&bluez_adapter_callbacks, adapter, NULL, BLUEZ_EVENT_REMOVE, NULL);
+ g_free(adapter);
+
+ } else if (split_length(path) == 5) {
+ /* device removal */
+ DEBUG("device removed!");
+ gchar *adapter = bluez_return_adapter(path);
+ gchar *device = bluez_return_device(path);
+ run_callbacks(&bluez_device_callbacks, adapter, device, BLUEZ_EVENT_REMOVE, NULL);
+ g_free(adapter);
+ g_free(device);
+
+ }
+ } else if (!g_strcmp0(signal_name, "PropertiesChanged")) {
+
+ g_variant_get(parameters, "(&s@a{sv}@as)", &path, &var, NULL);
+
+ if (!g_strcmp0(path, BLUEZ_DEVICE_INTERFACE)) {
+ gchar *adapter = bluez_return_adapter(object_path);
+ gchar *device = bluez_return_device(object_path);
+ run_callbacks(&bluez_device_callbacks, adapter, device, BLUEZ_EVENT_CHANGE, var);
+ g_free(adapter);
+ g_free(device);
+
+ } else if (!g_strcmp0(path, BLUEZ_ADAPTER_INTERFACE)) {
+ DEBUG("adapter changed!");
+ gchar *adapter = bluez_return_adapter(object_path);
+ run_callbacks(&bluez_adapter_callbacks, adapter, NULL, BLUEZ_EVENT_CHANGE, var);
+ g_free(adapter);
+
+ } else if (!g_strcmp0(path, BLUEZ_MEDIAPLAYER_INTERFACE)) {
+ gchar *adapter = bluez_return_adapter(object_path);
+ gchar *device = bluez_return_device(object_path);
+ gchar *player = find_index(object_path, 5);
+ DEBUG("media player changed!");
+ DEBUG("media player = %s", player);
+ run_media_callbacks(&bluez_media_player_callbacks,
+ adapter,
+ device,
+ NULL,
+ player,
+ BLUEZ_EVENT_CHANGE,
+ var);
+ g_free(adapter);
+ g_free(device);
+ g_free(player);
+
+ } else if (!g_strcmp0(path, BLUEZ_MEDIATRANSPORT_INTERFACE)) {
+ gchar *adapter = bluez_return_adapter(object_path);
+ gchar *device = bluez_return_device(object_path);
+ gchar *endpoint = bluez_return_endpoint(object_path);
+ DEBUG("media endpoint changed!");
+ DEBUG("media endpoint %s", endpoint);
+ run_media_callbacks(&bluez_media_control_callbacks,
+ adapter,
+ device,
+ endpoint,
+ NULL,
+ BLUEZ_EVENT_REMOVE,
+ var);
+ g_free(adapter);
+ g_free(device);
+ g_free(endpoint);
+
+ }
+ g_variant_unref(var);
+ }
+}
+
+// Returns number of adapters present
+static int bluez_select_init_adapter(void)
+{
+ struct bluez_state *ns = bluez_get_state();
+ GArray *adapters = NULL;
+ gboolean rc;
+ int i, n = 0;
+
+ if (!(ns && ns->default_adapter))
+ return 0;
+
+ rc = bluez_get_adapters(&adapters);
+ if (!rc) {
+ return 0;
+ }
+
+ for(i = 0; i < adapters->len; i++) {
+ gchar *adapter = g_array_index(adapters, gchar*, i);
+ if (!g_strcmp0(adapter, ns->default_adapter)) {
+ DEBUG("found default adapter %s", adapter);
+ ns->adapter = ns->default_adapter;
+ break;
+ }
+ }
+ if (adapters->len && i == adapters->len) {
+ /* fallback to 1st available adapter */
+ ns->adapter = g_strdup(g_array_index(adapters, gchar*, 0));
+ INFO("default adapter %s not found, fell back to: %s",
+ ns->default_adapter, ns->adapter);
+ }
+ n = adapters->len;
+
+ // Clean up
+ for(i = 0; i < adapters->len; i++) {
+ g_free(g_array_index(adapters, gchar*, i));
+ }
+ g_array_unref(adapters);
+
+ return n;
+}
+
+static struct bluez_state *bluez_dbus_init(GMainLoop *loop, gboolean autoconnect)
+{
+ struct bluez_state *ns;
+ GError *error = NULL;
+
+ ns = g_try_malloc0(sizeof(*ns));
+ if (!ns) {
+ ERROR("out of memory allocating bluez state");
+ goto err_no_ns;
+ }
+
+ INFO("connecting to dbus");
+
+ ns->loop = loop;
+ ns->conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (!ns->conn) {
+ if (error)
+ g_dbus_error_strip_remote_error(error);
+ ERROR("Cannot connect to D-Bus, %s",
+ error ? error->message : "unspecified");
+ g_error_free(error);
+ goto err_no_conn;
+
+ }
+
+ INFO("connected to dbus");
+
+ ns->device_sub = g_dbus_connection_signal_subscribe(
+ ns->conn,
+ BLUEZ_SERVICE,
+ NULL, /* interface */
+ NULL, /* member */
+ NULL, /* object path */
+ NULL, /* arg0 */
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ bluez_devices_signal_callback,
+ ns,
+ NULL);
+ if (!ns->device_sub) {
+ ERROR("Unable to subscribe to interface signals");
+ goto err_no_device_sub;
+ }
+
+ g_mutex_init(&ns->cw_mutex);
+ ns->next_cw_id = 1;
+
+ if (autoconnect)
+ g_timeout_add_seconds(5, bluez_autoconnect, ns);
+
+ INFO("done");
+ return ns;
+
+err_no_device_sub:
+ g_dbus_connection_close(ns->conn, NULL, NULL, NULL);
+err_no_conn:
+ g_free(ns);
+err_no_ns:
+ return NULL;
+}
+
+static void signal_init_done(struct init_data *id, gboolean rc)
+{
+ g_mutex_lock(&id->mutex);
+ id->init_done = TRUE;
+ id->rc = rc;
+ g_cond_signal(&id->cond);
+ g_mutex_unlock(&id->mutex);
+}
+
+static void bluez_cleanup(struct bluez_state *ns)
+{
+ g_dbus_connection_signal_unsubscribe(ns->conn, ns->device_sub);
+ g_dbus_connection_close(ns->conn, NULL, NULL, NULL);
+ g_free(ns);
+}
+
+typedef struct notifier_data {
+ struct bluez_state *ns;
+ bluez_init_cb_t cb;
+ gpointer cb_data;
+} notifier_data_t;
+
+// Helper function to trigger client init callback once the glib main loop
+// is running.
+static gboolean main_loop_start_notifier(gpointer user_data)
+{
+ if (!user_data) {
+ ERROR("Bad start notifier data!");
+
+ // Probably not much point returning TRUE to try again
+ return FALSE;
+ }
+
+ notifier_data_t *data = (notifier_data_t*) user_data;
+ DEBUG("running init callback");
+ if (data->cb && data->ns) {
+ bluez_init_cb_t cb = data->cb;
+ (*cb)(data->ns->adapter, TRUE, data->cb_data);
+ g_free(data);
+ }
+
+ // Let loop know we can be removed
+ return FALSE;
+}
+
+static gpointer bluez_handler_func(gpointer ptr)
+{
+ struct init_data *id = ptr;
+ struct bluez_state *ns;
+ GMainLoop *loop;
+ int num_adapters = 0;
+ unsigned int delay;
+ unsigned int attempt;
+ notifier_data_t *notifier_data;
+
+ g_atomic_rc_box_acquire(id);
+
+ // Save callback info for later use
+ notifier_data = g_malloc0(sizeof(*notifier_data));
+ if (!notifier_data) {
+ ERROR("Unable to alloc notifier data");
+ goto err_no_loop;
+ }
+ notifier_data->cb = id->cb;
+ notifier_data->cb_data = id->user_data;
+
+ loop = g_main_loop_new(NULL, FALSE);
+ if (!loop) {
+ ERROR("Unable to create main loop");
+ goto err_no_loop;
+ }
+
+ // Do BlueZ D-Bus related init
+ ns = bluez_dbus_init(loop, id->autoconnect);
+ if (!ns) {
+ ERROR("bluez_init() failed");
+ goto err_no_ns;
+ }
+
+ id->ns = ns;
+ bluez_set_state(ns);
+
+ ns->default_adapter = get_default_adapter();
+ if (!ns->default_adapter) {
+ ns->default_adapter = g_strdup(BLUEZ_DEFAULT_ADAPTER);
+ gboolean rc = set_default_adapter(BLUEZ_DEFAULT_ADAPTER);
+ if (!rc)
+ WARNING("Request to save default adapter to persistent storage failed ");
+ }
+
+ if (id->register_agent) {
+ gboolean rc = bluez_register_agent(id);
+ if (!rc) {
+ ERROR("bluetooth_register_agent() failed");
+ goto err_no_agent;
+ }
+ } else {
+ // Let main process know initialization is done
+ signal_init_done(id, TRUE);
+ }
+
+ // Cannot reference id after this point
+ g_atomic_rc_box_release(id);
+
+ // Wait for an adapter to appear
+ num_adapters = 0;
+ delay = 1;
+ attempt = 1;
+ while(num_adapters <= 0) {
+ num_adapters = bluez_select_init_adapter();
+ if (num_adapters > 0)
+ break;
+
+ // Back off querying rate after the first 60 seconds
+ if (attempt++ == 60)
+ delay = 10;
+
+ sleep(delay);
+ }
+
+ if (num_adapters > 0) {
+ notifier_data->ns = ns;
+ g_timeout_add_full(G_PRIORITY_DEFAULT,
+ 0,
+ main_loop_start_notifier,
+ notifier_data,
+ NULL);
+
+ DEBUG("calling g_main_loop_run");
+ g_main_loop_run(loop);
+ } else {
+ ERROR("bluez_select_init_adapter() failed");
+ }
+
+ g_main_loop_unref(ns->loop);
+
+ if (ns->agent_path)
+ bluez_unregister_agent(ns);
+
+ bluez_cleanup(ns);
+ bluez_set_state(NULL);
+
+ return NULL;
+
+err_no_agent:
+ bluez_cleanup(ns);
+
+err_no_ns:
+ g_main_loop_unref(loop);
+
+err_no_loop:
+ if (notifier_data && notifier_data->cb) {
+ (*notifier_data->cb)(NULL, FALSE, notifier_data->cb_data);
+ g_free(notifier_data);
+ }
+
+ signal_init_done(id, FALSE);
+ g_atomic_rc_box_release(id);
+
+ return NULL;
+}
+
+EXPORT gboolean bluez_init(gboolean register_agent,
+ gboolean autoconnect,
+ bluez_init_cb_t cb,
+ gpointer user_data)
+{
+ struct init_data *id = NULL;
+ gint64 end_time;
+ gboolean rc;
+ gboolean init_done;
+
+ id = g_atomic_rc_box_new0(struct init_data);
+ if (!id)
+ return -ENOMEM;
+ g_atomic_rc_box_acquire(id);
+
+ id->register_agent = register_agent;
+ id->autoconnect = autoconnect;
+ //id->init_done = FALSE;
+ id->init_done_cb = signal_init_done;
+ //id->rc = TRUE;
+ id->cb = cb;
+ id->user_data = user_data;
+ g_cond_init(&id->cond);
+ g_mutex_init(&id->mutex);
+
+ g_bluez_thread = g_thread_new("bluez_handler",
+ bluez_handler_func,
+ id);
+
+ INFO("waiting for init done");
+
+ /* wait maximum 10 seconds for init done */
+ end_time = g_get_monotonic_time () + 10 * G_TIME_SPAN_SECOND;
+ g_mutex_lock(&id->mutex);
+ while (!id->init_done) {
+ if (!g_cond_wait_until(&id->cond, &id->mutex, end_time))
+ break;
+ }
+ rc = id->rc;
+ init_done = id->init_done;
+ g_mutex_unlock(&id->mutex);
+ g_atomic_rc_box_release(id);
+
+ if (!init_done) {
+ ERROR("init timeout");
+ return FALSE;
+ }
+
+ if (!rc)
+ ERROR("init thread failed");
+ else
+ INFO("running");
+
+ return rc;
+}
+
+EXPORT char *bluez_get_default_adapter(void)
+{
+ struct bluez_state *ns = bluez_get_state();
+ if (!(ns && ns->adapter)) {
+ return NULL;
+ }
+ return ns->adapter;
+}
+
+EXPORT gboolean bluez_set_default_adapter(const char *adapter,
+ char **adapter_new)
+{
+ gboolean rc = TRUE;
+ char *adapter_default = get_default_adapter();
+
+ if (adapter) {
+ if (adapter_default && g_strcmp0(adapter_default, adapter)) {
+ rc = set_default_adapter(adapter);
+ if (!rc) {
+ WARNING("Request to save default adapter to persistent storage failed");
+ return FALSE;
+ }
+ }
+ if(rc && adapter_new)
+ *adapter_new = g_strdup(adapter);
+ } else if (adapter_default) {
+ *adapter_new = g_strdup(adapter_default);
+ } else {
+ ERROR("No default adapter");
+ rc = FALSE;
+ }
+ return rc;
+}
+
+EXPORT gboolean bluez_get_managed_objects(GVariant **reply)
+{
+ struct bluez_state *ns = bluez_get_state();
+ GError *error = NULL;
+
+ if (!ns) {
+ ERROR("Invalid state");
+ return FALSE;
+ }
+ if (!reply)
+ return FALSE;
+
+ *reply = bluez_get_properties(ns,
+ BLUEZ_AT_OBJECT,
+ BLUEZ_OBJECT_PATH,
+ &error);
+ if (error) {
+ ERROR("bluez_get_properties error: %s", error->message);
+ g_clear_error(&error);
+ *reply = NULL;
+ }
+
+ return TRUE;
+}
+
+EXPORT gboolean bluez_get_adapters(GArray **reply)
+{
+ struct bluez_state *ns = bluez_get_state();
+ GVariant *objects;
+ GError *error = NULL;
+
+ if (!ns) {
+ ERROR("No adapter");
+ return FALSE;
+ }
+ if (!reply)
+ return FALSE;
+
+ objects = bluez_get_properties(ns,
+ BLUEZ_AT_OBJECT,
+ BLUEZ_OBJECT_PATH,
+ &error);
+ if (error) {
+ ERROR("get properties error: %s", error->message);
+ g_error_free(error);
+ *reply = NULL;
+ return FALSE;
+ }
+
+ // Iterate and pull out adapters
+ GVariantIter *array, *array2;
+ GVariant *var = NULL;
+ const char *interface, *path2 = NULL;
+ GArray *adapters = g_array_new(FALSE, FALSE, sizeof(gchar*));
+
+ g_variant_get(objects, "(a{oa{sa{sv}}})", &array);
+ while (g_variant_iter_loop(array, "{oa{sa{sv}}}", &path2, &array2)) {
+ while (g_variant_iter_loop(array2, "{&s@a{sv}}", &interface, &var)) {
+ if (!strcmp(interface, BLUEZ_ADAPTER_INTERFACE)) {
+ gchar *adapter = bluez_return_adapter(path2);
+ g_array_append_val(adapters, adapter);
+ break;
+ }
+ }
+ }
+ if (array2)
+ g_variant_iter_free(array2);
+ if (array)
+ g_variant_iter_free(array);
+
+ *reply = adapters;
+
+ g_variant_unref(objects);
+ return TRUE;
+}
+
+EXPORT gboolean bluez_adapter_get_state(const char *adapter, GVariant **reply)
+{
+ struct bluez_state *ns = bluez_get_state();
+ GError *error = NULL;
+ const char *adapter_path;
+ GVariant *properties = NULL;
+
+ if (!ns || (!adapter && !ns->adapter)) {
+ ERROR("No adapter");
+ return FALSE;
+ }
+ if (!reply)
+ return FALSE;
+
+ adapter_path = BLUEZ_ROOT_PATH(adapter ? adapter : ns->adapter);
+ properties = bluez_get_properties(ns,
+ BLUEZ_AT_ADAPTER,
+ adapter_path,
+ &error);
+ if (error) {
+ ERROR("bluez_get_properties error: %s", error->message);
+ g_error_free(error);
+ *reply = NULL;
+ return FALSE;
+ }
+
+ // Pull properties out of tuple so caller does not have to
+ g_variant_get(properties, "(@a{sv})", reply);
+ g_variant_unref(properties);
+
+ return TRUE;
+}
+
+EXPORT gboolean bluez_adapter_get_devices(const char *adapter, GVariant **reply)
+{
+ struct bluez_state *ns = bluez_get_state();
+ GVariant *objects;
+ GError *error = NULL;
+ const char *target_adapter;
+
+ if (!ns || (!adapter && !ns->adapter)) {
+ ERROR("No adapter");
+ return FALSE;
+ }
+ if (!reply)
+ return FALSE;
+
+ target_adapter = adapter ? adapter : ns->adapter;
+
+ objects = bluez_get_properties(ns,
+ BLUEZ_AT_OBJECT,
+ BLUEZ_OBJECT_PATH,
+ &error);
+ if (error) {
+ ERROR("bluez_get_properties error: %s", error->message);
+ g_error_free(error);
+ *reply = NULL;
+ return FALSE;
+ }
+
+ // Iterate and pull out devices
+ GVariantIter *array, *array2;
+ GVariant *var = NULL;
+ const char *interface, *path2 = NULL;
+ GVariantDict *devices_dict = g_variant_dict_new(NULL);
+ if (!devices_dict) {
+ ERROR("g_variant_dict_new failed");
+ g_variant_unref(objects);
+ *reply = NULL;
+ return FALSE;
+ }
+
+ g_variant_get(objects, "(a{oa{sa{sv}}})", &array);
+ while (g_variant_iter_loop(array, "{oa{sa{sv}}}", &path2, &array2)) {
+ gchar *device_adapter = bluez_return_adapter(path2);
+ if (!device_adapter || strcmp(device_adapter, target_adapter) != 0) {
+ g_free(device_adapter);
+ continue;
+ }
+ while (g_variant_iter_loop(array2, "{&s@a{sv}}", &interface, &var)) {
+ if (!strcmp(interface, BLUEZ_DEVICE_INTERFACE)) {
+ gchar *device = bluez_return_device(path2);
+ g_variant_dict_insert_value(devices_dict,
+ device,
+ var);
+ }
+ }
+ }
+ if (array2)
+ g_variant_iter_free(array2);
+ if (array)
+ g_variant_iter_free(array);
+
+ *reply = g_variant_dict_end(devices_dict);
+ g_variant_dict_unref(devices_dict);
+ g_variant_unref(objects);
+
+ return TRUE;
+}
+
+EXPORT gboolean bluez_adapter_set_discovery(const char *adapter,
+ gboolean scan)
+{
+ struct bluez_state *ns = bluez_get_state();
+ GError *error = NULL;
+
+ if (!ns || (!adapter && !ns->adapter)) {
+ ERROR("No adapter");
+ return FALSE;
+ }
+
+ adapter = BLUEZ_ROOT_PATH(adapter ? adapter : ns->adapter);
+
+ GVariant *reply = bluez_call(ns, BLUEZ_AT_ADAPTER, adapter,
+ scan ? "StartDiscovery" : "StopDiscovery",
+ NULL, &error);
+ if (!reply) {
+ ERROR("adapter %s method %s error: %s",
+ adapter, "Scan", BLUEZ_ERRMSG(error));
+ g_error_free(error);
+ return FALSE;
+ }
+ g_variant_unref(reply);
+
+ return TRUE;
+}
+
+EXPORT gboolean bluez_adapter_set_discovery_filter(const char *adapter,
+ gchar **uuids,
+ gchar *transport)
+{
+ struct bluez_state *ns = bluez_get_state();
+ GError *error = NULL;
+ GVariantBuilder builder;
+ GVariant *filter, *reply;
+
+ if (!ns || (!adapter && !ns->adapter)) {
+ ERROR("No adapter");
+ return FALSE;
+ }
+
+ if (!(uuids || transport)) {
+ return FALSE;
+ }
+
+ adapter = BLUEZ_ROOT_PATH(adapter ? adapter : ns->adapter);
+
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+
+ if (uuids && *uuids != NULL) {
+ g_variant_builder_add(&builder, "{sv}", "UUIDs",
+ g_variant_new_strv((const gchar * const *) uuids, -1));
+ }
+
+ if (transport) {
+ g_variant_builder_add(&builder, "{sv}", "Transport",
+ g_variant_new_string(transport));
+ }
+
+ filter = g_variant_builder_end(&builder);
+
+ reply = bluez_call(ns, BLUEZ_AT_ADAPTER, adapter,
+ "SetDiscoveryFilter",
+ g_variant_new("(@a{sv})", filter), &error);
+ if (!reply) {
+ ERROR("adapter %s method %s error: %s",
+ adapter, "SetDiscoveryFilter", BLUEZ_ERRMSG(error));
+ g_error_free(error);
+ return FALSE;
+ }
+
+ g_variant_unref(reply);
+
+ return TRUE;
+}
+
+EXPORT gboolean bluez_adapter_set_discoverable(const char *adapter,
+ gboolean discoverable)
+{
+ struct bluez_state *ns = bluez_get_state();
+ GError *error = NULL;
+ gboolean rc;
+
+ if (!ns || (!adapter && !ns->adapter)) {
+ ERROR("No adapter");
+ return FALSE;
+ }
+
+ adapter = BLUEZ_ROOT_PATH(adapter ? adapter : ns->adapter);
+
+ DEBUG("discoverable = %d", (int) discoverable);
+ rc = bluez_set_boolean_property(ns, BLUEZ_AT_ADAPTER, adapter,
+ "Discoverable", discoverable, &error);
+ if (!rc) {
+ ERROR("adapter %s set_property %s error: %s",
+ adapter, "Discoverable", BLUEZ_ERRMSG(error));
+ g_error_free(error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+EXPORT gboolean bluez_adapter_set_powered(const char *adapter,
+ gboolean powered)
+{
+ struct bluez_state *ns = bluez_get_state();
+ GError *error = NULL;
+ gboolean rc;
+
+ if (!ns || (!adapter && !ns->adapter)) {
+ ERROR("No adapter");
+ return FALSE;
+ }
+
+ adapter = BLUEZ_ROOT_PATH(adapter ? adapter : ns->adapter);
+
+ rc = bluez_set_boolean_property(ns, BLUEZ_AT_ADAPTER, adapter,
+ "Powered", powered, &error);
+ if (!rc) {
+ ERROR("adapter %s set_property %s error: %s",
+ adapter, "Powered", BLUEZ_ERRMSG(error));
+ g_error_free(error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gchar *get_bluez_path(const char *adapter, const char *device)
+{
+ struct bluez_state *ns = bluez_get_state();
+ const char *tmp;
+
+ if (!ns || (!adapter && !ns->adapter))
+ return NULL;
+
+ if (!device)
+ return NULL;
+
+ tmp = device;
+
+ /* Stop the dbus call from segfaulting from special characters */
+ for (; *tmp; tmp++) {
+ if (!g_ascii_isalnum(*tmp) && *tmp != '_') {
+ ERROR("Invalid device parameter");
+ return NULL;
+ }
+ }
+
+ call_work_lock(ns);
+ adapter = adapter ? adapter : ns->adapter;
+ call_work_unlock(ns);
+
+ return g_strconcat("/org/bluez/", adapter, "/", device, NULL);
+}
+
+static void connect_service_callback(void *user_data,
+ GVariant *result,
+ GError **error)
+{
+ struct call_work *cw = user_data;
+ struct bluez_state *ns = cw->ns;
+ gboolean status = TRUE;
+
+ bluez_decode_call_error(ns,
+ cw->access_type, cw->type_arg, cw->bluez_method,
+ error);
+ if (error && *error) {
+ ERROR("Connect error: %s", (*error)->message);
+ status = FALSE;
+ }
+
+ if (result)
+ g_variant_unref(result);
+
+ // Run callback
+ if (cw->request_cb) {
+ bluez_device_connect_cb_t cb = (bluez_device_connect_cb_t) cw->request_cb;
+ gchar *device = g_strdup(cw->type_arg);
+ (*cb)(device, status, cw->request_user_data);
+ }
+
+ call_work_destroy(cw);
+}
+
+EXPORT gboolean bluez_device_connect(const char *device,
+ const char *uuid,
+ bluez_device_connect_cb_t cb,
+ gpointer user_data)
+{
+ gboolean rc = TRUE;
+ struct bluez_state *ns = bluez_get_state();
+ GError *error = NULL;
+ struct call_work *cw;
+ gchar *device_path;
+
+ device_path = get_bluez_path(NULL, device);
+ if (!device) {
+ ERROR("No path given");
+ return FALSE;
+ }
+
+ cw = call_work_create(ns, BLUEZ_AT_DEVICE, device_path,
+ "connect_service", "Connect", &error);
+ if (!cw) {
+ ERROR("can't queue work %s", error->message);
+ g_error_free(error);
+ rc = FALSE;
+ goto out_free;
+ }
+
+ // Set callback hook
+ cw->request_cb = cb;
+ cw->request_user_data = user_data;
+
+ if (uuid) {
+ /* connect single profile */
+ cw->cpw = bluez_call_async(ns, BLUEZ_AT_DEVICE, device_path,
+ "ConnectProfile", g_variant_new("(&s)", uuid),
+ &error,
+ connect_service_callback, cw);
+ } else {
+ cw->cpw = bluez_call_async(ns, BLUEZ_AT_DEVICE, device_path,
+ "Connect", NULL,
+ &error,
+ connect_service_callback, cw);
+ }
+ if (!cw->cpw) {
+ ERROR("Connection error: %s", error->message);
+ call_work_destroy(cw);
+ g_error_free(error);
+ rc = FALSE;
+ /* fall-thru */
+ }
+
+out_free:
+ g_free(device_path);
+
+ return rc;
+}
+
+EXPORT gboolean bluez_device_disconnect(const char *device,
+ const char *uuid)
+{
+ gboolean rc = TRUE;
+ struct bluez_state *ns = bluez_get_state();
+ GVariant *reply = NULL;
+ GError *error = NULL;
+ gchar *device_path;
+
+ device_path = get_bluez_path(NULL, device);
+ if (!device_path) {
+ ERROR("No device given to disconnect");
+ return FALSE;
+ }
+ DEBUG("device = %s, device_path = %s", device, device_path);
+
+ if (uuid) {
+ /* Disconnect single profile */
+ reply = bluez_call(ns, BLUEZ_AT_DEVICE, device_path,
+ "DisconnectProfile", g_variant_new("(&s)", uuid),
+ &error);
+ } else {
+ reply = bluez_call(ns, BLUEZ_AT_DEVICE, device_path,
+ "Disconnect", NULL,
+ &error);
+ }
+
+ if (!reply) {
+ ERROR("Disconnect error: %s", BLUEZ_ERRMSG(error));
+ g_error_free(error);
+ rc = FALSE;
+ goto out_free;
+ }
+
+ g_variant_unref(reply);
+
+out_free:
+ g_free(device_path);
+
+ return rc;
+}
+
+static void pair_service_callback(void *user_data,
+ GVariant *result,
+ GError **error)
+{
+ struct call_work *cw = user_data;
+ struct bluez_state *ns = cw->ns;
+ gboolean status = TRUE;
+
+ bluez_decode_call_error(ns,
+ cw->access_type, cw->type_arg, cw->bluez_method,
+ error);
+ if (error && *error) {
+ ERROR("Connect error: %s", (*error)->message);
+ status = FALSE;
+ }
+
+ if (result)
+ g_variant_unref(result);
+
+ // Run callback
+ if (cw->request_cb) {
+ bluez_device_pair_cb_t cb = (bluez_device_pair_cb_t) cw->request_cb;
+ gchar *device = g_strdup(cw->type_arg);
+ (*cb)(device, status, user_data);
+ }
+
+ call_work_destroy(cw);
+}
+
+EXPORT gboolean bluez_device_pair(const char *device,
+ bluez_device_pair_cb_t cb,
+ gpointer user_data)
+{
+ gboolean rc = TRUE;
+ struct bluez_state *ns = bluez_get_state();
+ GError *error = NULL;
+ gchar *device_path;
+ struct call_work *cw;
+
+ device_path = get_bluez_path(NULL, device);
+ if (!device_path) {
+ ERROR("No path given");
+ return FALSE;
+ }
+
+ cw = call_work_create(ns, BLUEZ_AT_DEVICE, device_path,
+ "pair_device", "Pair",
+ &error);
+ if (!cw) {
+ ERROR("can't queue work %s", error->message);
+ g_error_free(error);
+ rc = FALSE;
+ goto out_free;
+ }
+
+ // Set callback hook
+ cw->request_cb = cb;
+ cw->request_user_data = user_data;
+
+ cw->agent_data.fixed_pincode = get_pincode();
+
+ cw->cpw = bluez_call_async(ns, BLUEZ_AT_DEVICE, device_path,
+ "Pair", NULL,
+ &error,
+ pair_service_callback, cw);
+ if (!cw->cpw) {
+ ERROR("Pairing error: %s", error->message);
+ call_work_destroy(cw);
+ g_error_free(error);
+ rc = FALSE;
+ goto out_free;
+ }
+
+out_free:
+ g_free(device_path);
+
+ return rc;
+}
+
+EXPORT gboolean bluez_cancel_pairing(void)
+{
+ struct bluez_state *ns = bluez_get_state();
+ struct call_work *cw;
+ GVariant *reply = NULL;
+ GError *error = NULL;
+ gchar *device_path;
+
+ call_work_lock(ns);
+
+ cw = call_work_lookup_unlocked(ns, "device", NULL, "RequestConfirmation");
+ if (!cw) {
+ call_work_unlock(ns);
+ ERROR("No pairing in progress");
+ return FALSE;
+ }
+
+ device_path = cw->agent_data.device_path;
+ reply = bluez_call(ns, BLUEZ_AT_DEVICE, device_path,
+ "CancelPairing", NULL,
+ &error);
+ if (!reply) {
+ call_work_unlock(ns);
+ ERROR("device %s method %s error: %s",
+ device_path, "CancelPairing", error->message);
+ g_error_free(error);
+ return FALSE;
+ }
+
+ call_work_unlock(ns);
+
+ return TRUE;
+}
+
+EXPORT gboolean bluez_confirm_pairing(const char *pincode_str)
+{
+ gboolean rc = TRUE;
+ struct bluez_state *ns = bluez_get_state();
+ struct call_work *cw;
+ int pin = -1;
+
+ if (pincode_str)
+ pin = (int) strtol(pincode_str, NULL, 10);
+
+ // GSM - FIXME - this seems broken wrt a pin of all zeroes?
+ if (!pincode_str || !pin) {
+ ERROR("No pincode parameter");
+ return FALSE;
+ }
+
+ call_work_lock(ns);
+ cw = call_work_lookup_unlocked(ns, "device", NULL, "RequestConfirmation");
+ if (!cw) {
+ call_work_unlock(ns);
+ ERROR("No pairing in progress");
+ return FALSE;
+ }
+
+ if (pin == cw->agent_data.pin_code) {
+ g_dbus_method_invocation_return_value(cw->invocation, NULL);
+ INFO("pairing confirmed");
+ } else {
+ g_dbus_method_invocation_return_dbus_error(cw->invocation,
+ "org.bluez.Error.Rejected",
+ "No connection pending");
+ ERROR("pairing failed");
+ rc = FALSE;
+ }
+
+ call_work_destroy_unlocked(cw);
+ call_work_unlock(ns);
+ return rc;
+}
+
+EXPORT gboolean bluez_device_remove(const char *device)
+{
+ gboolean rc = TRUE;
+ struct bluez_state *ns = bluez_get_state();
+ GVariant *reply;
+ GError *error = NULL;
+ const char *adapter = NULL;
+
+ gchar *device_path = get_bluez_path(NULL, device);
+ if (!device_path) {
+ ERROR("No path given");
+ return FALSE;
+ }
+
+ GVariant *val = bluez_get_property(ns, BLUEZ_AT_DEVICE, device_path, "Adapter", &error);
+ if (error) {
+ // This can happen if a device is removed while pairing, so unless
+ // an additional check for device presence is done first, it should
+ // not be logged as a definite error.
+ WARNING("adapter not found for device %s error: %s",
+ device, error->message);
+ g_error_free(error);
+ rc = FALSE;
+ goto out_free;
+ }
+
+ adapter = g_variant_get_string(val, NULL);
+ if (!adapter) {
+ ERROR("adapter invalid for device %s", device);
+ rc = FALSE;
+ goto out_free_val;
+ }
+
+ reply = bluez_call(ns, BLUEZ_AT_ADAPTER, adapter,
+ "RemoveDevice", g_variant_new("(o)", device_path),
+ &error);
+ if (error) {
+ ERROR("device %s method %s error: %s",
+ device_path, "RemoveDevice", error->message);
+ g_error_free(error);
+ rc = FALSE;
+ goto out_free_val;
+ }
+ g_variant_unref(reply);
+
+ INFO("device %s removed", device_path);
+
+out_free_val:
+ g_variant_unref(val);
+out_free:
+ g_free(device_path);
+
+ return rc;
+}
+
+static void mediaplayer1_connect_disconnect(struct bluez_state *ns,
+ const gchar *player,
+ int state)
+{
+ GVariant *reply;
+ gchar *path = g_strdup(player);
+ const char *uuids[] = {
+ "0000110a-0000-1000-8000-00805f9b34fb",
+ "0000110e-0000-1000-8000-00805f9b34fb",
+ NULL
+ };
+ const char **tmp = (const char **) uuids;
+
+ *g_strrstr(path, "/") = '\0';
+
+ for (; *tmp; tmp++) {
+ reply = bluez_call(ns, BLUEZ_AT_DEVICE, path,
+ state ? "ConnectProfile" : "DisconnectProfile",
+ g_variant_new("(&s)", *tmp), NULL);
+ if (!reply)
+ break;
+ g_variant_unref(reply);
+ }
+
+ g_free(path);
+}
+
+EXPORT gboolean bluez_device_avrcp_controls(const char *device,
+ bluez_media_control_t action)
+{
+ struct bluez_state *ns = bluez_get_state();
+ const char *action_names[BLUEZ_MEDIA_CONTROL_REWIND + 1] = {
+ "Connect", "Disconnect", "Play", "Pause", "Stop",
+ "Next", "Previous", "FastForward", "Rewind"
+ };
+ const char *action_str = action_names[action];
+
+ gchar *player = NULL;
+ gchar *device_path = get_bluez_path(NULL, device);
+ if (device_path) {
+ // TODO: handle multiple players per device
+ GVariant *val = bluez_get_property(ns, BLUEZ_AT_MEDIACONTROL, device_path, "Player", NULL);
+ if (val) {
+ player = g_variant_get_string(val, NULL);
+ g_variant_unref(val);
+ }
+ if (!player)
+ player = g_strconcat(device_path, "/", BLUEZ_DEFAULT_PLAYER, NULL);
+
+ g_free(device_path);
+ } else {
+ player = g_strdup(ns->mediaplayer_path);
+ }
+
+ if (!player) {
+ ERROR("No path given");
+ return FALSE;
+ }
+
+ if (action == BLUEZ_MEDIA_CONTROL_CONNECT || action == BLUEZ_MEDIA_CONTROL_DISCONNECT) {
+ mediaplayer1_connect_disconnect(ns,
+ player,
+ action == BLUEZ_MEDIA_CONTROL_CONNECT);
+ goto out_success;
+ }
+
+ GError *error = NULL;
+ GVariant *reply = bluez_call(ns, BLUEZ_AT_MEDIAPLAYER, player, action_str, NULL, &error);
+ if (!reply) {
+ ERROR("mediaplayer %s method %s error: %s",
+ player, action_str, BLUEZ_ERRMSG(error));
+ g_free(player);
+ g_error_free(error);
+ return FALSE;
+ }
+ g_variant_unref(reply);
+
+out_success:
+ g_free(player);
+
+ return TRUE;
+}
+
+EXPORT gboolean bluez_set_pincode(const char *pincode)
+{
+ gboolean rc = TRUE;
+ char *error = NULL;
+
+ rc = set_pincode(pincode, &error);
+ if (rc) {
+ ERROR("%s", error);
+ }
+
+ return rc;
+}