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 #ifndef NCMPC_MINI
70 static void
71 update_xterm_title(void)
72 {
73 static char title[BUFSIZE];
74 char tmp[BUFSIZE];
75 struct mpd_status *status = NULL;
76 const struct mpd_song *song = NULL;
78 if (mpd) {
79 status = mpd->status;
80 song = mpd->song;
81 }
83 if (options.xterm_title_format && status && song &&
84 mpd_status_get_state(status) == MPD_STATE_PLAY)
85 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
86 else
87 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
89 if (strncmp(title, tmp, BUFSIZE)) {
90 g_strlcpy(title, tmp, BUFSIZE);
91 set_xterm_title("%s", title);
92 }
93 }
94 #endif
96 static void
97 exit_and_cleanup(void)
98 {
99 screen_exit();
100 #ifndef NCMPC_MINI
101 set_xterm_title("");
102 #endif
103 printf("\n");
105 if (mpd) {
106 mpdclient_disconnect(mpd);
107 mpdclient_free(mpd);
108 }
109 }
111 static void
112 catch_sigint(G_GNUC_UNUSED int sig)
113 {
114 g_main_loop_quit(main_loop);
115 }
118 static void
119 catch_sigcont(G_GNUC_UNUSED int sig)
120 {
121 screen_resize(mpd);
122 }
124 void
125 sigstop(void)
126 {
127 def_prog_mode(); /* save the tty modes */
128 endwin(); /* end curses mode temporarily */
129 kill(0, SIGSTOP); /* issue SIGSTOP */
130 }
132 static guint timer_sigwinch_id;
134 static gboolean
135 timer_sigwinch(G_GNUC_UNUSED gpointer data)
136 {
137 /* the following causes the screen to flicker. There might be
138 better solutions, but I believe it isn't all that
139 important. */
141 endwin();
142 refresh();
143 screen_resize(mpd);
145 return FALSE;
146 }
148 static void
149 catch_sigwinch(G_GNUC_UNUSED int sig)
150 {
151 if (timer_sigwinch_id != 0)
152 g_source_remove(timer_sigwinch_id);
154 timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
155 }
157 static gboolean
158 timer_mpd_update(gpointer data);
160 /**
161 * This timer is installed when the connection to the MPD server is
162 * broken. It tries to recover by reconnecting periodically.
163 */
164 static gboolean
165 timer_reconnect(G_GNUC_UNUSED gpointer data)
166 {
167 bool success;
169 assert(!mpdclient_is_connected(mpd));
171 screen_status_printf(_("Connecting to %s... [Press %s to abort]"),
172 options.host, get_key_names(CMD_QUIT,0) );
173 doupdate();
175 mpdclient_disconnect(mpd);
176 success = mpdclient_connect(mpd,
177 options.host, options.port,
178 1.5,
179 options.password);
180 if (!success) {
181 /* try again in 5 seconds */
182 g_timeout_add(5000, timer_reconnect, NULL);
183 return FALSE;
184 }
186 #ifndef NCMPC_MINI
187 /* quit if mpd is pre 0.11.0 - song id not supported by mpd */
188 if (mpd_connection_cmp_server_version(mpd->connection, 0, 12, 0) < 0) {
189 const unsigned *version =
190 mpd_connection_get_server_version(mpd->connection);
191 screen_status_printf(_("Error: MPD version %d.%d.%d is to old (%s needed)"),
192 version[0], version[1], version[2],
193 "0.12.0");
194 mpdclient_disconnect(mpd);
195 doupdate();
197 /* try again after 30 seconds */
198 g_timeout_add(30000, timer_reconnect, NULL);
199 return FALSE;
200 }
201 #endif
203 screen_status_printf(_("Connected to %s"),
204 options.host != NULL
205 ? options.host : "localhost");
206 doupdate();
208 /* update immediately */
209 g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE));
211 reconnect_source_id = 0;
212 return FALSE;
214 }
216 static gboolean
217 timer_mpd_update(gpointer data)
218 {
219 if (mpdclient_is_connected(mpd))
220 mpdclient_update(mpd);
221 else if (reconnect_source_id == 0)
222 reconnect_source_id = g_timeout_add(1000, timer_reconnect,
223 NULL);
225 #ifndef NCMPC_MINI
226 if (options.enable_xterm_title)
227 update_xterm_title();
228 #endif
230 screen_update(mpd);
232 mpd->events = 0;
234 return GPOINTER_TO_INT(data);
235 }
237 void begin_input_event(void)
238 {
239 }
241 void end_input_event(void)
242 {
243 screen_update(mpd);
244 }
246 int do_input_event(command_t cmd)
247 {
248 if (cmd == CMD_QUIT) {
249 g_main_loop_quit(main_loop);
250 return -1;
251 }
253 screen_cmd(mpd, cmd);
255 if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
256 /* make sure we don't update the volume yet */
257 g_source_remove(update_source_id);
258 update_source_id = g_timeout_add(update_interval,
259 timer_mpd_update,
260 GINT_TO_POINTER(TRUE));
261 }
263 return 0;
264 }
266 static gboolean
267 keyboard_event(G_GNUC_UNUSED GIOChannel *source,
268 G_GNUC_UNUSED GIOCondition condition,
269 G_GNUC_UNUSED gpointer data)
270 {
271 command_t cmd;
273 begin_input_event();
275 if ((cmd=get_keyboard_command()) != CMD_NONE)
276 if (do_input_event(cmd) != 0)
277 return FALSE;
279 end_input_event();
280 return TRUE;
281 }
283 #ifndef NCMPC_MINI
284 /**
285 * Check the configured key bindings for errors, and display a status
286 * message every 10 seconds.
287 */
288 static gboolean
289 timer_check_key_bindings(G_GNUC_UNUSED gpointer data)
290 {
291 char buf[256];
292 #ifdef ENABLE_KEYDEF_SCREEN
293 char comment[64];
294 #endif
295 gboolean key_error;
297 key_error = check_key_bindings(NULL, buf, sizeof(buf));
298 if (!key_error) {
299 /* no error: disable this timer for the rest of this
300 process */
301 check_key_bindings_source_id = 0;
302 return FALSE;
303 }
305 #ifdef ENABLE_KEYDEF_SCREEN
306 g_strchomp(buf);
307 g_strlcat(buf, " (", sizeof(buf));
308 /* to translators: a key was bound twice in the key editor,
309 and this is a hint for the user what to press to correct
310 that */
311 g_snprintf(comment, sizeof(comment), _("press %s for the key editor"),
312 get_key_names(CMD_SCREEN_KEYDEF, 0));
313 g_strlcat(buf, comment, sizeof(buf));
314 g_strlcat(buf, ")", sizeof(buf));
315 #endif
317 screen_status_printf("%s", buf);
319 doupdate();
320 return TRUE;
321 }
322 #endif
324 int
325 main(int argc, const char *argv[])
326 {
327 struct sigaction act;
328 #ifdef ENABLE_LOCALE
329 const char *charset = NULL;
330 #endif
331 GIOChannel *keyboard_channel;
332 #ifdef ENABLE_LIRC
333 int lirc_socket;
334 GIOChannel *lirc_channel = NULL;
335 #endif
337 #ifdef ENABLE_LOCALE
338 /* time and date formatting */
339 setlocale(LC_TIME,"");
340 /* care about sorting order etc */
341 setlocale(LC_COLLATE,"");
342 /* charset */
343 setlocale(LC_CTYPE,"");
344 /* initialize charset conversions */
345 charset = charset_init();
347 /* initialize i18n support */
348 #endif
350 #ifdef ENABLE_NLS
351 setlocale(LC_MESSAGES, "");
352 bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
353 #ifdef ENABLE_LOCALE
354 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
355 #endif
356 textdomain(GETTEXT_PACKAGE);
357 #endif
359 /* initialize options */
360 options_init();
362 /* parse command line options - 1 pass get configuration files */
363 options_parse(argc, argv);
365 #ifndef NCMPC_MINI
366 /* read configuration */
367 read_configuration();
369 /* check key bindings */
370 check_key_bindings(NULL, NULL, 0);
371 #endif
373 /* parse command line options - 2 pass */
374 options_parse(argc, argv);
376 /* setup signal behavior - SIGINT */
377 sigemptyset(&act.sa_mask);
378 act.sa_flags = 0;
379 act.sa_handler = catch_sigint;
380 if (sigaction(SIGINT, &act, NULL) < 0) {
381 perror("signal");
382 exit(EXIT_FAILURE);
383 }
385 /* setup signal behavior - SIGTERM */
387 act.sa_handler = catch_sigint;
388 if (sigaction(SIGTERM, &act, NULL) < 0) {
389 perror("sigaction()");
390 exit(EXIT_FAILURE);
391 }
393 /* setup signal behavior - SIGCONT */
395 act.sa_handler = catch_sigcont;
396 if (sigaction(SIGCONT, &act, NULL) < 0) {
397 perror("sigaction(SIGCONT)");
398 exit(EXIT_FAILURE);
399 }
401 /* setup signal behaviour - SIGHUP*/
403 act.sa_handler = catch_sigint;
404 if (sigaction(SIGHUP, &act, NULL) < 0) {
405 perror("sigaction(SIGHUP)");
406 exit(EXIT_FAILURE);
407 }
409 /* setup SIGWINCH */
411 act.sa_flags = SA_RESTART;
412 act.sa_handler = catch_sigwinch;
413 if (sigaction(SIGWINCH, &act, NULL) < 0) {
414 perror("sigaction(SIGWINCH)");
415 exit(EXIT_FAILURE);
416 }
418 /* ignore SIGPIPE */
420 act.sa_handler = SIG_IGN;
421 if (sigaction(SIGPIPE, &act, NULL) < 0) {
422 perror("sigaction(SIGPIPE)");
423 exit(EXIT_FAILURE);
424 }
426 ncu_init();
428 #ifdef ENABLE_LYRICS_SCREEN
429 lyrics_init();
430 #endif
432 /* create mpdclient instance */
433 mpd = mpdclient_new();
435 /* initialize curses */
436 screen_init(mpd);
438 /* the main loop */
439 main_loop = g_main_loop_new(NULL, FALSE);
441 /* watch out for keyboard input */
442 keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
443 g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
445 #ifdef ENABLE_LIRC
446 /* watch out for lirc input */
447 lirc_socket = ncmpc_lirc_open();
448 if (lirc_socket >= 0) {
449 lirc_channel = g_io_channel_unix_new(lirc_socket);
450 g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL);
451 }
452 #endif
454 /* attempt to connect */
455 reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
457 update_source_id = g_timeout_add(update_interval,
458 timer_mpd_update,
459 GINT_TO_POINTER(TRUE));
460 #ifndef NCMPC_MINI
461 check_key_bindings_source_id = g_timeout_add(10000, timer_check_key_bindings, NULL);
462 #endif
464 screen_paint(mpd);
466 g_main_loop_run(main_loop);
468 /* cleanup */
470 cancel_seek_timer();
472 g_source_remove(update_source_id);
474 #ifndef NCMPC_MINI
475 if (check_key_bindings_source_id != 0)
476 g_source_remove(check_key_bindings_source_id);
477 #endif
479 g_main_loop_unref(main_loop);
480 g_io_channel_unref(keyboard_channel);
482 #ifdef ENABLE_LIRC
483 if (lirc_socket >= 0)
484 g_io_channel_unref(lirc_channel);
485 ncmpc_lirc_close();
486 #endif
488 exit_and_cleanup();
490 #ifdef ENABLE_LYRICS_SCREEN
491 lyrics_deinit();
492 #endif
494 ncu_deinit();
495 options_deinit();
497 return 0;
498 }