1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2010 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "mpdclient.h"
29 #include "screen_utils.h"
30 #include "screen_status.h"
33 #include "player_command.h"
39 #ifdef ENABLE_LYRICS_SCREEN
47 #include <mpd/client.h>
59 /* time between mpd updates [ms] */
60 static const guint update_interval
= 500;
64 static struct mpdclient
*mpd
= NULL
;
65 static GMainLoop
*main_loop
;
66 static guint reconnect_source_id
, update_source_id
;
67 static int sigwinch_pipes
[2];
70 static guint check_key_bindings_source_id
;
75 update_xterm_title(void)
77 struct mpd_status
*status
= NULL
;
78 const struct mpd_song
*song
= NULL
;
85 if (options
.xterm_title_format
&& status
&& song
&&
86 mpd_status_get_state(status
) == MPD_STATE_PLAY
)
87 strfsong(tmp
, BUFSIZE
, options
.xterm_title_format
, song
);
89 g_strlcpy(tmp
, PACKAGE
" version " VERSION
, BUFSIZE
);
91 static char title
[BUFSIZE
];
92 if (strncmp(title
, tmp
, BUFSIZE
)) {
93 g_strlcpy(title
, tmp
, BUFSIZE
);
94 set_xterm_title("%s", title
);
100 exit_and_cleanup(void)
109 mpdclient_disconnect(mpd
);
116 catch_sigint(gcc_unused
int sig
)
118 g_main_loop_quit(main_loop
);
123 catch_sigcont(gcc_unused
int sig
)
125 char irrelevant
= 'a';
126 if (1 != write(sigwinch_pipes
[1], &irrelevant
, 1))
133 def_prog_mode(); /* save the tty modes */
134 endwin(); /* end curses mode temporarily */
135 kill(0, SIGSTOP
); /* issue SIGSTOP */
139 sigwinch_event(gcc_unused GIOChannel
*source
,
140 gcc_unused GIOCondition condition
, gcc_unused gpointer data
)
143 if (1 > read(sigwinch_pipes
[0], ignoreme
, 64))
154 catch_sigwinch(gcc_unused
int sig
)
156 char irrelevant
= 'a';
157 if (1 != write(sigwinch_pipes
[1], &irrelevant
, 1))
163 idle_callback(enum mpd_error error
,
164 gcc_unused
enum mpd_server_error server_error
,
165 const char *message
, enum mpd_idle events
,
166 gcc_unused
void *ctx
);
169 timer_mpd_update(gpointer data
);
172 enable_update_timer(void)
174 if (update_source_id
!= 0)
177 update_source_id
= g_timeout_add(update_interval
,
178 timer_mpd_update
, NULL
);
182 disable_update_timer(void)
184 if (update_source_id
== 0)
187 g_source_remove(update_source_id
);
188 update_source_id
= 0;
192 should_enable_update_timer(void)
194 return (mpdclient_is_connected(mpd
) &&
195 (mpd
->source
== NULL
|| /* "idle" not supported */
196 (mpd
->status
!= NULL
&&
197 mpd_status_get_state(mpd
->status
) == MPD_STATE_PLAY
)))
199 || options
.display_time
205 auto_update_timer(void)
207 if (should_enable_update_timer())
208 enable_update_timer();
210 disable_update_timer();
214 check_reconnect(void);
219 if (mpdclient_is_connected(mpd
) &&
220 (mpd
->source
== NULL
|| mpd
->events
!= 0 ||
221 (mpd
->status
!= NULL
&&
222 mpd_status_get_state(mpd
->status
) == MPD_STATE_PLAY
)))
223 mpdclient_update(mpd
);
226 if (options
.enable_xterm_title
)
227 update_xterm_title();
233 mpdclient_put_connection(mpd
);
237 #if LIBMPDCLIENT_CHECK_VERSION(2,4,0)
240 settings_name(const struct mpd_settings
*settings
)
242 const char *host
= mpd_settings_get_host(settings
);
247 return g_strdup(host
);
249 unsigned port
= mpd_settings_get_port(settings
);
250 if (port
== 0 || port
== 6600)
251 return g_strdup(host
);
253 return g_strdup_printf("%s:%u", host
, port
);
259 default_settings_name(void)
261 #if LIBMPDCLIENT_CHECK_VERSION(2,4,0)
262 struct mpd_settings
*settings
=
263 mpd_settings_new(options
.host
, options
.port
, 0,
264 NULL
, options
.password
);
265 if (settings
== NULL
)
266 return g_strdup(_("unknown"));
268 char *name
= settings_name(settings
);
269 mpd_settings_free(settings
);
274 * localhost is actually not correct, we only know that
275 * mpd_connection_new() has connected to the "default host".
277 const char *name
= options
.host
?: "localhost";
278 return g_strdup(name
);
283 * This timer is installed when the connection to the MPD server is
284 * broken. It tries to recover by reconnecting periodically.
287 timer_reconnect(gcc_unused gpointer data
)
289 assert(!mpdclient_is_connected(mpd
));
291 reconnect_source_id
= 0;
293 char *name
= default_settings_name();
294 screen_status_printf(_("Connecting to %s... [Press %s to abort]"),
295 name
, get_key_names(CMD_QUIT
, false));
299 mpdclient_disconnect(mpd
);
300 if (!mpdclient_connect(mpd
, options
.host
, options
.port
,
303 /* try again in 5 seconds */
304 reconnect_source_id
= g_timeout_add(5000,
305 timer_reconnect
, NULL
);
309 struct mpd_connection
*connection
= mpdclient_get_connection(mpd
);
312 /* quit if mpd is pre 0.14 - song id not supported by mpd */
313 if (mpd_connection_cmp_server_version(connection
, 0, 16, 0) < 0) {
314 const unsigned *version
=
315 mpd_connection_get_server_version(connection
);
316 screen_status_printf(_("Error: MPD version %d.%d.%d is too old (%s needed)"),
317 version
[0], version
[1], version
[2],
319 mpdclient_disconnect(mpd
);
322 /* try again after 30 seconds */
323 reconnect_source_id
= g_timeout_add(30000,
324 timer_reconnect
, NULL
);
329 mpd
->source
= mpd_glib_new(connection
,
332 screen_status_clear_message();
335 /* update immediately */
336 mpd
->events
= MPD_IDLE_ALL
;
346 check_reconnect(void)
348 if (!mpdclient_is_connected(mpd
) && reconnect_source_id
== 0)
349 /* reconnect when the connection is lost */
350 reconnect_source_id
= g_timeout_add(1000, timer_reconnect
,
355 * This function is called by the gidle.c library when MPD sends us an
356 * idle event (or when the connection dies).
359 idle_callback(enum mpd_error error
, enum mpd_server_error server_error
,
360 const char *message
, enum mpd_idle events
,
363 struct mpdclient
*c
= ctx
;
367 assert(mpdclient_is_connected(c
));
369 if (error
!= MPD_ERROR_SUCCESS
) {
370 if (error
== MPD_ERROR_SERVER
&&
371 server_error
== MPD_SERVER_ERROR_UNKNOWN_CMD
) {
372 /* the "idle" command is not supported - fall
373 back to timer based polling */
374 mpd_glib_free(c
->source
);
381 if (error
== MPD_ERROR_SERVER
)
382 message
= allocated
= utf8_to_locale(message
);
385 screen_status_message(message
);
390 mpdclient_disconnect(c
);
392 reconnect_source_id
= g_timeout_add(1000, timer_reconnect
,
401 if (options
.enable_xterm_title
)
402 update_xterm_title();
408 mpdclient_put_connection(c
);
414 timer_mpd_update(gcc_unused gpointer data
)
418 if (should_enable_update_timer())
421 update_source_id
= 0;
426 void begin_input_event(void)
430 void end_input_event(void)
435 mpdclient_put_connection(mpd
);
440 int do_input_event(command_t cmd
)
442 if (cmd
== CMD_QUIT
) {
443 g_main_loop_quit(main_loop
);
447 screen_cmd(mpd
, cmd
);
449 if (cmd
== CMD_VOLUME_UP
|| cmd
== CMD_VOLUME_DOWN
)
450 /* make sure we don't update the volume yet */
451 disable_update_timer();
457 keyboard_event(gcc_unused GIOChannel
*source
,
458 gcc_unused GIOCondition condition
,
459 gcc_unused gpointer data
)
463 command_t cmd
= get_keyboard_command();
465 if (do_input_event(cmd
) != 0)
474 * Check the configured key bindings for errors, and display a status
475 * message every 10 seconds.
478 timer_check_key_bindings(gcc_unused gpointer data
)
482 if (check_key_bindings(NULL
, buf
, sizeof(buf
))) {
483 /* no error: disable this timer for the rest of this
485 check_key_bindings_source_id
= 0;
489 #ifdef ENABLE_KEYDEF_SCREEN
491 g_strlcat(buf
, " (", sizeof(buf
));
492 /* to translators: a key was bound twice in the key editor,
493 and this is a hint for the user what to press to correct
496 g_snprintf(comment
, sizeof(comment
), _("press %s for the key editor"),
497 get_key_names(CMD_SCREEN_KEYDEF
, false));
498 g_strlcat(buf
, comment
, sizeof(buf
));
499 g_strlcat(buf
, ")", sizeof(buf
));
502 screen_status_printf("%s", buf
);
510 main(int argc
, const char *argv
[])
516 const char *charset
= NULL
;
517 /* time and date formatting */
518 setlocale(LC_TIME
,"");
519 /* care about sorting order etc */
520 setlocale(LC_COLLATE
,"");
522 setlocale(LC_CTYPE
,"");
523 /* initialize charset conversions */
524 charset
= charset_init();
526 /* initialize i18n support */
530 setlocale(LC_MESSAGES
, "");
531 bindtextdomain(GETTEXT_PACKAGE
, LOCALE_DIR
);
533 bind_textdomain_codeset(GETTEXT_PACKAGE
, charset
);
535 textdomain(GETTEXT_PACKAGE
);
538 /* initialize options */
541 /* parse command line options - 1 pass get configuration files */
542 options_parse(argc
, argv
);
545 /* read configuration */
546 read_configuration();
548 /* check key bindings */
549 check_key_bindings(NULL
, NULL
, 0);
552 /* parse command line options - 2 pass */
553 options_parse(argc
, argv
);
556 /* setup signal behavior - SIGINT */
557 struct sigaction act
;
558 sigemptyset(&act
.sa_mask
);
560 act
.sa_handler
= catch_sigint
;
561 if (sigaction(SIGINT
, &act
, NULL
) < 0) {
566 /* setup signal behavior - SIGTERM */
568 act
.sa_handler
= catch_sigint
;
569 if (sigaction(SIGTERM
, &act
, NULL
) < 0) {
570 perror("sigaction()");
574 /* setup signal behavior - SIGCONT */
576 act
.sa_handler
= catch_sigcont
;
577 if (sigaction(SIGCONT
, &act
, NULL
) < 0) {
578 perror("sigaction(SIGCONT)");
582 /* setup signal behaviour - SIGHUP*/
584 act
.sa_handler
= catch_sigint
;
585 if (sigaction(SIGHUP
, &act
, NULL
) < 0) {
586 perror("sigaction(SIGHUP)");
592 act
.sa_flags
= SA_RESTART
;
593 act
.sa_handler
= catch_sigwinch
;
594 if (sigaction(SIGWINCH
, &act
, NULL
) < 0) {
595 perror("sigaction(SIGWINCH)");
601 act
.sa_handler
= SIG_IGN
;
602 if (sigaction(SIGPIPE
, &act
, NULL
) < 0) {
603 perror("sigaction(SIGPIPE)");
610 #ifdef ENABLE_LYRICS_SCREEN
614 /* create mpdclient instance */
615 mpd
= mpdclient_new();
617 /* initialize curses */
621 main_loop
= g_main_loop_new(NULL
, FALSE
);
623 /* watch out for keyboard input */
624 GIOChannel
*keyboard_channel
= g_io_channel_unix_new(STDIN_FILENO
);
625 g_io_add_watch(keyboard_channel
, G_IO_IN
, keyboard_event
, NULL
);
628 /* watch out for lirc input */
629 int lirc_socket
= ncmpc_lirc_open();
630 GIOChannel
*lirc_channel
= NULL
;
631 if (lirc_socket
>= 0) {
632 lirc_channel
= g_io_channel_unix_new(lirc_socket
);
633 g_io_add_watch(lirc_channel
, G_IO_IN
, lirc_event
, NULL
);
638 GIOChannel
*sigwinch_channel
= NULL
;
639 if (!pipe(sigwinch_pipes
) &&
640 !fcntl(sigwinch_pipes
[1], F_SETFL
, O_NONBLOCK
)) {
641 sigwinch_channel
= g_io_channel_unix_new(sigwinch_pipes
[0]);
642 g_io_add_watch(sigwinch_channel
, G_IO_IN
, sigwinch_event
, NULL
);
645 perror("sigwinch pipe creation failed");
650 /* attempt to connect */
651 reconnect_source_id
= g_timeout_add(1, timer_reconnect
, NULL
);
656 check_key_bindings_source_id
= g_timeout_add(10000, timer_check_key_bindings
, NULL
);
661 g_main_loop_run(main_loop
);
667 disable_update_timer();
669 if (reconnect_source_id
!= 0)
670 g_source_remove(reconnect_source_id
);
673 if (check_key_bindings_source_id
!= 0)
674 g_source_remove(check_key_bindings_source_id
);
677 g_main_loop_unref(main_loop
);
678 g_io_channel_unref(keyboard_channel
);
679 g_io_channel_unref(sigwinch_channel
);
680 close(sigwinch_pipes
[0]);
681 close(sigwinch_pipes
[1]);
684 if (lirc_socket
>= 0)
685 g_io_channel_unref(lirc_channel
);
691 #ifdef ENABLE_LYRICS_SCREEN