/**
- * 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;
}