1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2009 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.
18 */
20 #include "config.h"
21 #include "ncmpc.h"
22 #include "mpdclient.h"
23 #include "charset.h"
24 #include "options.h"
25 #include "command.h"
26 #include "ncu.h"
27 #include "screen.h"
28 #include "screen_utils.h"
29 #include "strfsong.h"
30 #include "i18n.h"
31 #include "player_command.h"
33 #ifndef NCMPC_MINI
34 #include "conf.h"
35 #endif
37 #ifdef ENABLE_LYRICS_SCREEN
38 #include "lyrics.h"
39 #endif
41 #ifdef ENABLE_LIRC
42 #include "lirc.h"
43 #endif
45 #include <mpd/client.h>
47 #include <stdlib.h>
48 #include <unistd.h>
49 #include <signal.h>
50 #include <string.h>
52 #ifdef ENABLE_LOCALE
53 #include <locale.h>
54 #endif
56 /* time between mpd updates [s] */
57 static const guint update_interval = 500;
59 #define BUFSIZE 1024
61 static struct mpdclient *mpd = NULL;
62 static GMainLoop *main_loop;
63 static guint reconnect_source_id, update_source_id;
65 #ifndef NCMPC_MINI
66 static guint check_key_bindings_source_id;
67 #endif
69 static void
70 error_callback(G_GNUC_UNUSED struct mpdclient *c, G_GNUC_UNUSED gint error,
71 const gchar *_msg)
72 {
73 char *msg = utf8_to_locale(_msg);
74 screen_status_printf("%s", msg);
75 g_free(msg);
77 screen_bell();
78 doupdate();
79 }
81 #ifndef NCMPC_MINI
82 static void
83 update_xterm_title(void)
84 {
85 static char title[BUFSIZE];
86 char tmp[BUFSIZE];
87 struct mpd_status *status = NULL;
88 const struct mpd_song *song = NULL;
90 if (mpd) {
91 status = mpd->status;
92 song = mpd->song;
93 }
95 if (options.xterm_title_format && status && song &&
96 IS_PLAYING(mpd_status_get_state(status)))
97 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
98 else
99 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
101 if (strncmp(title, tmp, BUFSIZE)) {
102 g_strlcpy(title, tmp, BUFSIZE);
103 set_xterm_title("%s", title);
104 }
105 }
106 #endif
108 static void
109 exit_and_cleanup(void)
110 {
111 screen_exit();
112 #ifndef NCMPC_MINI
113 set_xterm_title("");
114 #endif
115 printf("\n");
117 if (mpd) {
118 mpdclient_disconnect(mpd);
119 mpdclient_free(mpd);
120 }
121 }
123 static void
124 catch_sigint(G_GNUC_UNUSED int sig)
125 {
126 g_main_loop_quit(main_loop);
127 }
130 static void
131 catch_sigcont(G_GNUC_UNUSED int sig)
132 {
133 screen_resize(mpd);
134 }
136 void
137 sigstop(void)
138 {
139 def_prog_mode(); /* save the tty modes */
140 endwin(); /* end curses mode temporarily */
141 kill(0, SIGSTOP); /* issue SIGSTOP */
142 }
144 static guint timer_sigwinch_id;
146 static gboolean
147 timer_sigwinch(G_GNUC_UNUSED gpointer data)
148 {
149 /* the following causes the screen to flicker. There might be
150 better solutions, but I believe it isn't all that
151 important. */
153 endwin();
154 refresh();
155 screen_resize(mpd);
157 return FALSE;
158 }
160 static void
161 catch_sigwinch(G_GNUC_UNUSED int sig)
162 {
163 if (timer_sigwinch_id != 0)
164 g_source_remove(timer_sigwinch_id);
166 timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
167 }
169 static gboolean
170 timer_mpd_update(gpointer data);
172 /**
173 * This timer is installed when the connection to the MPD server is
174 * broken. It tries to recover by reconnecting periodically.
175 */
176 static gboolean
177 timer_reconnect(G_GNUC_UNUSED gpointer data)
178 {
179 bool success;
181 assert(!mpdclient_is_connected(mpd));
183 screen_status_printf(_("Connecting to %s... [Press %s to abort]"),
184 options.host, get_key_names(CMD_QUIT,0) );
185 doupdate();
187 mpdclient_disconnect(mpd);
188 success = mpdclient_connect(mpd,
189 options.host, options.port,
190 1.5,
191 options.password);
192 if (!success) {
193 /* try again in 5 seconds */
194 g_timeout_add(5000, timer_reconnect, NULL);
195 return FALSE;
196 }
198 #ifndef NCMPC_MINI
199 /* quit if mpd is pre 0.11.0 - song id not supported by mpd */
200 if (mpd_connection_cmp_server_version(mpd->connection, 0, 12, 0) < 0) {
201 const unsigned *version =
202 mpd_connection_get_server_version(mpd->connection);
203 screen_status_printf(_("Error: MPD version %d.%d.%d is to old (%s needed)"),
204 version[0], version[1], version[2],
205 "0.12.0");
206 mpdclient_disconnect(mpd);
207 doupdate();
209 /* try again after 30 seconds */
210 g_timeout_add(30000, timer_reconnect, NULL);
211 return FALSE;
212 }
213 #endif
215 screen_status_printf(_("Connected to %s"),
216 options.host != NULL
217 ? options.host : "localhost");
218 doupdate();
220 /* update immediately */
221 g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE));
223 reconnect_source_id = 0;
224 return FALSE;
226 }
228 static gboolean
229 timer_mpd_update(gpointer data)
230 {
231 if (mpdclient_is_connected(mpd))
232 mpdclient_update(mpd);
233 else if (reconnect_source_id == 0)
234 reconnect_source_id = g_timeout_add(1000, timer_reconnect,
235 NULL);
237 #ifndef NCMPC_MINI
238 if (options.enable_xterm_title)
239 update_xterm_title();
240 #endif
242 screen_update(mpd);
244 return GPOINTER_TO_INT(data);
245 }
247 void begin_input_event(void)
248 {
249 }
251 void end_input_event(void)
252 {
253 screen_update(mpd);
254 }
256 int do_input_event(command_t cmd)
257 {
258 if (cmd == CMD_QUIT) {
259 g_main_loop_quit(main_loop);
260 return -1;
261 }
263 screen_cmd(mpd, cmd);
265 if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
266 /* make sure we don't update the volume yet */
267 g_source_remove(update_source_id);
268 update_source_id = g_timeout_add(update_interval,
269 timer_mpd_update,
270 GINT_TO_POINTER(TRUE));
271 }
273 return 0;
274 }
276 static gboolean
277 keyboard_event(G_GNUC_UNUSED GIOChannel *source,
278 G_GNUC_UNUSED GIOCondition condition,
279 G_GNUC_UNUSED gpointer data)
280 {
281 command_t cmd;
283 begin_input_event();
285 if ((cmd=get_keyboard_command()) != CMD_NONE)
286 if (do_input_event(cmd) != 0)
287 return FALSE;
289 end_input_event();
290 return TRUE;
291 }
293 #ifndef NCMPC_MINI
294 /**
295 * Check the configured key bindings for errors, and display a status
296 * message every 10 seconds.
297 */
298 static gboolean
299 timer_check_key_bindings(G_GNUC_UNUSED gpointer data)
300 {
301 char buf[256];
302 #ifdef ENABLE_KEYDEF_SCREEN
303 char comment[64];
304 #endif
305 gboolean key_error;
307 key_error = check_key_bindings(NULL, buf, sizeof(buf));
308 if (!key_error) {
309 /* no error: disable this timer for the rest of this
310 process */
311 check_key_bindings_source_id = 0;
312 return FALSE;
313 }
315 #ifdef ENABLE_KEYDEF_SCREEN
316 g_strchomp(buf);
317 g_strlcat(buf, " (", sizeof(buf));
318 /* to translators: a key was bound twice in the key editor,
319 and this is a hint for the user what to press to correct
320 that */
321 g_snprintf(comment, sizeof(comment), _("press %s for the key editor"),
322 get_key_names(CMD_SCREEN_KEYDEF, 0));
323 g_strlcat(buf, comment, sizeof(buf));
324 g_strlcat(buf, ")", sizeof(buf));
325 #endif
327 screen_status_printf("%s", buf);
329 doupdate();
330 return TRUE;
331 }
332 #endif
334 int
335 main(int argc, const char *argv[])
336 {
337 struct sigaction act;
338 #ifdef ENABLE_LOCALE
339 const char *charset = NULL;
340 #endif
341 GIOChannel *keyboard_channel;
342 #ifdef ENABLE_LIRC
343 int lirc_socket;
344 GIOChannel *lirc_channel = NULL;
345 #endif
347 #ifdef ENABLE_LOCALE
348 /* time and date formatting */
349 setlocale(LC_TIME,"");
350 /* care about sorting order etc */
351 setlocale(LC_COLLATE,"");
352 /* charset */
353 setlocale(LC_CTYPE,"");
354 /* initialize charset conversions */
355 charset = charset_init();
357 /* initialize i18n support */
358 #endif
360 #ifdef ENABLE_NLS
361 setlocale(LC_MESSAGES, "");
362 bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
363 #ifdef ENABLE_LOCALE
364 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
365 #endif
366 textdomain(GETTEXT_PACKAGE);
367 #endif
369 /* initialize options */
370 options_init();
372 /* parse command line options - 1 pass get configuration files */
373 options_parse(argc, argv);
375 #ifndef NCMPC_MINI
376 /* read configuration */
377 read_configuration();
379 /* check key bindings */
380 check_key_bindings(NULL, NULL, 0);
381 #endif
383 /* parse command line options - 2 pass */
384 options_parse(argc, argv);
386 /* setup signal behavior - SIGINT */
387 sigemptyset(&act.sa_mask);
388 act.sa_flags = 0;
389 act.sa_handler = catch_sigint;
390 if (sigaction(SIGINT, &act, NULL) < 0) {
391 perror("signal");
392 exit(EXIT_FAILURE);
393 }
395 /* setup signal behavior - SIGTERM */
397 act.sa_handler = catch_sigint;
398 if (sigaction(SIGTERM, &act, NULL) < 0) {
399 perror("sigaction()");
400 exit(EXIT_FAILURE);
401 }
403 /* setup signal behavior - SIGCONT */
405 act.sa_handler = catch_sigcont;
406 if (sigaction(SIGCONT, &act, NULL) < 0) {
407 perror("sigaction(SIGCONT)");
408 exit(EXIT_FAILURE);
409 }
411 /* setup signal behaviour - SIGHUP*/
413 act.sa_handler = catch_sigint;
414 if (sigaction(SIGHUP, &act, NULL) < 0) {
415 perror("sigaction(SIGHUP)");
416 exit(EXIT_FAILURE);
417 }
419 /* setup SIGWINCH */
421 act.sa_flags = SA_RESTART;
422 act.sa_handler = catch_sigwinch;
423 if (sigaction(SIGWINCH, &act, NULL) < 0) {
424 perror("sigaction(SIGWINCH)");
425 exit(EXIT_FAILURE);
426 }
428 /* ignore SIGPIPE */
430 act.sa_handler = SIG_IGN;
431 if (sigaction(SIGPIPE, &act, NULL) < 0) {
432 perror("sigaction(SIGPIPE)");
433 exit(EXIT_FAILURE);
434 }
436 ncu_init();
438 #ifdef ENABLE_LYRICS_SCREEN
439 lyrics_init();
440 #endif
442 /* create mpdclient instance */
443 mpd = mpdclient_new();
444 mpdclient_install_error_callback(mpd, error_callback);
446 /* initialize curses */
447 screen_init(mpd);
449 /* the main loop */
450 main_loop = g_main_loop_new(NULL, FALSE);
452 /* watch out for keyboard input */
453 keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
454 g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
456 #ifdef ENABLE_LIRC
457 /* watch out for lirc input */
458 lirc_socket = ncmpc_lirc_open();
459 if (lirc_socket >= 0) {
460 lirc_channel = g_io_channel_unix_new(lirc_socket);
461 g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL);
462 }
463 #endif
465 /* attempt to connect */
466 reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
468 update_source_id = g_timeout_add(update_interval,
469 timer_mpd_update,
470 GINT_TO_POINTER(TRUE));
471 #ifndef NCMPC_MINI
472 check_key_bindings_source_id = g_timeout_add(10000, timer_check_key_bindings, NULL);
473 #endif
475 screen_paint(mpd);
477 g_main_loop_run(main_loop);
479 /* cleanup */
481 cancel_seek_timer();
483 g_source_remove(update_source_id);
485 #ifndef NCMPC_MINI
486 if (check_key_bindings_source_id != 0)
487 g_source_remove(check_key_bindings_source_id);
488 #endif
490 g_main_loop_unref(main_loop);
491 g_io_channel_unref(keyboard_channel);
493 #ifdef ENABLE_LIRC
494 if (lirc_socket >= 0)
495 g_io_channel_unref(lirc_channel);
496 ncmpc_lirc_close();
497 #endif
499 exit_and_cleanup();
501 #ifdef ENABLE_LYRICS_SCREEN
502 lyrics_deinit();
503 #endif
505 ncu_deinit();
506 options_deinit();
508 return 0;
509 }