headphoned 1.9 "Hold The Line" released
[headphoned.git] / src / headphoned.c
blobf849d5dac46913d6067422931c0e0f440956d4cf
1 /**
2 * headphoned for the Nokia N900
4 * The headphone daemon watches the state of the headphone
5 * plug (connected, disconnected) and carries out actions
6 * based on these events.
8 * Contributions:
9 * Faheem Pervez - D-Bus/HAL-based disconnect detection
11 * Initial working version: 2009-10-21
13 * Copyright (c) 2009-2010 Thomas Perl <thpinfo.com>
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.
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.
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 **/
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 <libplayback/playback.h>
37 #include <fcntl.h>
38 #include <stdlib.h>
40 #include "config.h"
42 #ifdef HEADPHONED_DEBUG
43 # define debug_msg(...) { \
44 fprintf(stderr, "DEBUG: "); \
45 fprintf(stderr, __VA_ARGS__); \
46 fputc('\n',stderr); \
48 #else
49 # define debug_msg(...)
50 #endif
52 #define warning_msg(...) { \
53 fprintf(stderr, "WARNING: "); \
54 fprintf(stderr, __VA_ARGS__); \
55 fputc('\n', stderr); \
58 /* State file for wired headsets without microphone */
59 #define STATE_FILE "/sys/devices/platform/gpio-switch/headphone/state"
60 #define STATE_CONNECTED_STR "connected"
61 #define STATE_DISCONNECTED_STR "disconnected"
63 /* Where the HAL Device Manager sits on the D-Bus */
64 #define HAL_MANAGER_PATH "/org/freedesktop/Hal/Manager"
65 #define HAL_MANAGER_INTF "org.freedesktop.Hal.Manager"
67 /* The signal to watch when headphones are removed */
68 #define HAL_MANAGER_SIGN "DeviceRemoved"
70 /* A D-Bus rule for filtering events we're interested in */
71 #define DBUS_RULE_HAL "type='signal',interface='" HAL_MANAGER_INTF \
72 "',path='" HAL_MANAGER_PATH \
73 "',member='" HAL_MANAGER_SIGN "'"
75 /* The name for headphones (w/ microphone?) on the D-Bus */
76 #define HEADPHONE_UDI_NAME "/org/freedesktop/Hal/devices/computer_logicaldev_input_1"
78 #define MCE_SIGNAL_INTF "com.nokia.mce.signal"
79 #define MCE_SIGNAL_PATH "/com/nokia/mce/signal"
80 #define MCE_CALL_STATE_SIG "sig_call_state_ind"
82 #define MCE_CALL_ACTIVE "active"
83 #define MCE_CALL_RINGING "ringing"
84 #define MCE_CALL_DISCONNECT "none"
86 /* A D-Bus rule for getting the call state (active/none/...) */
87 #define DBUS_RULE_CALL "type='signal',interface='" MCE_SIGNAL_INTF \
88 "',path='" MCE_SIGNAL_PATH \
89 "',member='" MCE_CALL_STATE_SIG "'"
91 /* Delay in milliseconds for the pause-after-call pause signal */
92 #define POST_CALL_PAUSE_DELAY_MS 500
94 /* Where the Media Player backend sits on the D-Bus */
95 #ifdef DIABLO
96 # define MEDIA_SERVER_SRVC "com.nokia.osso_media_server"
97 # define MEDIA_SERVER_PATH "/com/nokia/osso_media_server"
98 # define MEDIA_SERVER_INTF "com.nokia.osso_media_server.music"
99 #else
100 # define MEDIA_SERVER_SRVC "com.nokia.mafw.renderer.Mafw-Gst-Renderer-Plugin.gstrenderer"
101 # define MEDIA_SERVER_PATH "/com/nokia/mafw/renderer/gstrenderer"
102 # define MEDIA_SERVER_INTF "com.nokia.mafw.renderer"
103 #endif
105 /* Where Panucci sits on the D-Bus */
106 #define PANUCCI_SRVC "org.panucci.panucciInterface"
107 #define PANUCCI_PATH "/panucciInterface"
108 #define PANUCCI_INTF "org.panucci.panucciInterface"
110 /* MPlayer is not yet 'on the bus', so we use a good old named pipe */
111 #define MPLAYER_FIFO "/etc/headphoned/mplayer-input"
113 /* Martin's FM Radio app on the D-Bus */
114 #define FMRADIO_SRVC "de.pycage.fmradio"
115 #define FMRADIO_PATH "/de/pycage/fmradio"
116 #define FMRADIO_INTF "de.pycage.fmradio"
118 /* Martin's MediaBox on the D-Bus */
119 #define MEDIABOX_SRVC "de.pycage.mediabox"
120 #define MEDIABOX_PATH "/de/pycage/mediabox/control"
121 #define MEDIABOX_INTF "de.pycage.mediabox.control"
123 typedef struct {
124 DBusConnection* session_bus;
125 DBusConnection* system_bus;
126 osso_context_t* osso;
127 pb_playback_t *playback;
128 gboolean initial;
129 gboolean call_active; /* True while a call is active */
130 gboolean unplugged_on_call; /* True when unplugged during active call */
131 } Headphoned;
133 /* This has to be globally accessible (for signal handling below) */
134 static GMainLoop* loop = NULL;
136 static void
137 sig_handler(int sig G_GNUC_UNUSED)
139 debug_msg("Received signal: %d", sig);
141 if (loop != NULL && g_main_loop_is_running(loop)) {
142 g_main_loop_quit(loop);
146 static void
147 libplayback_state_request_handler(pb_playback_t *pb,
148 enum pb_state_e req_state G_GNUC_UNUSED,
149 pb_req_t *req,
150 void *data G_GNUC_UNUSED)
152 pb_playback_req_completed(pb, req);
155 static void
156 libplayback_state_reply(pb_playback_t *pb,
157 enum pb_state_e granted_state G_GNUC_UNUSED,
158 const char *reason G_GNUC_UNUSED,
159 pb_req_t *req,
160 void *data G_GNUC_UNUSED)
162 pb_playback_req_completed(pb, req);
165 Headphoned*
166 headphoned_new()
168 Headphoned* this = g_new0(Headphoned, 1);
170 this->osso = osso_initialize("headphoned", "1.0", FALSE, NULL);
171 assert(this->osso != NULL);
173 this->session_bus = (DBusConnection*)osso_get_dbus_connection(this->osso);
174 this->system_bus = (DBusConnection*)osso_get_sys_dbus_connection(this->osso);
176 this->playback = pb_playback_new_2(this->session_bus,
177 PB_CLASS_MEDIA,
178 PB_FLAG_AUDIO,
179 PB_STATE_STOP,
180 (PBStateRequest)libplayback_state_request_handler,
181 NULL);
183 this->initial = TRUE;
184 this->call_active = FALSE;
185 this->unplugged_on_call = FALSE;
187 return this;
190 void
191 broadcast_pause_signal(Headphoned* headphoned)
193 int mplayer_fifo;
195 debug_msg("Sending pause signal to Media Player");
196 /* Nokia Media Player */
197 osso_rpc_run(headphoned->osso,
198 MEDIA_SERVER_SRVC,
199 MEDIA_SERVER_PATH,
200 MEDIA_SERVER_INTF,
201 "pause",
202 NULL,
203 DBUS_TYPE_INVALID);
204 pb_playback_req_state(headphoned->playback,
205 PB_STATE_STOP,
206 (PBStateReply)libplayback_state_reply,
207 NULL);
209 /* Panucci */
210 if (dbus_bus_name_has_owner(headphoned->session_bus, PANUCCI_SRVC, NULL)) {
211 debug_msg("Sending pause signal to Panucci");
212 osso_rpc_run(headphoned->osso,
213 PANUCCI_SRVC,
214 PANUCCI_PATH,
215 PANUCCI_INTF,
216 "pause",
217 NULL,
218 DBUS_TYPE_INVALID);
219 } else {
220 debug_msg("Panucci not running - not sending pause signal.");
223 /* MPlayer */
224 if ((mplayer_fifo = open(MPLAYER_FIFO, O_WRONLY | O_NONBLOCK)) != -1) {
225 debug_msg("Sending pause signal to MPlayer");
226 write(mplayer_fifo, "pause\n", 6);
227 close(mplayer_fifo);
228 } else {
229 debug_msg("MPlayer not running - not sending pause signal.");
232 /* FM Radio */
233 if (dbus_bus_name_has_owner(headphoned->session_bus, FMRADIO_SRVC, NULL)) {
234 debug_msg("Sending pause signal to FM Radio");
235 osso_rpc_run(headphoned->osso,
236 FMRADIO_SRVC,
237 FMRADIO_PATH,
238 FMRADIO_INTF,
239 "stop",
240 NULL,
241 DBUS_TYPE_INVALID);
244 /* MediaBox */
245 if (dbus_bus_name_has_owner(headphoned->session_bus, MEDIABOX_SRVC, NULL)) {
246 debug_msg("Sending pause signal to MediaBox");
247 osso_rpc_run(headphoned->osso,
248 MEDIABOX_SRVC,
249 MEDIABOX_PATH,
250 MEDIABOX_INTF,
251 "stop",
252 NULL,
253 DBUS_TYPE_INVALID);
257 /* A convenient GSourceFunc wrapper for broadcast_pause_signal */
258 gboolean
259 broadcast_pause_signal_later(gpointer data)
261 Headphoned *headphoned = (Headphoned*)data;
262 broadcast_pause_signal(headphoned);
263 return FALSE;
266 /* Handler for messages from "wired" headphones (via sysfs) */
267 gboolean
268 on_file_changed(GIOChannel* source, GIOCondition condition, gpointer data)
270 Headphoned* headphoned = (Headphoned*)data;
271 gchar* result;
273 debug_msg("File %s has changed.", STATE_FILE);
275 g_io_channel_seek_position(source, 0, G_SEEK_SET, NULL);
276 g_io_channel_read_line(source, &result, NULL, NULL, NULL);
277 g_strstrip(result);
279 if (headphoned->initial == TRUE) {
280 debug_msg("Ignoring initial file change.");
281 headphoned->initial = FALSE;
282 } else {
283 if (g_ascii_strcasecmp(result, STATE_DISCONNECTED_STR) == 0) {
284 debug_msg("Broadcasting pause signal (cause: state file)");
285 broadcast_pause_signal(headphoned);
287 if (headphoned->call_active == TRUE) {
289 * Remember this unplug event, so that we can
290 * pause players after the call is finished.
292 debug_msg("Setting flag: unplugged_on_call");
293 headphoned->unplugged_on_call = TRUE;
298 g_free(result);
299 return TRUE;
302 /* Handler for messages from Bluetooth + "wired w/ mic" headphones */
303 static DBusHandlerResult
304 on_msg_recieved(DBusConnection* connection G_GNUC_UNUSED, DBusMessage* message, void* data)
306 Headphoned* headphoned = (Headphoned*)data;
307 DBusMessageIter iter;
308 const char* result = NULL;
310 dbus_message_iter_init(message, &iter);
311 dbus_message_iter_get_basic(&iter, &result);
313 if (g_str_equal(dbus_message_get_path(message), HAL_MANAGER_PATH)) {
314 if (g_str_equal(result, HEADPHONE_UDI_NAME)) {
315 debug_msg("Broadcasting pause signal (cause: Hal via D-Bus)");
316 broadcast_pause_signal(headphoned);
317 return DBUS_HANDLER_RESULT_HANDLED;
318 } else {
319 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
321 } else if (g_str_equal(dbus_message_get_path(message), MCE_SIGNAL_PATH)) {
322 if (g_str_equal(result, MCE_CALL_ACTIVE) ||
323 g_str_equal(result, MCE_CALL_RINGING)) {
324 debug_msg("Active call detected.");
325 headphoned->call_active = TRUE;
326 return DBUS_HANDLER_RESULT_HANDLED;
327 } else if (g_str_equal(result, MCE_CALL_DISCONNECT)) {
328 debug_msg("Call disconnect detected.");
329 headphoned->call_active = FALSE;
330 if (headphoned->unplugged_on_call == TRUE) {
331 headphoned->unplugged_on_call = FALSE;
332 debug_msg("Broadcasting pause signal (cause: call disconnect)");
333 g_timeout_add(POST_CALL_PAUSE_DELAY_MS,
334 broadcast_pause_signal_later,
335 (gpointer)headphoned);
337 return DBUS_HANDLER_RESULT_HANDLED;
338 } else {
339 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
343 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
347 main(int argc, char* argv[])
349 Headphoned *headphoned = NULL;
350 GIOChannel* state = NULL;
352 signal(SIGINT, sig_handler);
353 signal(SIGQUIT, sig_handler);
354 signal(SIGTERM, sig_handler);
356 loop = g_main_loop_new(NULL, FALSE);
357 headphoned = headphoned_new();
359 state = g_io_channel_new_file(STATE_FILE, "r", NULL);
360 if (state != NULL) {
361 debug_msg("Adding I/O watch on %s", STATE_FILE);
362 g_io_add_watch(state, G_IO_PRI, on_file_changed, headphoned);
363 } else {
364 warning_msg("Cannot open state file: %s", STATE_FILE);
367 debug_msg("Registering D-Bus rule: %s", DBUS_RULE_HAL);
368 dbus_bus_add_match(headphoned->system_bus, DBUS_RULE_HAL, NULL);
370 debug_msg("Registering D-Bus rule: %s", DBUS_RULE_CALL);
371 dbus_bus_add_match(headphoned->system_bus, DBUS_RULE_CALL, NULL);
373 dbus_connection_add_filter(headphoned->system_bus, on_msg_recieved, headphoned, NULL);
375 debug_msg("Entering GLib main loop...");
376 g_main_loop_run(loop);
377 debug_msg("...main loop finished. Cleaning up.");
379 if (state != NULL) {
380 g_io_channel_unref(state);
383 pb_playback_destroy(headphoned->playback);
384 osso_deinitialize(headphoned->osso);
385 g_free(headphoned);
387 return EXIT_SUCCESS;