From 48663281ecfef07df888a2973f0c4bcbe5dc1b2a Mon Sep 17 00:00:00 2001 From: Thomas Perl Date: Tue, 2 Feb 2010 20:47:09 +0100 Subject: [PATCH] Headphoned 1.6 with support for Bluetooth headsets --- AUTHORS | 1 + debian/changelog | 11 +++ makefile | 11 ++- src/config.h | 11 +-- src/headphoned.c | 286 +++++++++++++++++++++++++++++++----------------------- 5 files changed, 188 insertions(+), 132 deletions(-) diff --git a/AUTHORS b/AUTHORS index 5900844..8b73f34 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,3 +1,4 @@ Thomas Perl Joost Kop +Faheem Pervez diff --git a/debian/changelog b/debian/changelog index ccdd2f7..9d01476 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 Tue, 2 Feb 2010 20:42:20 +0100 + headphoned (1.5) fremantle; urgency=low * First release for Maemo 5 diff --git a/makefile b/makefile index 0513c38..cdbc7f9 100644 --- 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 diff --git a/src/config.h b/src/config.h index 7f4b835..00f4cbe 100644 --- a/src/config.h +++ b/src/config.h @@ -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 +*/ diff --git a/src/headphoned.c b/src/headphoned.c index ba45861..6c9d921 100644 --- a/src/headphoned.c +++ b/src/headphoned.c @@ -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 * @@ -32,19 +31,50 @@ #include #include #include -#include #include #include #include +#include #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" @@ -55,163 +85,173 @@ # 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; } -- 1.7.9.5