summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWim Taymans <wtaymans@redhat.com>2014-08-06 22:16:56 +0200
committerWim Taymans <wtaymans@redhat.com>2014-08-06 22:16:56 +0200
commita6d2b7900a79b54d228141891013c5e360a38a5b (patch)
treea8ebf0b9d71799c0a7a9b2753bd8a8e2f72df16a /src
Initial commit
Diffstat (limited to 'src')
-rw-r--r--src/headsetd-old.c785
-rw-r--r--src/headsetd.c100
-rw-r--r--src/hsd-headset-transport.c339
-rw-r--r--src/hsd-headset-transport.h42
-rw-r--r--src/hsd-headset.c254
-rw-r--r--src/hsd-headset.h32
-rw-r--r--src/hsd-manager.c222
-rw-r--r--src/hsd-manager.h35
-rw-r--r--src/hsd-profile.c247
-rw-r--r--src/hsd-profile.h47
-rw-r--r--src/hsd.c35
-rw-r--r--src/hsd.h30
12 files changed, 2168 insertions, 0 deletions
diff --git a/src/headsetd-old.c b/src/headsetd-old.c
new file mode 100644
index 0000000..7f01b72
--- /dev/null
+++ b/src/headsetd-old.c
@@ -0,0 +1,785 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sco.h>
+
+
+#define HFP_AG_UUID "0000111f-0000-1000-8000-00805f9b34fb"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.bluez.Profile1'>"
+ " <method name='Release'>"
+ " </method>"
+ " <method name='Cancel'>"
+ " </method>"
+ " <method name='RequestDisconnection'>"
+ " <arg type='o' name='device' direction='in'/>"
+ " </method>"
+ " <method name='NewConnection'>"
+ " <arg type='o' name='device' direction='in'/>"
+ " <arg type='h' name='fd' direction='in'/>"
+ " <arg type='a{sv}' name='opts' direction='in'/>"
+ " </method>"
+ " </interface>"
+ " <interface name='org.bluez.MediaTransport1'>"
+ " <method name='Release'>"
+ " </method>"
+ " <method name='Acquire'>"
+ " <arg name='fd' direction='out' type='h'/>"
+ " <arg name='mtu_r' direction='out' type='q'/>"
+ " <arg name='mtu_w' direction='out' type='q'/>"
+ " </method>"
+ " <method name='TryAcquire'>"
+ " <arg name='fd' direction='out' type='h'/>"
+ " <arg name='mtu_r' direction='out' type='q'/>"
+ " <arg name='mtu_w' direction='out' type='q'/>"
+ " </method>"
+ " <property type='s' name='State' access='read'/>"
+ " </interface>"
+ "</node>";
+
+typedef struct {
+ GDBusConnection *conn;
+ GDBusProxy *manager;
+
+ gchar *uuid;
+ gchar *name;
+ guint id;
+
+ GHashTable *objs;
+} Profile;
+
+typedef struct {
+ Profile *profile;
+
+ gchar *obj;
+ gint fd;
+ guint id;
+ gchar *state;
+
+ gchar *owner;
+ gchar *spath;
+
+ gchar *src_addr;
+ gchar *dst_addr;
+
+ GVariantIter *props;
+ gchar *name;
+
+ GIOChannel *channel;
+} MediaTransport;
+
+static void send_set_configuration_reply (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ GError *error = NULL;
+ GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
+
+ g_dbus_connection_call_finish (connection, res, &error);
+
+ if (error) {
+ g_printerr ("error doing SetConfiguration %s\n", error->message);
+ g_clear_error (&error);
+ }
+}
+
+static void send_set_configuration (MediaTransport *t)
+{
+ /* send transport to endpoint */
+ GVariantBuilder *b;
+
+ b = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+ g_variant_builder_add (b, "{sv}", "UUID", g_variant_new_string ("0000111f-0000-1000-8000-00805f9b34fb"));
+ g_variant_builder_add (b, "{sv}", "Device", g_variant_new_object_path (t->obj));
+
+ g_dbus_connection_call (t->profile->conn,
+ t->owner,
+ t->spath,
+ "org.bluez.MediaEndpoint1",
+ "SetConfiguration",
+ g_variant_new ("(oa{sv})",
+ t->name,
+ b),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ send_set_configuration_reply,
+ t);
+}
+
+static void send_clear_configuration_reply (GObject *source_object, GAsyncResult *res, gpointer user_data)
+{
+ GError *error = NULL;
+ GDBusConnection *connection = G_DBUS_CONNECTION (source_object);
+
+ g_dbus_connection_call_finish (connection, res, &error);
+
+ if (error) {
+ g_printerr ("error doing ClearConfiguration %s\n", error->message);
+ g_clear_error (&error);
+ }
+}
+
+static void send_clear_configuration (MediaTransport *t)
+{
+ /* send transport to endpoint */
+ g_dbus_connection_call (t->profile->conn,
+ t->owner,
+ t->spath,
+ "org.bluez.MediaEndpoint1",
+ "ClearConfiguration",
+ g_variant_new ("(o)",
+ t->name),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ send_clear_configuration_reply,
+ t);
+}
+
+static void
+media_transport_configure_endpoint (MediaTransport *t)
+{
+ GVariantIter *props_i = t->props;
+ GVariant *value;
+ gchar *key;
+
+ /* now go over all registered endpoints and inform them about the
+ * new transport object */
+ while (g_variant_iter_next (props_i, "{&sv}", &key, &value)) {
+ g_print (" prop: %s\n", key);
+
+ if (g_str_equal (key, "MediaEndpoints")) {
+ GVariantIter *endpoints_i;
+ gchar *owner;
+ GVariant *oprops;
+
+ g_variant_get (value, "a{sv}", &endpoints_i);
+
+ while (g_variant_iter_next (endpoints_i, "{sv}", &owner, &oprops)) {
+ GVariantIter *oprops_i;
+ gchar *pname;
+ const gchar *spath;
+ GVariant *pval;
+
+ g_print (" owner: %s\n", owner);
+
+ g_variant_get (oprops, "a{sv}", &oprops_i);
+
+ while (g_variant_iter_next (oprops_i, "{&sv}", &pname, &pval)) {
+ if (g_variant_is_of_type (pval, G_VARIANT_TYPE_OBJECT_PATH)) {
+ t->spath = g_variant_dup_string (pval, NULL);
+ g_print (" key: %s object-path=%s\n", pname, spath);
+ } else if (g_variant_is_of_type (pval, G_VARIANT_TYPE_BYTE)) {
+ g_print (" key: %s byte=%c\n", pname, g_variant_get_byte (pval));
+ } else if (g_variant_is_of_type (pval, G_VARIANT_TYPE_ARRAY)) {
+ g_print (" key: %s array\n", pname);
+ } else if (g_variant_is_of_type (pval, G_VARIANT_TYPE_STRING)) {
+ g_print (" key: %s string=%s\n", pname, g_variant_get_string (pval, NULL));
+ } else {
+ g_print (" key: %s (%s)\n", pname, g_variant_get_type_string (pval));
+ }
+
+ g_variant_unref (pval);
+ }
+
+ t->owner = owner;
+
+ send_set_configuration (t);
+
+ g_variant_iter_free (oprops_i);
+ g_variant_unref (oprops);
+ }
+ g_variant_iter_free (endpoints_i);
+ }
+ g_variant_unref (value);
+ }
+ g_variant_iter_free (props_i);
+}
+
+static gboolean
+rfcomm_io_cb (GIOChannel *source, GIOCondition condition, gpointer data)
+{
+ gchar buf[512];
+ GIOStatus st;
+ MediaTransport *t = data;
+
+ g_print ("condition %d\n", condition);
+
+ if (condition & (G_IO_ERR | G_IO_HUP))
+ return FALSE;
+
+ if (condition & G_IO_IN) {
+ gint fd = g_io_channel_unix_get_fd (source);
+ gsize length;
+
+ g_print ("we can read\n", condition);
+
+ g_io_channel_read_chars (source, buf, 512, &length, NULL);
+ buf[length] = 0;
+ g_print ("%d %s\n", length, buf);
+
+ if (g_str_has_prefix (buf, "AT+BRSF=")) {
+ write (fd, "\r\n+BRSF:254\r\n", 13);
+ write (fd, "\r\nOK\r\n", 5);
+ }
+ else if (g_str_has_prefix (buf, "AT+CIND=?")) {
+ write (fd, "\r\n+CIND:254\r\n", 13);
+ write (fd, "\r\nOK\r\n", 5);
+ }
+ else if (g_str_has_prefix (buf, "AT+CIND?")) {
+ write (fd, "\r\n+CIND:254\r\n", 13);
+ write (fd, "\r\nOK\r\n", 5);
+ }
+ else if (g_str_has_prefix (buf, "AT+CMER=")) {
+ write (fd, "\r\n+VGM:15\r\n", 11);
+ write (fd, "\r\n+VGS:15\r\n", 11);
+ write (fd, "\r\nOK\r\n", 5);
+ }
+ else if (g_str_has_prefix (buf, "AT+CMEE=")) {
+ write (fd, "\r\nOK\r\n", 5);
+ media_transport_configure_endpoint (t);
+ }
+ else {
+ write (fd, "\r\nOK\r\n", 5);
+ }
+ }
+ return TRUE;
+}
+
+static void
+transport_set_state (MediaTransport *t, const gchar *state)
+{
+ GVariantBuilder *builder;
+ GVariantBuilder *invalidated_builder;
+ GError *error;
+
+ error = NULL;
+ builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY);
+ invalidated_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
+ g_variant_builder_add (builder,
+ "{sv}",
+ "State",
+ g_variant_new_string (state));
+ g_dbus_connection_emit_signal (t->profile->conn,
+ NULL,
+ t->name,
+ "org.freedesktop.DBus.Properties",
+ "PropertiesChanged",
+ g_variant_new ("(sa{sv}as)",
+ "org.bluez.MediaTransport1",
+ builder,
+ invalidated_builder),
+ &error);
+ g_assert_no_error (error);
+}
+
+static void
+transport_acquire (MediaTransport *t,
+ gboolean optional,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ GUnixFDList *fdlist;
+ GDBusMessage *reply;
+ GError *error;
+ guchar *blob;
+ gsize out_size;
+ struct sockaddr_sco addr;
+ int err, i;
+ GIOCondition cond;
+ GIOChannel *io;
+ bdaddr_t src;
+ bdaddr_t dst;
+ int voice = 0x60;
+ socklen_t len;
+ gchar *src_addr;
+ gchar *dst_addr;
+
+ src_addr = t->src_addr;
+ dst_addr = t->dst_addr;
+
+ for (i = 5; i >= 0; i--, src_addr += 3)
+ src.b[i] = strtol(src_addr, NULL, 16);
+ for (i = 5; i >= 0; i--, dst_addr += 3)
+ dst.b[i] = strtol(dst_addr, NULL, 16);
+
+ t->fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
+ if (t->fd < 0) {
+ g_printerr("socket(SEQPACKET, SCO)");
+ return;
+ }
+
+ g_print ("got fd %d\n", t->fd);
+ transport_set_state (t, "pending");
+
+ fdlist = g_unix_fd_list_new ();
+ g_unix_fd_list_append (fdlist, t->fd, NULL);
+ g_dbus_method_invocation_return_value_with_unix_fd_list (
+ invocation, g_variant_new ("(hqq)", 0, 48, 48), fdlist);
+
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sco_family = AF_BLUETOOTH;
+ bacpy(&addr.sco_bdaddr, &src);
+
+ if (bind(t->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ perror("bind()");
+ return;
+ }
+
+ if (voice) {
+ struct bt_voice opts;
+
+ /* SCO voice setting */
+ memset(&opts, 0, sizeof(opts));
+ opts.setting = voice;
+ if (setsockopt(t->fd, SOL_BLUETOOTH, BT_VOICE, &opts, sizeof(opts)) < 0) {
+ perror("setsockopt()");
+ return;
+ }
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sco_family = AF_BLUETOOTH;
+ bacpy(&addr.sco_bdaddr, &dst);
+
+ g_printerr ("doing connect\n");
+ err = connect(t->fd, (struct sockaddr *) &addr, sizeof(addr));
+ if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) {
+ perror("connect()");
+ return;
+ }
+
+ g_printerr ("connected\n");
+
+ transport_set_state (t, "active");
+}
+
+static void
+transport_release (MediaTransport *t,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+
+ g_print ("close fd %d\n", t->fd);
+
+ if (t->fd != -1) {
+ shutdown (t->fd, SHUT_RDWR);
+ close (t->fd);
+ }
+ t->fd = -1;
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+
+ transport_set_state (t, "idle");
+}
+
+static void
+transport_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ MediaTransport *t = user_data;
+
+ if (g_strcmp0 (method_name, "Acquire") == 0) {
+ transport_acquire (t, TRUE, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "TryAcquire") == 0) {
+ transport_acquire (t, FALSE, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "Release") == 0) {
+ transport_release (t, connection, parameters, invocation);
+ }
+}
+
+static GVariant *
+transport_get_property (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *property_name,
+ GError **error,
+ gpointer user_data)
+{
+ MediaTransport *t = user_data;
+ GVariant *ret;
+
+ ret = NULL;
+ if (g_strcmp0 (property_name, "State") == 0) {
+ ret = g_variant_new_string (t->state);
+ }
+ return ret;
+}
+
+static const GDBusInterfaceVTable transport_interface_vtable =
+{
+ transport_method_call,
+ transport_get_property,
+ NULL
+};
+
+static MediaTransport *
+media_transport_free (MediaTransport *t)
+{
+ g_print ("media transport free %p\n", t);
+
+ send_clear_configuration (t);
+
+ if (t->id)
+ g_dbus_connection_unregister_object (t->profile->conn, t->id);
+ if (t->channel)
+ g_io_channel_unref (t->channel);
+ g_free (t->owner);
+ g_free (t->spath);
+ g_free (t->src_addr);
+ g_free (t->dst_addr);
+ g_free (t->obj);
+ g_free (t);
+}
+
+static MediaTransport *
+media_transport_new (Profile *p, const gchar *obj, gint fd, GVariantIter *props)
+{
+ MediaTransport *t;
+ GError *error = NULL;
+ GVariant *res, *var;
+ const gchar *adapter;
+
+ t = g_new0 (MediaTransport, 1);
+ t->profile = p;
+ t->obj = g_strdup (obj);
+ t->fd = fd;
+ t->props = props;
+ t->state = "idle";
+
+ g_print ("new media transport %p\n", t);
+
+ /* make a new transport object to handle the setup of the SCO connection */
+ t->name = g_strdup_printf ("%s/fd%d", obj, fd);
+ t->id = g_dbus_connection_register_object (p->conn,
+ t->name,
+ introspection_data->interfaces[1],
+ &transport_interface_vtable,
+ t,
+ NULL, /* user_data_free_func */
+ &error); /* GError** */
+ if (t->id == 0) {
+ g_printerr ("error registering object %s\n", error->message);
+ media_transport_free (t);
+ return NULL;
+ }
+
+ res = g_dbus_connection_call_sync (p->conn,
+ "org.bluez",
+ obj,
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ "org.bluez.Device1",
+ "Address"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (res == NULL) {
+ g_printerr ("Can't get Device Address", error->message);
+ return NULL;
+ }
+ g_variant_get (res, "(v)", &var);
+ t->dst_addr = g_variant_dup_string (var, NULL);
+ g_variant_unref (var);
+ g_print ("device addres %s\n", t->dst_addr);
+
+ res = g_dbus_connection_call_sync (p->conn,
+ "org.bluez",
+ obj,
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ "org.bluez.Device1",
+ "Adapter"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (res == NULL) {
+ g_printerr ("Can't get Adapter", error->message);
+ return NULL;
+ }
+ g_variant_get (res, "(v)", &var);
+ adapter = g_variant_get_string (var, NULL);
+ g_print ("device adapter %s\n", adapter);
+
+ res = g_dbus_connection_call_sync (p->conn,
+ "org.bluez",
+ adapter,
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)",
+ "org.bluez.Adapter1",
+ "Address"),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ g_variant_unref (var);
+
+ if (res == NULL) {
+ g_printerr ("Can't get Adapter Address", error->message);
+ return NULL;
+ }
+ g_variant_get (res, "(v)", &var);
+ t->src_addr = g_variant_dup_string (var, NULL);
+ g_variant_unref (var);
+ g_print ("adapter addres %s\n", t->src_addr);
+
+ t->channel = g_io_channel_unix_new (fd);
+ g_io_channel_set_close_on_unref (t->channel, TRUE);
+ g_io_add_watch (t->channel, G_IO_IN | G_IO_ERR | G_IO_HUP, rfcomm_io_cb, t);
+
+ g_hash_table_insert (p->objs, (gpointer) obj, t);
+
+ return t;
+}
+
+static void
+profile_new_connection (Profile *p,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ gchar *obj;
+ gint32 fd;
+ GVariantIter *props;
+ GDBusMessage *message;
+ GUnixFDList * fdlist;
+ MediaTransport *t;
+
+ g_variant_get (parameters, "(oha{sv})", &obj, &fd, &props);
+
+ message = g_dbus_method_invocation_get_message (invocation);
+ fdlist = g_dbus_message_get_unix_fd_list (message);
+ fd = g_unix_fd_list_get (fdlist, fd, NULL);
+
+ g_print ("NewConnection %s %d\n", obj, fd);
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+
+ /* make a new transport object to handle the setup of the SCO connection */
+ t = media_transport_new (p, obj, fd, props);
+}
+
+static void
+profile_request_disconnection (Profile *p,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ gchar *obj;
+ MediaTransport *t;
+
+ g_variant_get (parameters, "(o)", &obj);
+ g_print ("RequestDisconnection %s\n", obj);
+
+ t = g_hash_table_lookup (p->objs, obj);
+ if (t == NULL) {
+ g_warning ("unknown transport object %s", obj);
+ goto done;
+ }
+
+ g_hash_table_remove (p->objs, obj);
+ media_transport_free (t);
+
+done:
+ g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+profile_release (Profile *p,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+profile_cancel (Profile *p,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+profile_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ Profile *p = user_data;
+
+ if (g_strcmp0 (method_name, "Release") == 0) {
+ profile_release (p, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "Cancel") == 0) {
+ profile_cancel (p, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "RequestDisconnection") == 0) {
+ profile_request_disconnection (p, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "NewConnection") == 0) {
+ profile_new_connection (p, connection, parameters, invocation);
+ } else
+ g_print ("Unhandled %s\n", method_name);
+}
+
+static const GDBusInterfaceVTable profile_interface_vtable =
+{
+ profile_method_call,
+ NULL,
+ NULL
+};
+
+static void profile_free (Profile *p)
+{
+ if (p->id)
+ g_dbus_connection_unregister_object (p->conn, p->id);
+ g_free (p->uuid);
+ g_free (p->name);
+ g_hash_table_unref (p->objs);
+ g_free (p);
+}
+
+static Profile *
+register_profile (GDBusConnection *connection, GDBusProxy * manager, const gchar *uuid)
+{
+ GError *error = NULL;
+ guint registration_id;
+ Profile *profile;
+ GVariant *res;
+
+ profile = g_new0 (Profile, 1);
+ profile->conn = connection;
+ profile->manager = manager;
+ profile->name = g_strdup_printf ("/org/test/Profile");
+ profile->uuid = g_strdup (uuid);
+ profile->objs = g_hash_table_new (g_str_hash, g_str_equal);
+
+ profile->id = g_dbus_connection_register_object (connection,
+ profile->name,
+ introspection_data->interfaces[0],
+ &profile_interface_vtable,
+ profile,
+ NULL, /* user_data_free_func */
+ &error); /* GError** */
+ if (profile->id == 0) {
+ g_printerr ("error registering object %s\n", error->message);
+ profile_free (profile);
+ return NULL;
+ }
+
+ g_print ("Register profile\n");
+ res = g_dbus_proxy_call_sync (manager,
+ "RegisterProfile",
+ g_variant_new ("(osa{sv})",
+ profile->name,
+ uuid, NULL),
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ &error);
+ if (res == NULL) {
+ g_printerr ("error registering %s\n", error->message);
+ profile_free (profile);
+ return NULL;
+ }
+ g_print ("Profile registered\n");
+ g_variant_unref (res);
+
+ return profile;
+}
+
+int
+main (int argc, char *argv[])
+{
+ GDBusConnection * connection;
+ GError *error = NULL;
+ GMainLoop *loop;
+ guint registration_id;
+ GDBusProxy *manager;
+
+ g_print ("connecting to system bus\n");
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+ if (error != NULL) {
+ g_printerr ("error getting bus: %s", error->message);
+ return -1;
+ }
+
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+ g_assert (introspection_data != NULL);
+
+ g_print ("making proxy for ProfileManager1\n");
+ manager = g_dbus_proxy_new_sync (connection,
+ G_DBUS_PROXY_FLAGS_NONE,
+ NULL, "org.bluez", "/org/bluez", "org.bluez.ProfileManager1",
+ NULL, &error);
+ if (error != NULL) {
+ g_printerr ("error getting ProfileManager1: %s", error->message);
+ return -1;
+ }
+
+ if (register_profile (connection, manager, HFP_AG_UUID) == NULL)
+ return -1;
+
+ g_print ("going into mainloop\n");
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+ g_print ("exit mainloop\n");
+
+ g_dbus_node_info_unref (introspection_data);
+
+ return 0;
+}
diff --git a/src/headsetd.c b/src/headsetd.c
new file mode 100644
index 0000000..beaf4ec
--- /dev/null
+++ b/src/headsetd.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include "hsd.h"
+#include "hsd-manager.h"
+#include "hsd-profile.h"
+
+static GDBusConnection *the_connection;
+static GMainLoop *loop;
+
+GDBusConnection *
+hsd_dbus_connection_get (void)
+{
+ return the_connection;
+}
+
+static void
+on_bus_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ HsdManager *manager;
+
+ the_connection = connection;
+
+ manager = hsd_manager_new (&error);
+ if (manager == NULL) {
+ g_printerr ("error registering manager: %s", error->message);
+ g_clear_error (&error);
+ g_main_loop_quit (loop);
+ return;
+ }
+
+ hsd_profile_new (manager, HSD_PROFILE_ID_HSP_AG, &error);
+ if (error != NULL) {
+ g_printerr ("error registering profile: %s", error->message);
+ g_clear_error (&error);
+ g_main_loop_quit (loop);
+ return;
+ }
+}
+
+static void
+on_name_acquired (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_print ("Acquired the name %s on the session bus\n", name);
+}
+
+static void
+on_name_lost (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ g_print ("Lost the name %s on the session bus\n", name);
+}
+
+int
+main (int argc, char *argv[])
+{
+ GError *error = NULL;
+ guint owner_id;
+
+ owner_id = g_bus_own_name (G_BUS_TYPE_SYSTEM,
+ HSD_SERVICE,
+ G_BUS_NAME_OWNER_FLAGS_REPLACE,
+ on_bus_acquired,
+ on_name_acquired,
+ on_name_lost,
+ NULL,
+ NULL);
+
+ g_print ("going into mainloop\n");
+ loop = g_main_loop_new (NULL, FALSE);
+ g_main_loop_run (loop);
+ g_print ("exit mainloop\n");
+
+ g_bus_unown_name (owner_id);
+
+ return 0;
+}
+
diff --git a/src/hsd-headset-transport.c b/src/hsd-headset-transport.c
new file mode 100644
index 0000000..05acf38
--- /dev/null
+++ b/src/hsd-headset-transport.c
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sco.h>
+
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include "hsd.h"
+#include "hsd-headset.h"
+#include "hsd-headset-transport.h"
+
+static GDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.freedesktop.HeadsetTransport'>"
+ " <method name='Acquire'>"
+ " <arg type='h' name='fd' direction='out'/>"
+ " <arg type='q' name='mtu_r' direction='out'/>"
+ " <arg type='q' name='mtu_w' direction='out'/>"
+ " </method>"
+ " <method name='Release'>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+static GDBusInterfaceInfo *
+get_headset_transport_interface_info (void)
+{
+ static GDBusNodeInfo *introspection_data = NULL;
+
+ if (introspection_data == NULL)
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+
+ return introspection_data->interfaces[0];
+}
+
+static void
+headset_transport_set_state (HsdHeadsetTransport *t,
+ HsdTransportState state)
+{
+ if (t->state == state)
+ return;
+
+ t->state = state;
+}
+
+static gboolean
+transport_connect_cb (GIOChannel *io, GIOCondition cond, gpointer user_data)
+{
+ HsdHeadsetTransport *t = user_data;
+ GUnixFDList *fdlist;
+ gint sock;
+ GDBusMethodInvocation *invocation;
+
+ invocation = t->invocation;
+ t->invocation = NULL;
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ goto connect_failed;
+
+ headset_transport_set_state (t, HSD_TRANSPORT_STATE_ACTIVE);
+
+ g_debug ("connected");
+ fdlist = g_unix_fd_list_new ();
+ g_unix_fd_list_append (fdlist, t->fd, NULL);
+
+ g_dbus_method_invocation_return_value_with_unix_fd_list (
+ invocation, g_variant_new ("(hqq)", 0, 48, 48), fdlist);
+
+ return FALSE;
+
+connect_failed:
+ {
+ close (t->fd);
+ t->fd = -1;
+ headset_transport_set_state (t, HSD_TRANSPORT_STATE_IDLE);
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.Failed", "failed to connect");
+ return FALSE;
+ }
+}
+
+static void
+headset_transport_acquire (HsdHeadsetTransport *t,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ GDBusMessage *reply;
+ GError *error;
+ guchar *blob;
+ gsize out_size;
+ struct sockaddr_sco addr;
+ int err, i;
+ GIOCondition cond;
+ GIOChannel *io;
+ bdaddr_t src;
+ bdaddr_t dst;
+ int voice = 0x60;
+ socklen_t len;
+ gchar *src_addr;
+ gchar *dst_addr;
+
+ g_message ("transport Acquire");
+
+ if (t->state != HSD_TRANSPORT_STATE_IDLE)
+ goto not_idle;
+
+ src_addr = t->headset->adapter_addr;
+ dst_addr = t->headset->device_addr;
+
+ for (i = 5; i >= 0; i--, src_addr += 3)
+ src.b[i] = strtol(src_addr, NULL, 16);
+ for (i = 5; i >= 0; i--, dst_addr += 3)
+ dst.b[i] = strtol(dst_addr, NULL, 16);
+
+ t->fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, BTPROTO_SCO);
+ if (t->fd < 0)
+ goto socket_failed;
+
+ g_debug ("got fd %d", t->fd);
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sco_family = AF_BLUETOOTH;
+ bacpy(&addr.sco_bdaddr, &src);
+
+ if (bind(t->fd, (struct sockaddr *) &addr, sizeof(addr)) < 0)
+ goto bind_failed;
+
+ if (voice) {
+ struct bt_voice opts;
+
+ /* SCO voice setting */
+ memset(&opts, 0, sizeof(opts));
+ opts.setting = voice;
+ if (setsockopt(t->fd, SOL_BLUETOOTH, BT_VOICE, &opts, sizeof(opts)) < 0)
+ goto sockopt_failed;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sco_family = AF_BLUETOOTH;
+ bacpy(&addr.sco_bdaddr, &dst);
+
+ g_debug ("doing connect");
+
+ err = connect(t->fd, (struct sockaddr *) &addr, sizeof(addr));
+ if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
+ goto connect_failed;
+
+ headset_transport_set_state (t, HSD_TRANSPORT_STATE_PENDING);
+
+ t->invocation = invocation;
+
+ io = g_io_channel_unix_new(t->fd);
+ g_io_add_watch(io, G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ transport_connect_cb, t);
+ g_io_channel_unref(io);
+
+ return;
+
+not_idle:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.Failed", "transport not idle");
+ return;
+ }
+socket_failed:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.Failed", "failed to create socket");
+ goto close_fd;
+ }
+bind_failed:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.Failed", "failed to bind socket");
+ goto close_fd;
+ }
+sockopt_failed:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.Failed", "failed to setsockopt");
+ goto close_fd;
+ }
+connect_failed:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.Failed", "failed to connect");
+ goto close_fd;
+ }
+close_fd:
+ {
+ close (t->fd);
+ t->fd = -1;
+ return;
+ }
+}
+
+static void
+headset_transport_release (HsdHeadsetTransport *t,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ g_message ("transport release");
+
+ if (t->fd != -1) {
+ shutdown (t->fd, SHUT_RDWR);
+ close (t->fd);
+ }
+ t->fd = -1;
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+
+ headset_transport_set_state (t, HSD_TRANSPORT_STATE_IDLE);
+}
+
+static void
+headset_transport_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ HsdHeadsetTransport *t = user_data;
+
+ if (t->invocation)
+ goto busy;
+
+ if (strcmp (t->owner, sender) != 0)
+ goto not_owner;
+
+ if (g_strcmp0 (method_name, "Acquire") == 0) {
+ headset_transport_acquire (t, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "Release") == 0) {
+ headset_transport_release (t, connection, parameters, invocation);
+ } else
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.NotImplemented", "no such method");
+
+ return;
+
+busy:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.Busy", "We have a pending operation");
+ return;
+ }
+not_owner:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.NotAuthorized", "not the transport owner");
+ return;
+ }
+}
+
+static const GDBusInterfaceVTable headset_transport_interface_vtable =
+{
+ headset_transport_method_call,
+ NULL,
+ NULL
+};
+
+HsdHeadsetTransport *
+hsd_headset_transport_new (HsdHeadset *headset, gchar *name, const gchar *owner, GVariantIter *props, GError **error)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+ HsdHeadsetTransport *transport;
+
+ transport = g_new0 (HsdHeadsetTransport, 1);
+ transport->headset = headset;
+ transport->name = name;
+ transport->owner = g_strdup (owner);
+ transport->state = HSD_TRANSPORT_STATE_IDLE;
+ transport->fd = -1;
+
+ /* FIXME, use props */
+ if (props)
+ g_variant_iter_free (props);
+
+ /* FIXME, watch owner */
+
+ g_debug ("Registering headset-transport object");
+ transport->id = g_dbus_connection_register_object (conn,
+ name,
+ get_headset_transport_interface_info(),
+ &headset_transport_interface_vtable,
+ transport,
+ NULL, /* user_data_free_func */
+ error); /* GError** */
+ if (transport->id == 0) {
+ hsd_headset_transport_free (transport);
+ return NULL;
+ }
+
+ return transport;
+}
+
+void
+hsd_headset_transport_free (HsdHeadsetTransport *transport)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+
+ if (transport->id)
+ g_dbus_connection_unregister_object (conn, transport->id);
+ if (transport->fd != -1)
+ close (transport->fd);
+ g_free (transport->name);
+ g_free (transport->owner);
+ g_free (transport);
+}
+
diff --git a/src/hsd-headset-transport.h b/src/hsd-headset-transport.h
new file mode 100644
index 0000000..9eb131e
--- /dev/null
+++ b/src/hsd-headset-transport.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+typedef enum {
+ HSD_TRANSPORT_STATE_IDLE,
+ HSD_TRANSPORT_STATE_PENDING,
+ HSD_TRANSPORT_STATE_ACTIVE,
+} HsdTransportState;
+
+struct _HsdHeadsetTransport {
+ HsdHeadset *headset;
+
+ gchar *name;
+ gchar *owner;
+
+ guint id;
+
+ gint fd;
+ HsdTransportState state;
+ GDBusMethodInvocation *invocation;
+};
+
+HsdHeadsetTransport * hsd_headset_transport_new (HsdHeadset *headset, gchar *name,
+ const gchar *owner, GVariantIter *props,
+ GError **error);
+void hsd_headset_transport_free (HsdHeadsetTransport *transport);
diff --git a/src/hsd-headset.c b/src/hsd-headset.c
new file mode 100644
index 0000000..b8d536e
--- /dev/null
+++ b/src/hsd-headset.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include <gio/gio.h>
+
+#include "hsd.h"
+#include "hsd-headset.h"
+#include "hsd-headset-transport.h"
+
+static guint count = 0;
+
+static GDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.freedesktop.Headset'>"
+ " <method name='GetTransport'>"
+ " <arg type='a{sv}' name='properties' direction='in'/>"
+ " <arg type='o' name='transport' direction='out'/>"
+ " </method>"
+ " <method name='ReleaseTransport'>"
+ " <arg type='o' name='transport' direction='in'/>"
+ " </method>"
+ " <property type='o' name='Device' access='read'/>"
+ " </interface>"
+ "</node>";
+
+static GDBusInterfaceInfo *
+get_headset_interface_info (void)
+{
+ static GDBusNodeInfo *introspection_data = NULL;
+
+ if (introspection_data == NULL)
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+
+ return introspection_data->interfaces[0];
+}
+
+static void
+headset_get_transport (HsdHeadset *h,
+ GDBusConnection *connection,
+ const gchar *sender,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ GVariantIter *props;
+ HsdHeadsetTransport *t;
+ GError *error = NULL;
+ gchar *tname;
+
+ g_debug ("headset GetTransport");
+
+ g_variant_get (parameters, "(a{sv})", &props);
+
+ /* FIXME, watch owner */
+
+ tname = g_strdup_printf ("%s/fd%d", h->device, count++);
+ t = hsd_headset_transport_new (h, tname, sender, props, &error);
+ if (t == NULL)
+ goto transport_failed;
+
+ g_dbus_method_invocation_return_value (invocation,
+ g_variant_new ("(o)", tname));
+
+ g_hash_table_insert (h->transports, tname, t);
+
+ return;
+
+transport_failed:
+ {
+ g_error ("failed to get transport: %s", error->message);
+ g_dbus_method_invocation_take_error (invocation, error);
+ }
+}
+
+static void
+headset_release_transport (HsdHeadset *h,
+ GDBusConnection *connection,
+ const gchar *sender,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ HsdHeadsetTransport *t;
+ gchar *tname;
+
+ g_debug ("headset ReleaseTransport");
+
+ g_variant_get (parameters, "(&o)", &tname);
+
+ t = g_hash_table_lookup (h->transports, tname);
+ if (t == NULL)
+ goto unknown_transport;
+
+ if (strcmp (t->owner, sender) != 0)
+ goto not_owner;
+
+ g_hash_table_remove (h->transports, tname);
+ hsd_headset_transport_free (t);
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+
+ return;
+
+unknown_transport:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.DoesNotExist", "no such transport");
+ return;
+ }
+not_owner:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.NotAuthorized", "not the transport owner");
+ return;
+ }
+}
+
+static void
+headset_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ HsdHeadset *h = user_data;
+
+ if (g_strcmp0 (method_name, "GetTransport") == 0) {
+ headset_get_transport (h, connection, sender, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "ReleaseTransport") == 0) {
+ headset_release_transport (h, connection, sender, parameters, invocation);
+ } else
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.NotImplemented", "no such method");
+}
+
+static const GDBusInterfaceVTable headset_interface_vtable =
+{
+ headset_method_call,
+ NULL,
+ NULL
+};
+
+static gchar *
+dbus_get_property (GDBusConnection *conn, const gchar *service, const gchar *obj,
+ const gchar *interface, const gchar *prop, GError **error)
+{
+ GVariant *res, *var;
+ gchar *result;
+
+ res = g_dbus_connection_call_sync (conn,
+ service,
+ obj,
+ "org.freedesktop.DBus.Properties",
+ "Get",
+ g_variant_new ("(ss)", interface, prop),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ error);
+ if (res == NULL)
+ return NULL;
+
+ g_variant_get (res, "(v)", &var);
+ result = g_variant_dup_string (var, NULL);
+ g_variant_unref (var);
+
+ return result;
+}
+
+
+HsdHeadset *
+hsd_headset_new (const gchar *device, GError **error)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+ HsdHeadset *headset;
+ GVariant *res, *var;
+
+ headset = g_new0 (HsdHeadset, 1);
+ headset->device = g_strdup (device);
+ headset->transports = g_hash_table_new (g_str_hash, g_str_equal);
+
+ headset->device_addr = dbus_get_property (conn, "org.bluez", device, "org.bluez.Device1", "Address", error);
+ if (headset->device_addr == NULL)
+ goto cleanup;
+
+ headset->adapter = dbus_get_property (conn, "org.bluez", device, "org.bluez.Device1", "Adapter", error);
+ if (headset->adapter == NULL)
+ goto cleanup;
+
+ headset->adapter_addr = dbus_get_property (conn, "org.bluez", headset->adapter, "org.bluez.Adapter1", "Address", error);
+ if (headset->adapter_addr == NULL)
+ goto cleanup;
+
+ g_message ("device addres %s", headset->device_addr);
+ g_message ("device adapter %s", headset->adapter);
+ g_message ("adapter address %s", headset->adapter_addr);
+
+ g_debug ("Registering headset object");
+ headset->id = g_dbus_connection_register_object (conn,
+ device,
+ get_headset_interface_info(),
+ &headset_interface_vtable,
+ headset,
+ NULL, /* user_data_free_func */
+ error); /* GError** */
+ if (headset->id == 0)
+ goto cleanup;
+
+ return headset;
+
+cleanup:
+ {
+ g_error ("failed to make new headset: %s", error ? (*error)->message : "unknown reason");
+ hsd_headset_free (headset);
+ return NULL;
+ }
+}
+
+void
+hsd_headset_free (HsdHeadset *headset)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+
+ if (headset->id)
+ g_dbus_connection_unregister_object (conn, headset->id);
+ g_free (headset->device);
+ g_free (headset->device_addr);
+ g_free (headset->adapter);
+ g_free (headset->adapter_addr);
+ g_hash_table_unref (headset->transports);
+ g_free (headset);
+}
+
diff --git a/src/hsd-headset.h b/src/hsd-headset.h
new file mode 100644
index 0000000..5e5f5dd
--- /dev/null
+++ b/src/hsd-headset.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+struct _HsdHeadset {
+ guint id;
+
+ gchar *device;
+ gchar *device_addr;
+ gchar *adapter;
+ gchar *adapter_addr;
+
+ GHashTable *transports;
+};
+
+HsdHeadset * hsd_headset_new (const gchar *device, GError **error);
+void hsd_headset_free (HsdHeadset *headset);
diff --git a/src/hsd-manager.c b/src/hsd-manager.c
new file mode 100644
index 0000000..8e1eee7
--- /dev/null
+++ b/src/hsd-manager.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include <gio/gio.h>
+
+#include "hsd.h"
+#include "hsd-headset.h"
+#include "hsd-manager.h"
+
+static GDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.freedesktop.HeadsetManager'>"
+ " <method name='GetDevices'>"
+ " <arg type='a(oa{sv})' name='devices' direction='out'/>"
+ " </method>"
+ " <method name='Register'>"
+ " <arg type='o' name='agent' direction='in'/>"
+ " <arg type='a{sv}' name='properties' direction='in'/>"
+ " </method>"
+ " <method name='Unregister'>"
+ " <arg type='o' name='agent' direction='in'/>"
+ " </method>"
+ " <signal name='DeviceAdded'>"
+ " <arg type='o' name='device'/>"
+ " <arg type='a{sv}' name='properties'/>"
+ " </signal>"
+ " <signal name='DeviceRemoved'>"
+ " <arg type='o' name='device'/>"
+ " </signal>"
+ " </interface>"
+ "</node>";
+
+static GDBusInterfaceInfo *
+get_manager_interface_info (void)
+{
+ static GDBusNodeInfo *introspection_data = NULL;
+
+ if (introspection_data == NULL)
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+
+ return introspection_data->interfaces[0];
+}
+
+static void
+manager_get_devices (HsdManager *m,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ g_debug ("manager GetDevices");
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.NotImplemented", "no such method");
+}
+
+static void
+manager_register (HsdManager *m,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ g_debug ("manager Register");
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.NotImplemented", "no such method");
+}
+
+static void
+manager_unregister (HsdManager *m,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ g_debug ("manager Unregister");
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.NotImplemented", "no such method");
+}
+
+static void
+manager_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ HsdManager *m = user_data;
+
+ if (g_strcmp0 (method_name, "GetDevices") == 0) {
+ manager_get_devices (m, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "Register") == 0) {
+ manager_register (m, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "Unregister") == 0) {
+ manager_unregister (m, connection, parameters, invocation);
+ } else
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.NotImplemented", "no such method");
+}
+
+static const GDBusInterfaceVTable manager_interface_vtable =
+{
+ manager_method_call,
+ NULL,
+ NULL
+};
+
+HsdManager *
+hsd_manager_new (GError **error)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+ HsdManager *manager;
+
+ manager = g_new0 (HsdManager, 1);
+ manager->profiles = g_hash_table_new (g_str_hash, g_str_equal);
+ manager->devices = g_hash_table_new (g_str_hash, g_str_equal);
+
+ g_debug ("Registering manager object");
+ manager->id = g_dbus_connection_register_object (conn,
+ "/",
+ get_manager_interface_info(),
+ &manager_interface_vtable,
+ manager,
+ NULL, /* user_data_free_func */
+ error); /* GError** */
+ if (manager->id == 0) {
+ hsd_manager_free (manager);
+ return NULL;
+ }
+
+ return manager;
+}
+
+void
+hsd_manager_free (HsdManager *manager)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+
+ if (manager->id)
+ g_dbus_connection_unregister_object (conn, manager->id);
+ g_hash_table_unref (manager->profiles);
+ g_hash_table_unref (manager->devices);
+ g_free (manager);
+}
+
+HsdHeadset *
+hsd_manager_add_headset (HsdManager *manager, const gchar *device, GError **error)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+ HsdHeadset *headset;
+ GError *local_error = NULL;
+
+ headset = hsd_headset_new (device, error);
+ if (headset == NULL)
+ return NULL;
+
+ g_hash_table_insert (manager->devices, headset->device, headset);
+
+ g_dbus_connection_emit_signal (conn,
+ NULL,
+ "/",
+ HSD_MANAGER_INTERFACE_SERVICE,
+ "DeviceAdded",
+ g_variant_new ("(oa{sv})",
+ device,
+ NULL),
+ &local_error);
+ if (local_error != NULL) {
+ g_warning ("error emiting signal %s", local_error->message);
+ g_clear_error (&local_error);
+ }
+ return headset;
+}
+
+void
+hsd_manager_remove_headset (HsdManager *manager, const gchar *device, GError **error)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+ HsdHeadset *headset;
+ GError *local_error = NULL;
+
+ headset = g_hash_table_lookup (manager->devices, device);
+ if (headset == NULL) {
+ g_warning ("unknown headset device %s", device);
+ return;
+ }
+
+ g_hash_table_remove (manager->devices, device);
+ hsd_headset_free (headset);
+
+ g_dbus_connection_emit_signal (conn,
+ NULL,
+ "/",
+ HSD_MANAGER_INTERFACE_SERVICE,
+ "DeviceRemoved",
+ g_variant_new ("(o)",
+ device),
+ &local_error);
+ if (local_error != NULL) {
+ g_warning ("error emiting signal %s", local_error->message);
+ g_clear_error (&local_error);
+ }
+}
+
diff --git a/src/hsd-manager.h b/src/hsd-manager.h
new file mode 100644
index 0000000..25c9eea
--- /dev/null
+++ b/src/hsd-manager.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#define HSD_MANAGER_INTERFACE_SERVICE HSD_SERVICE ".HeadsetManager"
+
+struct _HsdManager {
+ guint id;
+
+ GHashTable *profiles;
+ GHashTable *devices;
+};
+
+HsdManager * hsd_manager_new (GError **error);
+void hsd_manager_free (HsdManager *manager);
+
+HsdHeadset * hsd_manager_add_headset (HsdManager *manager,
+ const gchar *device, GError **error);
+void hsd_manager_remove_headset (HsdManager *manager,
+ const gchar *device, GError **error);
diff --git a/src/hsd-profile.c b/src/hsd-profile.c
new file mode 100644
index 0000000..3be9b8f
--- /dev/null
+++ b/src/hsd-profile.c
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include <gio/gio.h>
+
+#include "hsd.h"
+#include "hsd-profile.h"
+
+typedef struct {
+ HsdProfileId id;
+ const gchar *name;
+ const gchar *uuid;
+} ProfileDef;
+
+static const ProfileDef profiles[] = {
+ { HSD_PROFILE_ID_HSP_AG, "hsp_ag", "00001112-0000-1000-8000-00805f9b34fb" },
+ { HSD_PROFILE_ID_HFP_AG, "hfp_ag", "0000111f-0000-1000-8000-00805f9b34fb" },
+ { HSD_PROFILE_ID_INVALID, NULL, NULL },
+};
+
+static GDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+ "<node>"
+ " <interface name='org.bluez.Profile1'>"
+ " <method name='Release'>"
+ " </method>"
+ " <method name='Cancel'>"
+ " </method>"
+ " <method name='RequestDisconnection'>"
+ " <arg type='o' name='device' direction='in'/>"
+ " </method>"
+ " <method name='NewConnection'>"
+ " <arg type='o' name='device' direction='in'/>"
+ " <arg type='h' name='fd' direction='in'/>"
+ " <arg type='a{sv}' name='opts' direction='in'/>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+static GDBusInterfaceInfo *
+get_profile_interface_info (void)
+{
+ static GDBusNodeInfo *introspection_data = NULL;
+
+ if (introspection_data == NULL)
+ introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
+
+ return introspection_data->interfaces[0];
+}
+
+static void
+profile_new_connection (HsdProfile *p,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ gchar *obj;
+ gint32 fd;
+ GVariantIter *props;
+ GDBusMessage *message;
+ GUnixFDList * fdlist;
+ GError *error = NULL;
+
+ g_variant_get (parameters, "(oha{sv})", &obj, &fd, &props);
+
+ g_debug ("profile NewConnection device=%s, fd=%d", obj, fd);
+
+ message = g_dbus_method_invocation_get_message (invocation);
+ fdlist = g_dbus_message_get_unix_fd_list (message);
+ fd = g_unix_fd_list_get (fdlist, fd, NULL);
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+
+ hsd_manager_add_headset (p->manager, obj, &error);
+}
+
+static void
+profile_request_disconnection (HsdProfile *p,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ gchar *obj;
+ gpointer t;
+
+ g_variant_get (parameters, "(o)", &obj);
+ g_debug ("profile RequestDisconnection device=%s", obj);
+
+ t = g_hash_table_lookup (p->objs, obj);
+ if (t == NULL)
+ goto unknown_transport;
+
+ g_hash_table_remove (p->objs, obj);
+
+ g_dbus_method_invocation_return_value (invocation, NULL);
+ return;
+
+unknown_transport:
+ {
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.Rejected", "no such transport");
+ return;
+ }
+}
+
+static void
+profile_release (HsdProfile *p,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+profile_cancel (HsdProfile *p,
+ GDBusConnection *connection,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation)
+{
+ g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
+profile_method_call (GDBusConnection *connection,
+ const gchar *sender,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *method_name,
+ GVariant *parameters,
+ GDBusMethodInvocation *invocation,
+ gpointer user_data)
+{
+ HsdProfile *p = user_data;
+
+ if (g_strcmp0 (method_name, "Release") == 0) {
+ profile_release (p, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "Cancel") == 0) {
+ profile_cancel (p, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "RequestDisconnection") == 0) {
+ profile_request_disconnection (p, connection, parameters, invocation);
+ } else if (g_strcmp0 (method_name, "NewConnection") == 0) {
+ profile_new_connection (p, connection, parameters, invocation);
+ } else
+ g_dbus_method_invocation_return_dbus_error (invocation,
+ "org.freedesktop.Headset.Error.NotImplemented", "no such method");
+}
+
+static const GDBusInterfaceVTable profile_interface_vtable =
+{
+ profile_method_call,
+ NULL,
+ NULL
+};
+
+static const ProfileDef *
+get_profile_def (HsdProfileId id)
+{
+ if (id <= HSD_PROFILE_ID_INVALID && id > HSD_PROFILE_ID_LAST)
+ return NULL;
+
+ return &profiles[id - 1];
+}
+
+HsdProfile *
+hsd_profile_new (HsdManager *manager, HsdProfileId id, GError **error)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+ HsdProfile *profile;
+ GVariant *res;
+ const ProfileDef *def = get_profile_def (id);
+
+ g_assert (def != NULL);
+
+ profile = g_new0 (HsdProfile, 1);
+ profile->manager = manager;
+ profile->path = g_strdup_printf ("/org/bluez/profile/%s", def->name);
+ profile->uuid = g_strdup (def->uuid);
+ profile->objs = g_hash_table_new (g_str_hash, g_str_equal);
+
+ g_debug ("Registering profile object %s", profile->path);
+ profile->id = g_dbus_connection_register_object (conn,
+ profile->path,
+ get_profile_interface_info(),
+ &profile_interface_vtable,
+ profile,
+ NULL, /* user_data_free_func */
+ error); /* GError** */
+ if (profile->id == 0) {
+ hsd_profile_free (profile);
+ return NULL;
+ }
+
+ g_debug ("Registering profile %s", profile->path);
+ res = g_dbus_connection_call_sync (conn,
+ "org.bluez",
+ "/org/bluez",
+ "org.bluez.ProfileManager1",
+ "RegisterProfile",
+ g_variant_new ("(osa{sv})",
+ profile->path,
+ profile->uuid, NULL),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ -1,
+ NULL,
+ error);
+ if (res == NULL) {
+ hsd_profile_free (profile);
+ return NULL;
+ }
+ g_variant_unref (res);
+
+ return profile;
+}
+
+void
+hsd_profile_free (HsdProfile *profile)
+{
+ GDBusConnection *conn = hsd_dbus_connection_get ();
+
+ if (profile->id)
+ g_dbus_connection_unregister_object (conn, profile->id);
+ g_free (profile->path);
+ g_free (profile->uuid);
+ g_hash_table_unref (profile->objs);
+ g_free (profile);
+}
+
diff --git a/src/hsd-profile.h b/src/hsd-profile.h
new file mode 100644
index 0000000..c2baa95
--- /dev/null
+++ b/src/hsd-profile.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+typedef enum
+{
+ HSD_PROFILE_ID_INVALID,
+ HSD_PROFILE_ID_HSP_AG,
+ HSD_PROFILE_ID_HFP_AG,
+
+ HSD_PROFILE_ID_LAST = HSD_PROFILE_ID_HFP_AG
+} HsdProfileId;
+
+#define HSD_PROFILE_HSP_AG_NAME "hsp_ag"
+#define HSD_PROFILE_HSP_AG_UUID "00001112-0000-1000-8000-00805f9b34fb"
+
+#define HSD_PROFILE_HFP_AG_NAME "hfp_ag"
+#define HSD_PROFILE_HFP_AG_UUID "0000111f-0000-1000-8000-00805f9b34fb"
+
+struct _HsdProfile {
+ HsdManager *manager;
+
+ gchar *uuid;
+
+ gchar *path;
+ guint id;
+
+ GHashTable *objs;
+};
+
+HsdProfile * hsd_profile_new (HsdManager *manager, HsdProfileId id, GError **error);
+void hsd_profile_free (HsdProfile *profile);
diff --git a/src/hsd.c b/src/hsd.c
new file mode 100644
index 0000000..c96d2a5
--- /dev/null
+++ b/src/hsd.c
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include "hsd.h"
+
+static GDBusConnection *connection;
+
+void
+hsd_init (GError **error)
+{
+ g_debug ("Getting DBus connection");
+ connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
+}
+
+GDBusConnection *
+hsd_dbus_connection_get (void)
+{
+ return connection;
+}
diff --git a/src/hsd.h b/src/hsd.h
new file mode 100644
index 0000000..a150f91
--- /dev/null
+++ b/src/hsd.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library 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.
+ */
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#define HSD_SERVICE "org.freedesktop.Headset"
+
+typedef struct _HsdProfile HsdProfile;
+typedef struct _HsdManager HsdManager;
+typedef struct _HsdHeadset HsdHeadset;
+typedef struct _HsdHeadsetTransport HsdHeadsetTransport;
+
+GDBusConnection * hsd_dbus_connection_get (void);