diff options
author | Wim Taymans <wtaymans@redhat.com> | 2014-08-06 22:16:56 +0200 |
---|---|---|
committer | Wim Taymans <wtaymans@redhat.com> | 2014-08-06 22:16:56 +0200 |
commit | a6d2b7900a79b54d228141891013c5e360a38a5b (patch) | |
tree | a8ebf0b9d71799c0a7a9b2753bd8a8e2f72df16a /src |
Initial commit
Diffstat (limited to 'src')
-rw-r--r-- | src/headsetd-old.c | 785 | ||||
-rw-r--r-- | src/headsetd.c | 100 | ||||
-rw-r--r-- | src/hsd-headset-transport.c | 339 | ||||
-rw-r--r-- | src/hsd-headset-transport.h | 42 | ||||
-rw-r--r-- | src/hsd-headset.c | 254 | ||||
-rw-r--r-- | src/hsd-headset.h | 32 | ||||
-rw-r--r-- | src/hsd-manager.c | 222 | ||||
-rw-r--r-- | src/hsd-manager.h | 35 | ||||
-rw-r--r-- | src/hsd-profile.c | 247 | ||||
-rw-r--r-- | src/hsd-profile.h | 47 | ||||
-rw-r--r-- | src/hsd.c | 35 | ||||
-rw-r--r-- | src/hsd.h | 30 |
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); |