diff options
Diffstat (limited to 'src/hsd-headset.c')
-rw-r--r-- | src/hsd-headset.c | 291 |
1 files changed, 235 insertions, 56 deletions
diff --git a/src/hsd-headset.c b/src/hsd-headset.c index 1af096c..3011272 100644 --- a/src/hsd-headset.c +++ b/src/hsd-headset.c @@ -17,11 +17,18 @@ * 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 guint count = 0; @@ -31,14 +38,14 @@ static GDBusNodeInfo *introspection_data = NULL; static const gchar introspection_xml[] = "<node>" " <interface name='org.freedesktop.Headset'>" - " <method name='GetTransport'>" + " <method name='Connect'>" " <arg type='a{sv}' name='properties' direction='in'/>" - " <arg type='o' name='transport' direction='out'/>" + " <arg type='a{sv}' name='results' direction='out'/>" " </method>" - " <method name='ReleaseTransport'>" - " <arg type='o' name='transport' direction='in'/>" + " <method name='Disconnect'>" " </method>" " <property type='o' name='Device' access='read'/>" + " <property type='s' name='State' access='read'/>" " </interface>" "</node>"; @@ -53,81 +60,220 @@ get_headset_interface_info (void) return introspection_data->interfaces[0]; } +static const gchar * +headset_state_to_string (HsdHeadsetState state) +{ + switch (state) { + case HSD_HEADSET_STATE_IDLE: + return "idle"; + case HSD_HEADSET_STATE_PENDING: + return "pending"; + case HSD_HEADSET_STATE_ACTIVE: + return "active"; + default: + return "invalid"; + } +} + static void -headset_get_transport (HsdHeadset *h, - GDBusConnection *connection, - const gchar *sender, - GVariant *parameters, - GDBusMethodInvocation *invocation) +headset_set_state (HsdHeadset *h, + HsdHeadsetState state) { - GVariantIter *props; - HsdHeadsetTransport *t; - GError *error = NULL; - gchar *tname; + if (h->state == state) + return; + + h->state = state; +} - g_debug ("headset GetTransport"); +static gboolean +headset_connect_cb (GIOChannel *io, GIOCondition cond, gpointer user_data) +{ + HsdHeadset *h = user_data; + GUnixFDList *fdlist; + gint sock; + GDBusMethodInvocation *invocation; + GVariantBuilder *b; - g_variant_get (parameters, "(a{sv})", &props); + invocation = h->invocation; + h->invocation = NULL; - /* FIXME, watch owner */ + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + goto connect_failed; - 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; + headset_set_state (h, HSD_HEADSET_STATE_ACTIVE); - g_dbus_method_invocation_return_value (invocation, - g_variant_new ("(o)", tname)); + g_debug ("connected"); + fdlist = g_unix_fd_list_new (); + g_unix_fd_list_append (fdlist, h->fd, NULL); - g_hash_table_insert (h->transports, tname, t); + b = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); + g_variant_builder_add (b, "{sv}", "fd", g_variant_new_handle (0)); + g_variant_builder_add (b, "{sv}", "mtu_r", g_variant_new_uint16 (48)); + g_variant_builder_add (b, "{sv}", "mtu_w", g_variant_new_uint16 (48)); + g_dbus_method_invocation_return_value_with_unix_fd_list ( + invocation, g_variant_new ("(a{sv})", b), fdlist); - return; + return FALSE; -transport_failed: +connect_failed: { - g_error ("failed to get transport: %s", error->message); - g_dbus_method_invocation_take_error (invocation, error); + close (h->fd); + h->fd = -1; + g_free (h->owner); + h->owner = NULL; + headset_set_state (h, HSD_HEADSET_STATE_IDLE); + g_dbus_method_invocation_return_dbus_error (invocation, + "org.freedesktop.Headset.Error.Failed", "failed to connect"); + return FALSE; } } static void -headset_release_transport (HsdHeadset *h, - GDBusConnection *connection, - const gchar *sender, - GVariant *parameters, - GDBusMethodInvocation *invocation) +headset_connect (HsdHeadset *h, + GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) { - HsdHeadsetTransport *t; - gchar *tname; - - g_debug ("headset ReleaseTransport"); + 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 ("Connect headset"); + + if (h->state != HSD_HEADSET_STATE_IDLE) + goto not_idle; + + src_addr = h->adapter_addr; + dst_addr = h->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); + + h->fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, BTPROTO_SCO); + if (h->fd < 0) + goto socket_failed; + + g_debug ("got fd %d", h->fd); + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &src); + + if (bind(h->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(h->fd, SOL_BLUETOOTH, BT_VOICE, &opts, sizeof(opts)) < 0) + goto sockopt_failed; + } - g_variant_get (parameters, "(&o)", &tname); + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, &dst); - t = g_hash_table_lookup (h->transports, tname); - if (t == NULL) - goto unknown_transport; + g_debug ("doing connect"); - if (strcmp (t->owner, sender) != 0) - goto not_owner; + err = connect(h->fd, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + goto connect_failed; - g_hash_table_remove (h->transports, tname); - hsd_headset_transport_free (t); + headset_set_state (h, HSD_HEADSET_STATE_PENDING); + h->owner = g_strdup (sender); + h->invocation = invocation; - g_dbus_method_invocation_return_value (invocation, NULL); + io = g_io_channel_unix_new(h->fd); + g_io_add_watch(io, G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + headset_connect_cb, h); + g_io_channel_unref(io); return; -unknown_transport: +not_idle: { g_dbus_method_invocation_return_dbus_error (invocation, - "org.freedesktop.Headset.Error.DoesNotExist", "no such transport"); + "org.freedesktop.Headset.Error.Failed", "headset 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 (h->fd); + h->fd = -1; + return; + } +} + +static void +headset_disconnect (HsdHeadset *h, + GDBusConnection *connection, + const gchar *sender, + GVariant *parameters, + GDBusMethodInvocation *invocation) +{ + g_message ("headset disconnect"); + + if (h->owner != NULL && strcmp (h->owner, sender)) + goto not_owner; + + if (h->fd != -1) { + shutdown (h->fd, SHUT_RDWR); + close (h->fd); + } + h->fd = -1; + + h->owner = NULL; + headset_set_state (h, HSD_HEADSET_STATE_IDLE); + + g_dbus_method_invocation_return_value (invocation, NULL); + + return; + not_owner: { g_dbus_method_invocation_return_dbus_error (invocation, - "org.freedesktop.Headset.Error.NotAuthorized", "not the transport owner"); + "org.freedesktop.Headset.Error.NotAuthorized", "not the connection owner"); return; } } @@ -144,19 +290,53 @@ headset_method_call (GDBusConnection *connection, { 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); + if (h->invocation) + goto busy; + + if (g_strcmp0 (method_name, "Connect") == 0) { + headset_connect (h, connection, sender, parameters, invocation); + } else if (g_strcmp0 (method_name, "Disconnect") == 0) { + headset_disconnect (h, connection, sender, 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; + } } +static GVariant * +headset_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + HsdHeadset *h = user_data; + GVariant *ret; + + ret = NULL; + if (g_strcmp0 (property_name, "Device") == 0) { + ret = g_variant_new_object_path (h->device); + } else if (g_strcmp0 (property_name, "State") == 0) { + ret = g_variant_new_string (headset_state_to_string (h->state)); + } + return ret; +} + + static const GDBusInterfaceVTable headset_interface_vtable = { headset_method_call, - NULL, + headset_get_property, NULL }; @@ -244,7 +424,6 @@ hsd_headset_new (const gchar *device, GIOChannel *rfcomm, GError **error) headset = g_new0 (HsdHeadset, 1); headset->device = g_strdup (device); - headset->transports = g_hash_table_new (g_str_hash, g_str_equal); headset->rfcomm = rfcomm; headset->device_addr = dbus_get_property (conn, "org.bluez", device, "org.bluez.Device1", "Address", error); @@ -297,8 +476,8 @@ hsd_headset_free (HsdHeadset *headset) g_free (headset->device_addr); g_free (headset->adapter); g_free (headset->adapter_addr); + g_free (headset->owner); g_io_channel_unref (headset->rfcomm); - g_hash_table_unref (headset->transports); g_free (headset); } |