summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am95
-rwxr-xr-xbootstrap7
-rwxr-xr-xbootstrap-configure15
-rw-r--r--configure.ac185
-rw-r--r--doc/design.txt40
-rw-r--r--doc/headset-api.txt171
-rw-r--r--src/headsetd-old.c785
-rw-r--r--src/headsetd.c100
-rw-r--r--src/hsd-headset-transport.c339
-rw-r--r--src/hsd-headset-transport.h42
-rw-r--r--src/hsd-headset.c254
-rw-r--r--src/hsd-headset.h32
-rw-r--r--src/hsd-manager.c222
-rw-r--r--src/hsd-manager.h35
-rw-r--r--src/hsd-profile.c247
-rw-r--r--src/hsd-profile.h47
-rw-r--r--src/hsd.c35
-rw-r--r--src/hsd.h30
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);