diff options
-rw-r--r-- | Makefile.am | 95 | ||||
-rwxr-xr-x | bootstrap | 7 | ||||
-rwxr-xr-x | bootstrap-configure | 15 | ||||
-rw-r--r-- | configure.ac | 185 | ||||
-rw-r--r-- | doc/design.txt | 40 | ||||
-rw-r--r-- | doc/headset-api.txt | 171 | ||||
-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 |
18 files changed, 2681 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..67899c9 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,95 @@ + +AM_MAKEFLAGS = --no-print-directory + +lib_LTLIBRARIES = + +noinst_LIBRARIES = + +noinst_LTLIBRARIES = + +bin_PROGRAMS = + +noinst_PROGRAMS = + +dist_man_MANS = + +dist_noinst_MANS = + +CLEANFILES = + +EXTRA_DIST = + +libexecdir = @libexecdir@/headset + +libexec_PROGRAMS = + +includedir = @includedir@/headset + +include_HEADERS = + +AM_CFLAGS = $(WARNING_CFLAGS) $(MISC_CFLAGS) +AM_LDFLAGS = $(MISC_LDFLAGS) + +if DATAFILES +dbusdir = @DBUS_CONFDIR@/dbus-1/system.d +dbus_DATA = src/headset.conf + +confdir = $(sysconfdir)/headset +conf_DATA = + +statedir = $(localstatedir)/lib/headset +state_DATA = +endif + +if SYSTEMD +systemdsystemunitdir = @SYSTEMD_SYSTEMUNITDIR@ +systemdsystemunit_DATA = src/headset.service + +dbussystembusdir = @DBUS_SYSTEMBUSDIR@ +dbussystembus_DATA = src/org.freedesktop.Headset.service +endif + +EXTRA_DIST += src/headset.service.in src/org.bluez.service + +plugindir = $(libdir)/headset/plugins + +if MAINTAINER_MODE +build_plugindir = $(abs_top_srcdir)/plugins/.libs +else +build_plugindir = $(plugindir) +endif + + +plugin_LTLIBRARIES = + +libexec_PROGRAMS += src/headsetd + +src_headsetd_SOURCES = src/headsetd.c src/hsd-profile.c src/hsd-manager.c src/hsd-headset.c \ + src/hsd-headset-transport.c +src_headsetd_LDADD = @GLIB_LIBS@ @GIO_LIBS@ @DBUS_LIBS@ -ldl -lrt +src_headsetd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic + +src_headsetd_DEPENDENCIES = + +src_headsetd_CFLAGS = $(AM_CFLAGS) -DPLUGINDIR=\""$(build_plugindir)"\" +src_headsetd_SHORTNAME = headsetd + +CLEANFILES += src/headset.service + +EXTRA_DIST += src/headset.conf + +EXTRA_DIST += doc/headset-api.txt + +AM_CFLAGS += @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GIO_CFLAGS@ + +EXTRA_DIST += $(manual_pages:.1=.txt) + +DISTCHECK_CONFIGURE_FLAGS = --disable-datafiles --enable-library \ + --enable-manpages \ + --disable-systemd --disable-udev + +DISTCLEANFILES = $(pkgconfig_DATA) $(manual_pages) + +MAINTAINERCLEANFILES = Makefile.in \ + aclocal.m4 configure config.h.in config.sub config.guess \ + ltmain.sh depcomp compile missing install-sh mkinstalldirs test-driver diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..91756f9 --- /dev/null +++ b/bootstrap @@ -0,0 +1,7 @@ +#!/bin/sh + +aclocal && \ + autoheader && \ + libtoolize --automake --copy --force && \ + automake --add-missing --copy && \ + autoconf diff --git a/bootstrap-configure b/bootstrap-configure new file mode 100755 index 0000000..ad88c8c --- /dev/null +++ b/bootstrap-configure @@ -0,0 +1,15 @@ +#!/bin/sh + +if [ -f config.status ]; then + make maintainer-clean +fi + +./bootstrap && \ + ./configure --enable-maintainer-mode \ + --enable-debug \ + --prefix=/usr \ + --mandir=/usr/share/man \ + --sysconfdir=/etc \ + --localstatedir=/var \ + --enable-manpages \ + --disable-datafiles $* diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..4d9a56f --- /dev/null +++ b/configure.ac @@ -0,0 +1,185 @@ +AC_PREREQ(2.60) +AC_INIT(headsetd, 0.01) + +AM_INIT_AUTOMAKE([foreign subdir-objects color-tests silent-rules + tar-pax no-dist-gzip dist-xz]) +AC_CONFIG_HEADERS(config.h) +AC_USE_SYSTEM_EXTENSIONS + +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AM_MAINTAINER_MODE + +AC_PREFIX_DEFAULT(/usr/local) + +PKG_PROG_PKG_CONFIG + +COMPILER_FLAGS + +AC_LANG_C + +AC_C_RESTRICT + +AC_PROG_CC +AM_PROG_CC_C_O +AC_PROG_INSTALL +AC_PROG_MKDIR_P + +m4_define([_LT_AC_TAGCONFIG], []) +m4_ifdef([AC_LIBTOOL_TAGS], [AC_LIBTOOL_TAGS([])]) + +AC_DISABLE_STATIC +AC_PROG_LIBTOOL + +MISC_FLAGS + +AC_ARG_ENABLE(threads, AC_HELP_STRING([--enable-threads], + [enable threading support]), [enable_threads=${enableval}]) + +AC_CHECK_FUNC(signalfd, dummy=yes, + AC_MSG_ERROR(signalfd support is required)) + +AC_CHECK_LIB(rt, clock_gettime, dummy=yes, + AC_MSG_ERROR(realtime clock support is required)) + +AC_CHECK_LIB(pthread, pthread_create, dummy=yes, + AC_MSG_ERROR(posix thread support is required)) + +AC_CHECK_LIB(dl, dlopen, dummy=yes, + AC_MSG_ERROR(dynamic linking loader is required)) + +PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.28, dummy=yes, + AC_MSG_ERROR(GLib >= 2.28 is required)) +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) + +PKG_CHECK_MODULES(GIO, gio-unix-2.0 >= 2.28, dummy=yes, + AC_MSG_ERROR(Gio-unix >= 2.28 is required)) +AC_SUBST(GIO_CFLAGS) +AC_SUBST(GIO_LIBS) + +if (test "${enable_threads}" = "yes"); then + AC_DEFINE(NEED_THREADS, 1, [Define if threading support is required]) + PKG_CHECK_MODULES(GTHREAD, gthread-2.0 >= 2.16, dummy=yes, + AC_MSG_ERROR(GThread >= 2.16 is required)) + GLIB_CFLAGS="$GLIB_CFLAGS $GTHREAD_CFLAGS" + GLIB_LIBS="$GLIB_LIBS $GTHREAD_LIBS" +fi + +PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.6, dummy=yes, + AC_MSG_ERROR(D-Bus >= 1.6 is required)) +AC_SUBST(DBUS_CFLAGS) +AC_SUBST(DBUS_LIBS) + +AC_ARG_WITH([dbusconfdir], AC_HELP_STRING([--with-dbusconfdir=DIR], + [path to D-Bus configuration directory]), + [path_dbusconfdir=${withval}]) +if (test -z "${path_dbusconfdir}"); then + AC_MSG_CHECKING([D-Bus configuration directory]) + path_dbusconfdir="`$PKG_CONFIG --variable=sysconfdir dbus-1`" + if (test -z "${path_dbusconfdir}"); then + AC_MSG_ERROR([D-Bus configuration directory is required]) + fi + AC_MSG_RESULT([${path_dbusconfdir}]) +fi +AC_SUBST(DBUS_CONFDIR, [${path_dbusconfdir}]) + +AC_ARG_WITH([dbussystembusdir], AC_HELP_STRING([--with-dbussystembusdir=DIR], + [path to D-Bus system bus services directory]), + [path_dbussystembusdir=${withval}]) +if (test -z "${path_dbussystembusdir}"); then + AC_MSG_CHECKING([D-Bus system bus services dir]) + path_dbussystembusdir="`$PKG_CONFIG --variable=system_bus_services_dir dbus-1`" + if (test -z "${path_dbussystembusdir}"); then + AC_MSG_ERROR([D-Bus system bus services directory is required]) + fi + AC_MSG_RESULT([${path_dbussystembusdir}]) +fi +AC_SUBST(DBUS_SYSTEMBUSDIR, [${path_dbussystembusdir}]) + +AC_ARG_WITH([dbussessionbusdir], AC_HELP_STRING([--with-dbussessionbusdir=DIR], + [path to D-Bus session bus services directory]), + [path_dbussessionbusdir=${withval}]) +if (test -z "${path_dbussessionbusdir}"); then + AC_MSG_CHECKING([D-Bus session bus services dir]) + path_dbussessionbusdir="`$PKG_CONFIG --variable=session_bus_services_dir dbus-1`" + if (test -z "${path_dbussessionbusdir}"); then + AC_MSG_ERROR([D-Bus session bus services directory is required]) + fi + AC_MSG_RESULT([${path_dbussessionbusdir}]) +fi +AC_SUBST(DBUS_SESSIONBUSDIR, [${path_dbussessionbusdir}]) + +AC_ARG_ENABLE(library, AC_HELP_STRING([--enable-library], + [install Bluetooth library]), [enable_library=${enableval}]) +AM_CONDITIONAL(LIBRARY, test "${enable_library}" = "yes") + +AC_ARG_ENABLE(systemd, AC_HELP_STRING([--disable-systemd], + [disable systemd integration]), [enable_systemd=${enableval}]) +AM_CONDITIONAL(SYSTEMD, test "${enable_systemd}" != "no") + +AC_ARG_WITH([systemdsystemunitdir], + AC_HELP_STRING([--with-systemdsystemunitdir=DIR], + [path to systemd system unit directory]), + [path_systemunitdir=${withval}]) +if (test "${enable_systemd}" != "no" && test -z "${path_systemunitdir}"); then + AC_MSG_CHECKING([systemd system unit dir]) + path_systemunitdir="`$PKG_CONFIG --variable=systemdsystemunitdir systemd`" + if (test -z "${path_systemunitdir}"); then + AC_MSG_ERROR([systemd system unit directory is required]) + fi + AC_MSG_RESULT([${path_systemunitdir}]) +fi +AC_SUBST(SYSTEMD_SYSTEMUNITDIR, [${path_systemunitdir}]) + +AC_ARG_WITH([systemduserunitdir], + AC_HELP_STRING([--with-systemduserunitdir=DIR], + [path to systemd user unit directory]), + [path_userunitdir=${withval}]) +if (test "${enable_systemd}" != "no" && test -z "${path_userunitdir}"); then + AC_MSG_CHECKING([systemd user unit dir]) + path_userunitdir="`$PKG_CONFIG --variable=systemduserunitdir systemd`" + if (test -z "${path_userunitdir}"); then + AC_MSG_ERROR([systemd user unit directory is required]) + fi + AC_MSG_RESULT([${path_userunitdir}]) +fi +AC_SUBST(SYSTEMD_USERUNITDIR, [${path_userunitdir}]) + +AC_ARG_ENABLE(datafiles, AC_HELP_STRING([--disable-datafiles], + [do not install configuration and data files]), + [enable_datafiles=${enableval}]) +AM_CONDITIONAL(DATAFILES, test "${enable_datafiles}" != "no") + +AC_ARG_ENABLE(manpages, AC_HELP_STRING([--enable-manpages], + [enable building of manual pages]), + [enable_manpages=${enableval}]) +AM_CONDITIONAL(MANPAGES, test "${enable_manpages}" = "yes") + +if (test "${prefix}" = "NONE"); then + dnl no prefix and no localstatedir, so default to /var + if (test "$localstatedir" = '${prefix}/var'); then + AC_SUBST([localstatedir], ['/var']) + fi + + prefix="${ac_default_prefix}" +fi + +if (test "$localstatedir" = '${prefix}/var'); then + storagedir="${prefix}/var/lib/headset-daemon" +else + storagedir="${localstatedir}/lib/headset-daemon" +fi +AC_DEFINE_UNQUOTED(STORAGEDIR, "${storagedir}", + [Directory for the storage files]) + +if (test "$sysconfdir" = '${prefix}/etc'); then + configdir="${prefix}/etc/headsetd" +else + configdir="${sysconfdir}/headsetd" +fi +AC_DEFINE_UNQUOTED(CONFIGDIR, "${configdir}", + [Directory for the configuration files]) +AC_SUBST(CONFIGDIR, "${configdir}") + +AC_OUTPUT(Makefile) diff --git a/doc/design.txt b/doc/design.txt new file mode 100644 index 0000000..4539c56 --- /dev/null +++ b/doc/design.txt @@ -0,0 +1,40 @@ + + + bluez headsetd pulseaudio + | | | + |<---------------------| | + | RegisterProfile | | + | |<----------------------| + | | RegisterAgent | + | | | + | | | + |--------------------->| | + bluetooth | NewConnection |---------------------->| + connection | | NewConnection | Agent can choose to + | | | manage rfcomm or not + | | | + | | | A new Headset Device h + | | | is made + | | | + | |<----------------------| + | | h.GetTransport | On the new device, we + | | | request a transport t. + | | | + | | | + | |<----------------------| + | | t.Acquire | We get fd to read/write + | | | + | | | + | |<----------------------| + | | t.Release | + | | | + | | | + | |<----------------------| + | | h.ReleaseTransport | + |--------------------->| | + | RequestDisconnection | | + | | | Release all transport + | |---------------------->| + | | RequestDisconnection | + | | | + | | | diff --git a/doc/headset-api.txt b/doc/headset-api.txt new file mode 100644 index 0000000..c27228c --- /dev/null +++ b/doc/headset-api.txt @@ -0,0 +1,171 @@ +Headset D-Bus API description +***************************** + + +HeadsetManager hierarchy +======================== + +Service org.freedesktop.Headset +Interface org.freedesktop.HeadsetManager +Object path / + + The HeadsetManager interface manages the detected Devices + and Registered Agents. + +Methods array{object,dict} GetDevices() + + Get an array of device objects and properties + that represent the currently attached devices. + + This method call should only be used once when an + application starts up. Further device additions + and removal shall be monitored via DeviceAdded and + DeviceRemoved signals. + + void Register(object path, dict) + + Registers a Headset Agent with a specific + path (freely selectable by the subsystem) and + properties + + Possible Errors: [service].Error.InvalidArguments + + void Unregister(object path) + + Unregisters a Headset Agent with specific path + + Possible Errors: [service].Error.NotFound + [service].Error.InvalidArguments + [service].Error.NotAllowed + +Signals DeviceAdded(object path, dict properties) + + Signal that is sent when a new device is added. It + contains the object path of new device and its + properties. + + DeviceRemoved(object path) + + Signal that is sent when a device has been removed. + The object path is no longer accessible after this + signal and only emitted for reference. + + +Headset Agent hierarchy +======================= + +Service <freely defined> +Interface org.freedesktop.HeadsetAgent +Object path <freely defined> + + The Agent is used to take control of the rfcomm connection of + a Headset. When no Agent is registered, the Headset daemon + will manage the rfcomm channel itself. + +Methods bool NewConnection(object device, fd rfcomm, dict properties) + + Notify the agent that a new device is connected. The Agent + returns True if it will use the rfcomm fd. + When all registered Agents return False, headsetd will + manage the rfcomm instead. + + Possible Errors: [service].Error.NotAuthorized + [service].Error.Failed + + void RequestDisconnection(object device) + + Notify the agent that the connection with the device + ended. + + void Release() + + Notifies the Agent that it is no longer registered + + +Headset hierarchy +================= + +Service org.freedesktop.Headset +Interface org.freedesktop.Headset +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX + +Methods object GetTransport(dict properties) + + Get a Transport Object for the given properties + + Possible Errors: [service].Error.NotAuthorized + [service].Error.Failed + + void ReleaseTransport(object transport) + + Releases transport descriptor. + +Properties object Device [readonly] + + The bluetooth Device object. + + +HeadsetTransport hierarchy +========================== + +Service org.freedesktop.Headset +Interface org.freedesktop.HeadsetTransport +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/fdX + +Methods fd, uint16, uint16 Acquire() + + Acquire transport file descriptor and the MTU for read + and write respectively. + + Possible Errors: [service].Error.NotAuthorized + [service].Error.Failed + + void Release() + + Releases file descriptor. + +Properties object Device [readonly] + + The Device object. + + string UUID [readonly] + + UUID of the profile which this transport is for. + + byte Codec [readonly] + + Assigned number of codec that the transport support. + The values should match the profile specification which + is indicated by the UUID. + + string State [readonly] + + Indicates the state of the transport. Possible + values are: + "idle": not streaming + "pending": streaming but not acquired + "active": streaming and acquired + + uint16 Delay [readwrite] + + Optional. Transport delay in 1/10 of millisecond, this + property is only writeable when the transport was + acquired by the sender. + + byte MicrophoneGain [readwrite] + + Optional. Indicates volume level of the transport's + incoming audio stream. This + property is only writeable when the transport was + acquired by the sender. + + Possible Values: 0-15 + + byte SpeakerGain [readwrite] + + Optional. Indicates volume level of the transport's + outgoing audio stream. This + property is only writeable when the transport was + acquired by the sender. + + Possible Values: 0-15 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); |