summaryrefslogtreecommitdiff
path: root/src/hsd-headset.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/hsd-headset.c')
-rw-r--r--src/hsd-headset.c291
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);
}