Headphoned 1.6 with support for Bluetooth headsets 1.6
authorThomas Perl <thp@thpinfo.com>
Tue, 2 Feb 2010 19:47:09 +0000 (20:47 +0100)
committerThomas Perl <thp@thpinfo.com>
Tue, 2 Feb 2010 19:47:09 +0000 (20:47 +0100)
AUTHORS
debian/changelog
makefile
src/config.h
src/headphoned.c

diff --git a/AUTHORS b/AUTHORS
index 5900844..8b73f34 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,3 +1,4 @@
 Thomas Perl <thp@thpinfo.com>
 Joost Kop <joost.kop@gmail.com>
+Faheem Pervez <trippin1@gmail.com>
 
index ccdd2f7..9d01476 100644 (file)
@@ -1,3 +1,14 @@
+headphoned (1.6) fremantle; urgency=low
+
+  * Support for Bluetooth headphones (thanks to Faheem Pervez for
+    the initial patch and Alan Peery for sponsoring a Bluetooth
+    headset with with I can debug and test the implementation :)
+  * Compile-time option for enabling debugging output
+  * Removed support for volume setting (it did never work right)
+  * Code clean-up and more comments and #defines for readability
+
+ -- Thomas Perl <thp@thpinfo.com>  Tue,  2 Feb 2010 20:42:20 +0100
+
 headphoned (1.5) fremantle; urgency=low
 
   * First release for Maemo 5
index 0513c38..cdbc7f9 100644 (file)
--- a/makefile
+++ b/makefile
@@ -6,6 +6,14 @@ LDFLAGS += `pkg-config --libs glib-2.0 gconf-2.0 libosso dbus-1`
 DESTDIR ?= 
 PREFIX ?= /usr
 
+ifdef DEBUG
+  CFLAGS += -DHEADPHONED_DEBUG
+endif
+
+all: $(PROG)
+
+src/headphoned.o: src/headphoned.c src/config.h
+
 $(PROG): $(OBJS)
        $(CC) $(LDFLAGS) $(LIBS) $^ -o $@
        strip $@
@@ -22,5 +30,6 @@ install: $(PROG)
 clean:
        rm -f $(PROG) $(OBJS)
 
-.PHONY: install clean
+.PHONY: all install clean
+.DEFAULT: all
 
index 7f4b835..00f4cbe 100644 (file)
@@ -2,12 +2,7 @@
  * Configuration file for headphoned
  **/
 
-// Define Diablo if you are compiling for Maemo 4
-//#define DIABLO
-
-// Volume control is currently broken, as something is messing
-// with the controls from outside this process in GConf..
-//#define ENABLE_VOLUME_CONTROL
-#define ENABLE_PAUSE_ON_DISCONNECT
-
+/* Define Diablo if you are compiling for Maemo 4
+#define DIABLO
+*/
 
index ba45861..6c9d921 100644 (file)
@@ -1,13 +1,12 @@
 /**
- * headphoned for the Nokia N8x0 and the N900
+ * headphoned for the Nokia N900
  *
  * The headphone daemon watches the state of the headphone
  * plug (connected, disconnected) and carries out actions
  * based on these events.
  *
- * Currently supported:
- *   * Send "pause" to the media player on disconnect
- *   * Maintain different volume settings for each state
+ * Contributions:
+ *   Faheem Pervez - D-Bus/HAL-based disconnect detection
  *
  * Initial working version: 2009-10-21
  *
 #include <unistd.h>
 #include <assert.h>
 #include <glib.h>
-#include <gconf/gconf-client.h>
 #include <libosso.h>
 #include <dbus/dbus.h>
 #include <fcntl.h>
+#include <stdlib.h>
 
 #include "config.h"
 
+#ifdef HEADPHONED_DEBUG
+#    define debug_msg(...) { \
+    fprintf(stderr, "DEBUG: "); \
+    fprintf(stderr, __VA_ARGS__); \
+    fputc('\n',stderr); \
+}
+#else
+#    define debug_msg(...)
+#endif
+
+#define warning_msg(...) { \
+    fprintf(stderr, "WARNING: "); \
+    fprintf(stderr, __VA_ARGS__); \
+    fputc('\n', stderr); \
+}
+
+/* State file for wired headsets without microphone */
 #define STATE_FILE "/sys/devices/platform/gpio-switch/headphone/state"
 #define STATE_CONNECTED_STR "connected"
 #define STATE_DISCONNECTED_STR "disconnected"
 
-#define GCONF_VOLUME_CONTROL "/apps/osso/sound/master_volume"
+/* Where the HAL Device Manager sits on the D-Bus */
+#define HAL_MANAGER_PATH "/org/freedesktop/Hal/Manager"
+#define HAL_MANAGER_INTF "org.freedesktop.Hal.Manager"
 
+/* The signal to watch when headphones are removed */
+#define HAL_MANAGER_SIGN "DeviceRemoved"
+
+/* A D-Bus rule for filtering events we're interested in */
+#define DBUS_RULE "type='signal',interface='" HAL_MANAGER_INTF \
+                  "',path='" HAL_MANAGER_PATH \
+                  "',member='" HAL_MANAGER_SIGN "'"
+
+/* The name for headphones (w/ microphone?) on the D-Bus */
+#define HEADPHONE_UDI_NAME "/org/freedesktop/Hal/devices/computer_logicaldev_input_1"
+
+/* Where the Media Player backend sits on the D-Bus */
 #ifdef DIABLO
 #    define MEDIA_SERVER_SRVC "com.nokia.osso_media_server"
 #    define MEDIA_SERVER_PATH "/com/nokia/osso_media_server"
 #    define MEDIA_SERVER_INTF "com.nokia.mafw.renderer"
 #endif
 
+/* Where Panucci sits on the D-Bus */
 #define PANUCCI_SRVC "org.panucci.panucciInterface"
 #define PANUCCI_PATH "/panucciInterface"
 #define PANUCCI_INTF "org.panucci.panucciInterface"
 
+/* MPlayer is not yet 'on the bus', so we use a good old named pipe */
 #define MPLAYER_FIFO "/etc/headphoned/mplayer-input"
 
-enum { STATE_UNKNOWN, STATE_CONNECTED, STATE_DISCONNECTED, STATE_COUNT };
 
 typedef struct {
-       GConfClient* client;
-       DBusConnection* session_bus;
-       osso_context_t* osso;
-       guint state;
-       gint volume[STATE_COUNT];
-       gboolean initial;
+    DBusConnection* session_bus;
+    DBusConnection* system_bus;
+    osso_context_t* osso;
+    gboolean initial;
 } Headphoned;
 
-static
-GMainLoop* loop = NULL;
+/* This has to be globally accessible (for signal handling below) */
+static GMainLoop* loop = NULL;
 
 static void
-sig_handler (int sig G_GNUC_UNUSED)
+sig_handler(int sig G_GNUC_UNUSED)
 {
-       if (loop && g_main_loop_is_running (loop)) {
-               g_main_loop_quit (loop);
-       }
-}
+    debug_msg("Received signal: %d", sig);
 
-void
-on_volume_changed(GConfClient* client, guint gnxn_id, GConfEntry* entry,
-               gpointer data)
-{
-       Headphoned* headphoned = (Headphoned*)data;
-       headphoned->volume[headphoned->state] =
-               gconf_value_get_int(entry->value);
+    if (loop != NULL && g_main_loop_is_running(loop)) {
+        g_main_loop_quit(loop);
+    }
 }
 
 Headphoned*
 headphoned_new()
 {
-       Headphoned* this = g_new0(Headphoned, 1);
+    Headphoned* this = g_new0(Headphoned, 1);
 
-       this->osso = osso_initialize("headphoned", "1.0", FALSE, NULL);
-       assert(this->osso != NULL);
+    this->osso = osso_initialize("headphoned", "1.0", FALSE, NULL);
+    assert(this->osso != NULL);
 
-#ifdef ENABLE_VOLUME_CONTROL
-       this->client = gconf_client_get_default();
-       gconf_client_add_dir(this->client, GCONF_VOLUME_CONTROL,
-                       GCONF_CLIENT_PRELOAD_NONE, NULL);
-       gconf_client_notify_add(this->client, GCONF_VOLUME_CONTROL,
-                       on_volume_changed, this, NULL, NULL);
-#endif
+    this->session_bus = (DBusConnection*)osso_get_dbus_connection(this->osso);
+    this->system_bus = (DBusConnection*)osso_get_sys_dbus_connection(this->osso);
+
+    this->initial = TRUE;
 
-       this->session_bus = (DBusConnection*)osso_get_dbus_connection(this->osso);
-       this->initial = TRUE;
+    return this;
+}
 
-       return this;
+void
+broadcast_pause_signal(Headphoned* headphoned)
+{
+    int mplayer_fifo;
+
+    debug_msg("Sending pause signal to Media Player");
+    /* Nokia Media Player */
+    osso_rpc_run(headphoned->osso,
+            MEDIA_SERVER_SRVC,
+            MEDIA_SERVER_PATH,
+            MEDIA_SERVER_INTF,
+            "pause",
+            NULL,
+            DBUS_TYPE_INVALID);
+
+    /* Panucci */
+    if (dbus_bus_name_has_owner(headphoned->session_bus, PANUCCI_SRVC, NULL)) {
+        debug_msg("Sending pause signal to Panucci");
+        osso_rpc_run(headphoned->osso,
+                PANUCCI_SRVC,
+                PANUCCI_PATH,
+                PANUCCI_INTF,
+                "pause",
+                NULL,
+                DBUS_TYPE_INVALID);
+    } else {
+        debug_msg("Panucci not running - not sending pause signal.");
+    }
+
+    /* MPlayer */
+    if ((mplayer_fifo = open(MPLAYER_FIFO, O_WRONLY | O_NONBLOCK)) != -1) {
+        debug_msg("Sending pause signal to MPlayer");
+        write(mplayer_fifo, "pause\n", 6);
+        close(mplayer_fifo);
+    } else {
+        debug_msg("MPlayer not running - not sending pause signal.");
+    }
 }
 
+/* Handler for messages from "wired" headphones (via sysfs) */
 gboolean
 on_file_changed(GIOChannel* source, GIOCondition condition, gpointer data)
 {
-       Headphoned* headphoned = (Headphoned*)data;
-#ifdef ENABLE_VOLUME_CONTROL
-       gint volume = headphoned->volume[headphoned->state];
-#endif
-       gchar* result;
-       int mplayer_fifo;
-
-       g_io_channel_seek_position(source, 0, G_SEEK_SET, NULL);
-       g_io_channel_read_line(source, &result, NULL, NULL, NULL);
-       g_strstrip(result);
-       
-       if (g_ascii_strcasecmp(result, STATE_CONNECTED_STR) == 0) {
-               headphoned->state = STATE_CONNECTED;
-       } else {
-               headphoned->state = STATE_DISCONNECTED;
-#ifdef ENABLE_PAUSE_ON_DISCONNECT
-               if (headphoned->initial == FALSE) {
-                       /* Nokia Media Player */
-                       osso_rpc_run(headphoned->osso,
-                                       MEDIA_SERVER_SRVC,
-                                       MEDIA_SERVER_PATH,
-                                       MEDIA_SERVER_INTF,
-                                       "pause",
-                                       NULL,
-                                       DBUS_TYPE_INVALID);
-
-                       /* Panucci */
-                       if (dbus_bus_name_has_owner(headphoned->session_bus,
-                                                       PANUCCI_SRVC,
-                                                       NULL)) {
-                               osso_rpc_run(headphoned->osso,
-                                               PANUCCI_SRVC,
-                                               PANUCCI_PATH,
-                                               PANUCCI_INTF,
-                                               "pause",
-                                               NULL,
-                                               DBUS_TYPE_INVALID);
-                       }
-
-                       /* MPlayer */
-                       if ((mplayer_fifo = open(MPLAYER_FIFO,
-                                               O_WRONLY | O_NONBLOCK)) != -1) {
-                               write(mplayer_fifo, "pause\n", 6);
-                               close(mplayer_fifo);
-                       }
-               }
-#endif
-       }
-
-#ifdef ENABLE_VOLUME_CONTROL
-       gint new_volume = headphoned->volume[headphoned->state];
-       if (new_volume != volume) {
-               gconf_client_set_int(headphoned->client, GCONF_VOLUME_CONTROL,
-                               new_volume, NULL);
-               /*gconf_client_suggest_sync(headphoned->client, NULL);
-               gconf_client_clear_cache(headphoned->client);*/
-       }
-#endif
-       headphoned->initial = FALSE;
+    Headphoned* headphoned = (Headphoned*)data;
+    gchar* result;
+
+    debug_msg("File %s has changed.", STATE_FILE);
+
+    g_io_channel_seek_position(source, 0, G_SEEK_SET, NULL);
+    g_io_channel_read_line(source, &result, NULL, NULL, NULL);
+    g_strstrip(result);
+
+    if (g_ascii_strcasecmp(result, STATE_DISCONNECTED_STR) == 0) {
+        if (headphoned->initial == TRUE) {
+            debug_msg("Ignoring initial file change.");
+            headphoned->initial = FALSE;
+        } else {
+            debug_msg("Broadcasting pause signal (cause: state file)");
+            broadcast_pause_signal(headphoned);
+        }
+    }
+
+    g_free(result);
+    return TRUE;
+}
 
-       g_free(result);
-       return TRUE;
+/* Handler for messages from Bluetooth + "wired w/ mic" headphones */
+static DBusHandlerResult
+on_msg_recieved(DBusConnection* connection G_GNUC_UNUSED, DBusMessage* message, void* data)
+{
+    Headphoned* headphoned = (Headphoned*)data;
+    DBusMessageIter iter;
+    const char* result = NULL;
+
+    dbus_message_iter_init(message, &iter);
+    dbus_message_iter_get_basic(&iter, &result);
+
+    if (g_str_equal(result, HEADPHONE_UDI_NAME)) {
+        debug_msg("Broadcasting pause signal (cause: Hal via D-Bus)");
+        broadcast_pause_signal(headphoned);
+        return DBUS_HANDLER_RESULT_HANDLED;
+    } else {
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
 }
 
 int
 main(int argc, char* argv[])
 {
-       Headphoned *headphoned;
-       g_type_init();
+    Headphoned *headphoned = NULL;
+    GIOChannel* state = NULL;
 
-       signal(SIGINT, sig_handler);
-       signal(SIGQUIT, sig_handler);
-       signal(SIGTERM, sig_handler);
+    signal(SIGINT, sig_handler);
+    signal(SIGQUIT, sig_handler);
+    signal(SIGTERM, sig_handler);
 
-       loop = g_main_loop_new(NULL, FALSE);
-       headphoned = headphoned_new();
+    loop = g_main_loop_new(NULL, FALSE);
+    headphoned = headphoned_new();
 
-       GIOChannel* state = g_io_channel_new_file(STATE_FILE, "r", NULL);
-       g_io_add_watch(state, G_IO_PRI, on_file_changed, headphoned);
+    state = g_io_channel_new_file(STATE_FILE, "r", NULL);
+    if (state != NULL) {
+        debug_msg("Adding I/O watch on %s", STATE_FILE);
+        g_io_add_watch(state, G_IO_PRI, on_file_changed, headphoned);
+    } else {
+        warning_msg("Cannot open state file: %s", STATE_FILE);
+    }
 
-       g_main_loop_run(loop);
+    debug_msg("Registering D-Bus rule: %s", DBUS_RULE);
+    dbus_bus_add_match(headphoned->system_bus, DBUS_RULE, NULL);
+    dbus_connection_add_filter(headphoned->system_bus, on_msg_recieved, headphoned, NULL);
 
-#ifdef ENABLE_VOLUME_CONTROL
-       if (headphoned->client) {
-               gconf_client_remove_dir(headphoned->client, GCONF_VOLUME_CONTROL, NULL);
-               g_object_unref(headphoned->client);
-       }
-#endif
+    debug_msg("Entering GLib main loop...");
+    g_main_loop_run(loop);
+    debug_msg("...main loop finished. Cleaning up.");
+
+    if (state != NULL) {
+        g_io_channel_unref(state);
+    }
 
-       if (state) {
-               g_io_channel_unref(state);
-       }
+    osso_deinitialize(headphoned->osso);
+    g_free(headphoned);
 
-       osso_deinitialize(headphoned->osso);
-       g_free(headphoned);
-       return 0;
+    return EXIT_SUCCESS;
 }