Headphoned 1.6 with support for Bluetooth headsets
[headphoned] / src / headphoned.c
1 /**
2  * headphoned for the Nokia N900
3  *
4  * The headphone daemon watches the state of the headphone
5  * plug (connected, disconnected) and carries out actions
6  * based on these events.
7  *
8  * Contributions:
9  *   Faheem Pervez - D-Bus/HAL-based disconnect detection
10  *
11  * Initial working version: 2009-10-21
12  *
13  * Copyright (c) 2009-2010 Thomas Perl <thpinfo.com>
14  *
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this package; if not, write to the Free Software
27  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
28  **/
29
30 #include <stdio.h>
31 #include <unistd.h>
32 #include <assert.h>
33 #include <glib.h>
34 #include <libosso.h>
35 #include <dbus/dbus.h>
36 #include <fcntl.h>
37 #include <stdlib.h>
38
39 #include "config.h"
40
41 #ifdef HEADPHONED_DEBUG
42 #    define debug_msg(...) { \
43     fprintf(stderr, "DEBUG: "); \
44     fprintf(stderr, __VA_ARGS__); \
45     fputc('\n',stderr); \
46 }
47 #else
48 #    define debug_msg(...)
49 #endif
50
51 #define warning_msg(...) { \
52     fprintf(stderr, "WARNING: "); \
53     fprintf(stderr, __VA_ARGS__); \
54     fputc('\n', stderr); \
55 }
56
57 /* State file for wired headsets without microphone */
58 #define STATE_FILE "/sys/devices/platform/gpio-switch/headphone/state"
59 #define STATE_CONNECTED_STR "connected"
60 #define STATE_DISCONNECTED_STR "disconnected"
61
62 /* Where the HAL Device Manager sits on the D-Bus */
63 #define HAL_MANAGER_PATH "/org/freedesktop/Hal/Manager"
64 #define HAL_MANAGER_INTF "org.freedesktop.Hal.Manager"
65
66 /* The signal to watch when headphones are removed */
67 #define HAL_MANAGER_SIGN "DeviceRemoved"
68
69 /* A D-Bus rule for filtering events we're interested in */
70 #define DBUS_RULE "type='signal',interface='" HAL_MANAGER_INTF \
71                   "',path='" HAL_MANAGER_PATH \
72                   "',member='" HAL_MANAGER_SIGN "'"
73
74 /* The name for headphones (w/ microphone?) on the D-Bus */
75 #define HEADPHONE_UDI_NAME "/org/freedesktop/Hal/devices/computer_logicaldev_input_1"
76
77 /* Where the Media Player backend sits on the D-Bus */
78 #ifdef DIABLO
79 #    define MEDIA_SERVER_SRVC "com.nokia.osso_media_server"
80 #    define MEDIA_SERVER_PATH "/com/nokia/osso_media_server"
81 #    define MEDIA_SERVER_INTF "com.nokia.osso_media_server.music"
82 #else
83 #    define MEDIA_SERVER_SRVC "com.nokia.mafw.renderer.Mafw-Gst-Renderer-Plugin.gstrenderer"
84 #    define MEDIA_SERVER_PATH "/com/nokia/mafw/renderer/gstrenderer"
85 #    define MEDIA_SERVER_INTF "com.nokia.mafw.renderer"
86 #endif
87
88 /* Where Panucci sits on the D-Bus */
89 #define PANUCCI_SRVC "org.panucci.panucciInterface"
90 #define PANUCCI_PATH "/panucciInterface"
91 #define PANUCCI_INTF "org.panucci.panucciInterface"
92
93 /* MPlayer is not yet 'on the bus', so we use a good old named pipe */
94 #define MPLAYER_FIFO "/etc/headphoned/mplayer-input"
95
96
97 typedef struct {
98     DBusConnection* session_bus;
99     DBusConnection* system_bus;
100     osso_context_t* osso;
101     gboolean initial;
102 } Headphoned;
103
104 /* This has to be globally accessible (for signal handling below) */
105 static GMainLoop* loop = NULL;
106
107 static void
108 sig_handler(int sig G_GNUC_UNUSED)
109 {
110     debug_msg("Received signal: %d", sig);
111
112     if (loop != NULL && g_main_loop_is_running(loop)) {
113         g_main_loop_quit(loop);
114     }
115 }
116
117 Headphoned*
118 headphoned_new()
119 {
120     Headphoned* this = g_new0(Headphoned, 1);
121
122     this->osso = osso_initialize("headphoned", "1.0", FALSE, NULL);
123     assert(this->osso != NULL);
124
125     this->session_bus = (DBusConnection*)osso_get_dbus_connection(this->osso);
126     this->system_bus = (DBusConnection*)osso_get_sys_dbus_connection(this->osso);
127
128     this->initial = TRUE;
129
130     return this;
131 }
132
133 void
134 broadcast_pause_signal(Headphoned* headphoned)
135 {
136     int mplayer_fifo;
137
138     debug_msg("Sending pause signal to Media Player");
139     /* Nokia Media Player */
140     osso_rpc_run(headphoned->osso,
141             MEDIA_SERVER_SRVC,
142             MEDIA_SERVER_PATH,
143             MEDIA_SERVER_INTF,
144             "pause",
145             NULL,
146             DBUS_TYPE_INVALID);
147
148     /* Panucci */
149     if (dbus_bus_name_has_owner(headphoned->session_bus, PANUCCI_SRVC, NULL)) {
150         debug_msg("Sending pause signal to Panucci");
151         osso_rpc_run(headphoned->osso,
152                 PANUCCI_SRVC,
153                 PANUCCI_PATH,
154                 PANUCCI_INTF,
155                 "pause",
156                 NULL,
157                 DBUS_TYPE_INVALID);
158     } else {
159         debug_msg("Panucci not running - not sending pause signal.");
160     }
161
162     /* MPlayer */
163     if ((mplayer_fifo = open(MPLAYER_FIFO, O_WRONLY | O_NONBLOCK)) != -1) {
164         debug_msg("Sending pause signal to MPlayer");
165         write(mplayer_fifo, "pause\n", 6);
166         close(mplayer_fifo);
167     } else {
168         debug_msg("MPlayer not running - not sending pause signal.");
169     }
170 }
171
172 /* Handler for messages from "wired" headphones (via sysfs) */
173 gboolean
174 on_file_changed(GIOChannel* source, GIOCondition condition, gpointer data)
175 {
176     Headphoned* headphoned = (Headphoned*)data;
177     gchar* result;
178
179     debug_msg("File %s has changed.", STATE_FILE);
180
181     g_io_channel_seek_position(source, 0, G_SEEK_SET, NULL);
182     g_io_channel_read_line(source, &result, NULL, NULL, NULL);
183     g_strstrip(result);
184
185     if (g_ascii_strcasecmp(result, STATE_DISCONNECTED_STR) == 0) {
186         if (headphoned->initial == TRUE) {
187             debug_msg("Ignoring initial file change.");
188             headphoned->initial = FALSE;
189         } else {
190             debug_msg("Broadcasting pause signal (cause: state file)");
191             broadcast_pause_signal(headphoned);
192         }
193     }
194
195     g_free(result);
196     return TRUE;
197 }
198
199 /* Handler for messages from Bluetooth + "wired w/ mic" headphones */
200 static DBusHandlerResult
201 on_msg_recieved(DBusConnection* connection G_GNUC_UNUSED, DBusMessage* message, void* data)
202 {
203     Headphoned* headphoned = (Headphoned*)data;
204     DBusMessageIter iter;
205     const char* result = NULL;
206
207     dbus_message_iter_init(message, &iter);
208     dbus_message_iter_get_basic(&iter, &result);
209
210     if (g_str_equal(result, HEADPHONE_UDI_NAME)) {
211         debug_msg("Broadcasting pause signal (cause: Hal via D-Bus)");
212         broadcast_pause_signal(headphoned);
213         return DBUS_HANDLER_RESULT_HANDLED;
214     } else {
215         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
216     }
217 }
218
219 int
220 main(int argc, char* argv[])
221 {
222     Headphoned *headphoned = NULL;
223     GIOChannel* state = NULL;
224
225     signal(SIGINT, sig_handler);
226     signal(SIGQUIT, sig_handler);
227     signal(SIGTERM, sig_handler);
228
229     loop = g_main_loop_new(NULL, FALSE);
230     headphoned = headphoned_new();
231
232     state = g_io_channel_new_file(STATE_FILE, "r", NULL);
233     if (state != NULL) {
234         debug_msg("Adding I/O watch on %s", STATE_FILE);
235         g_io_add_watch(state, G_IO_PRI, on_file_changed, headphoned);
236     } else {
237         warning_msg("Cannot open state file: %s", STATE_FILE);
238     }
239
240     debug_msg("Registering D-Bus rule: %s", DBUS_RULE);
241     dbus_bus_add_match(headphoned->system_bus, DBUS_RULE, NULL);
242     dbus_connection_add_filter(headphoned->system_bus, on_msg_recieved, headphoned, NULL);
243
244     debug_msg("Entering GLib main loop...");
245     g_main_loop_run(loop);
246     debug_msg("...main loop finished. Cleaning up.");
247
248     if (state != NULL) {
249         g_io_channel_unref(state);
250     }
251
252     osso_deinitialize(headphoned->osso);
253     g_free(headphoned);
254
255     return EXIT_SUCCESS;
256 }
257