1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2017 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
4 *
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.
9 *
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.
14 *
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 "callbacks.h"
24 #include "charset.h"
25 #include "options.h"
26 #include "command.h"
27 #include "ncu.h"
28 #include "screen.h"
29 #include "screen_utils.h"
30 #include "screen_status.h"
31 #include "strfsong.h"
32 #include "i18n.h"
33 #include "player_command.h"
34 #include "keyboard.h"
35 #include "lirc.h"
37 #ifndef NCMPC_MINI
38 #include "conf.h"
39 #endif
41 #ifdef ENABLE_LYRICS_SCREEN
42 #include "lyrics.h"
43 #endif
45 #include <mpd/client.h>
47 #include <stdlib.h>
48 #include <unistd.h>
49 #include <fcntl.h>
50 #include <signal.h>
51 #include <string.h>
53 #ifdef ENABLE_LOCALE
54 #include <locale.h>
55 #endif
57 /* time between mpd updates [ms] */
58 static const guint update_interval = 500;
60 #define BUFSIZE 1024
62 static struct mpdclient *mpd = NULL;
63 static GMainLoop *main_loop;
64 static guint reconnect_source_id, update_source_id;
65 static int sigwinch_pipes[2];
67 #ifndef NCMPC_MINI
68 static guint check_key_bindings_source_id;
69 #endif
71 #ifndef NCMPC_MINI
72 static void
73 update_xterm_title(void)
74 {
75 const struct mpd_song *song = mpd->song;
77 char tmp[BUFSIZE];
78 if (options.xterm_title_format && mpd->playing && song)
79 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
80 else
81 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
83 static char title[BUFSIZE];
84 if (strncmp(title, tmp, BUFSIZE)) {
85 g_strlcpy(title, tmp, BUFSIZE);
86 set_xterm_title("%s", title);
87 }
88 }
89 #endif
91 #ifndef WIN32
92 static void
93 catch_sigint(gcc_unused int sig)
94 {
95 g_main_loop_quit(main_loop);
96 }
99 static void
100 catch_sigcont(gcc_unused int sig)
101 {
102 if (1 != write(sigwinch_pipes[1], "", 1))
103 exit(EXIT_FAILURE);
104 }
106 static gboolean
107 sigwinch_event(gcc_unused GIOChannel *source,
108 gcc_unused GIOCondition condition, gcc_unused gpointer data)
109 {
110 char ignoreme[64];
111 if (1 > read(sigwinch_pipes[0], ignoreme, 64))
112 exit(EXIT_FAILURE);
114 endwin();
115 refresh();
116 screen_resize(mpd);
118 return TRUE;
119 }
121 static void
122 catch_sigwinch(gcc_unused int sig)
123 {
124 if (1 != write(sigwinch_pipes[1], "", 1))
125 exit(EXIT_FAILURE);
126 }
127 #endif /* WIN32 */
129 static gboolean
130 timer_mpd_update(gpointer data);
132 static void
133 enable_update_timer(void)
134 {
135 if (update_source_id != 0)
136 return;
138 update_source_id = g_timeout_add(update_interval,
139 timer_mpd_update, NULL);
140 }
142 static void
143 disable_update_timer(void)
144 {
145 if (update_source_id == 0)
146 return;
148 g_source_remove(update_source_id);
149 update_source_id = 0;
150 }
152 static bool
153 should_enable_update_timer(void)
154 {
155 return mpd->playing
156 #ifndef NCMPC_MINI
157 || options.display_time
158 #endif
159 ;
160 }
162 static void
163 auto_update_timer(void)
164 {
165 if (should_enable_update_timer())
166 enable_update_timer();
167 else
168 disable_update_timer();
169 }
171 static void
172 check_reconnect(void);
174 static void
175 do_mpd_update(void)
176 {
177 if (mpdclient_is_connected(mpd) &&
178 (mpd->events != 0 || mpd->playing))
179 mpdclient_update(mpd);
181 #ifndef NCMPC_MINI
182 if (options.enable_xterm_title)
183 update_xterm_title();
184 #endif
186 screen_update(mpd);
187 mpd->events = 0;
189 mpdclient_put_connection(mpd);
190 check_reconnect();
191 }
193 static char *
194 settings_name(const struct mpd_settings *settings)
195 {
196 const char *host = mpd_settings_get_host(settings);
197 if (host == NULL)
198 host = _("unknown");
200 if (host[0] == '/')
201 return g_strdup(host);
203 unsigned port = mpd_settings_get_port(settings);
204 if (port == 0 || port == 6600)
205 return g_strdup(host);
207 return g_strdup_printf("%s:%u", host, port);
208 }
210 static char *
211 default_settings_name(void)
212 {
213 struct mpd_settings *settings =
214 mpd_settings_new(options.host, options.port, 0,
215 NULL, options.password);
216 if (settings == NULL)
217 return g_strdup(_("unknown"));
219 char *name = settings_name(settings);
220 mpd_settings_free(settings);
222 return name;
223 }
225 /**
226 * This timer is installed when the connection to the MPD server is
227 * broken. It tries to recover by reconnecting periodically.
228 */
229 static gboolean
230 timer_reconnect(gcc_unused gpointer data)
231 {
232 assert(mpdclient_is_dead(mpd));
234 reconnect_source_id = 0;
236 char *name = default_settings_name();
237 screen_status_printf(_("Connecting to %s... [Press %s to abort]"),
238 name, get_key_names(CMD_QUIT, false));
239 g_free(name);
240 doupdate();
242 mpdclient_disconnect(mpd);
243 mpdclient_connect(mpd, options.host, options.port,
244 options.timeout_ms,
245 options.password);
247 return FALSE;
248 }
250 static void
251 check_reconnect(void)
252 {
253 if (mpdclient_is_dead(mpd) && reconnect_source_id == 0)
254 /* reconnect when the connection is lost */
255 reconnect_source_id = g_timeout_add(1000, timer_reconnect,
256 NULL);
257 }
259 void
260 mpdclient_connected_callback(void)
261 {
262 assert(reconnect_source_id == 0);
264 #ifndef NCMPC_MINI
265 /* quit if mpd is pre 0.14 - song id not supported by mpd */
266 struct mpd_connection *connection = mpdclient_get_connection(mpd);
267 if (mpd_connection_cmp_server_version(connection, 0, 16, 0) < 0) {
268 const unsigned *version =
269 mpd_connection_get_server_version(connection);
270 screen_status_printf(_("Error: MPD version %d.%d.%d is too old (%s needed)"),
271 version[0], version[1], version[2],
272 "0.16.0");
273 mpdclient_disconnect(mpd);
274 doupdate();
276 /* try again after 30 seconds */
277 reconnect_source_id = g_timeout_add(30000,
278 timer_reconnect, NULL);
279 return;
280 }
281 #endif
283 screen_status_clear_message();
284 doupdate();
286 /* update immediately */
287 mpd->events = MPD_IDLE_ALL;
289 do_mpd_update();
291 auto_update_timer();
292 }
294 void
295 mpdclient_failed_callback(void)
296 {
297 assert(reconnect_source_id == 0);
299 /* try again in 5 seconds */
300 reconnect_source_id = g_timeout_add(5000,
301 timer_reconnect, NULL);
302 }
304 void
305 mpdclient_lost_callback(void)
306 {
307 assert(reconnect_source_id == 0);
309 screen_update(mpd);
311 reconnect_source_id = g_timeout_add(1000, timer_reconnect, NULL);
312 }
314 /**
315 * This function is called by the gidle.c library when MPD sends us an
316 * idle event (or when the connection dies).
317 */
318 void
319 mpdclient_idle_callback(gcc_unused enum mpd_idle events)
320 {
321 #ifndef NCMPC_MINI
322 if (options.enable_xterm_title)
323 update_xterm_title();
324 #endif
326 screen_update(mpd);
327 auto_update_timer();
328 }
330 static gboolean
331 timer_mpd_update(gcc_unused gpointer data)
332 {
333 do_mpd_update();
335 if (should_enable_update_timer())
336 return true;
337 else {
338 update_source_id = 0;
339 return false;
340 }
341 }
343 void begin_input_event(void)
344 {
345 }
347 void end_input_event(void)
348 {
349 screen_update(mpd);
350 mpd->events = 0;
352 mpdclient_put_connection(mpd);
353 check_reconnect();
354 auto_update_timer();
355 }
357 bool
358 do_input_event(command_t cmd)
359 {
360 if (cmd == CMD_QUIT) {
361 g_main_loop_quit(main_loop);
362 return false;
363 }
365 screen_cmd(mpd, cmd);
367 if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN)
368 /* make sure we don't update the volume yet */
369 disable_update_timer();
371 return true;
372 }
374 #ifndef NCMPC_MINI
375 /**
376 * Check the configured key bindings for errors, and display a status
377 * message every 10 seconds.
378 */
379 static gboolean
380 timer_check_key_bindings(gcc_unused gpointer data)
381 {
382 char buf[256];
384 if (check_key_bindings(NULL, buf, sizeof(buf))) {
385 /* no error: disable this timer for the rest of this
386 process */
387 check_key_bindings_source_id = 0;
388 return FALSE;
389 }
391 #ifdef ENABLE_KEYDEF_SCREEN
392 g_strchomp(buf);
393 g_strlcat(buf, " (", sizeof(buf));
394 /* to translators: a key was bound twice in the key editor,
395 and this is a hint for the user what to press to correct
396 that */
397 char comment[64];
398 g_snprintf(comment, sizeof(comment), _("press %s for the key editor"),
399 get_key_names(CMD_SCREEN_KEYDEF, false));
400 g_strlcat(buf, comment, sizeof(buf));
401 g_strlcat(buf, ")", sizeof(buf));
402 #endif
404 screen_status_printf("%s", buf);
406 doupdate();
407 return TRUE;
408 }
409 #endif
411 int
412 main(int argc, const char *argv[])
413 {
414 #ifdef ENABLE_LOCALE
415 #ifndef ENABLE_NLS
416 gcc_unused
417 #endif
418 const char *charset = NULL;
419 /* time and date formatting */
420 setlocale(LC_TIME,"");
421 /* care about sorting order etc */
422 setlocale(LC_COLLATE,"");
423 /* charset */
424 setlocale(LC_CTYPE,"");
425 /* initialize charset conversions */
426 charset = charset_init();
428 /* initialize i18n support */
429 #endif
431 #ifdef ENABLE_NLS
432 setlocale(LC_MESSAGES, "");
433 bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
434 #ifdef ENABLE_LOCALE
435 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
436 #endif
437 textdomain(GETTEXT_PACKAGE);
438 #endif
440 /* initialize options */
441 options_init();
443 /* parse command line options - 1 pass get configuration files */
444 options_parse(argc, argv);
446 #ifndef NCMPC_MINI
447 /* read configuration */
448 read_configuration();
450 /* check key bindings */
451 check_key_bindings(NULL, NULL, 0);
452 #endif
454 /* parse command line options - 2 pass */
455 options_parse(argc, argv);
457 #ifndef WIN32
458 /* setup signal behavior - SIGINT */
459 struct sigaction act;
460 sigemptyset(&act.sa_mask);
461 act.sa_flags = 0;
462 act.sa_handler = catch_sigint;
463 if (sigaction(SIGINT, &act, NULL) < 0) {
464 perror("signal");
465 exit(EXIT_FAILURE);
466 }
468 /* setup signal behavior - SIGTERM */
470 act.sa_handler = catch_sigint;
471 if (sigaction(SIGTERM, &act, NULL) < 0) {
472 perror("sigaction()");
473 exit(EXIT_FAILURE);
474 }
476 /* setup signal behavior - SIGCONT */
478 act.sa_handler = catch_sigcont;
479 if (sigaction(SIGCONT, &act, NULL) < 0) {
480 perror("sigaction(SIGCONT)");
481 exit(EXIT_FAILURE);
482 }
484 /* setup signal behaviour - SIGHUP*/
486 act.sa_handler = catch_sigint;
487 if (sigaction(SIGHUP, &act, NULL) < 0) {
488 perror("sigaction(SIGHUP)");
489 exit(EXIT_FAILURE);
490 }
492 /* setup SIGWINCH */
494 act.sa_flags = SA_RESTART;
495 act.sa_handler = catch_sigwinch;
496 if (sigaction(SIGWINCH, &act, NULL) < 0) {
497 perror("sigaction(SIGWINCH)");
498 exit(EXIT_FAILURE);
499 }
501 /* ignore SIGPIPE */
503 act.sa_handler = SIG_IGN;
504 if (sigaction(SIGPIPE, &act, NULL) < 0) {
505 perror("sigaction(SIGPIPE)");
506 exit(EXIT_FAILURE);
507 }
508 #endif
510 ncu_init();
512 #ifdef ENABLE_LYRICS_SCREEN
513 lyrics_init();
514 #endif
516 /* create mpdclient instance */
517 mpd = mpdclient_new();
519 /* initialize curses */
520 screen_init(mpd);
522 /* the main loop */
523 main_loop = g_main_loop_new(NULL, FALSE);
525 /* watch out for keyboard input */
526 keyboard_init();
528 /* watch out for lirc input */
529 ncmpc_lirc_init();
531 #ifndef WIN32
532 if (!pipe(sigwinch_pipes) &&
533 !fcntl(sigwinch_pipes[1], F_SETFL, O_NONBLOCK)) {
534 GIOChannel *sigwinch_channel = g_io_channel_unix_new(sigwinch_pipes[0]);
535 g_io_add_watch(sigwinch_channel, G_IO_IN, sigwinch_event, NULL);
536 g_io_channel_unref(sigwinch_channel);
537 }
538 else {
539 perror("sigwinch pipe creation failed");
540 exit(EXIT_FAILURE);
541 }
542 #endif
544 /* attempt to connect */
545 reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
547 auto_update_timer();
549 #ifndef NCMPC_MINI
550 check_key_bindings_source_id = g_timeout_add(10000, timer_check_key_bindings, NULL);
551 #endif
553 screen_paint(mpd);
555 g_main_loop_run(main_loop);
556 g_main_loop_unref(main_loop);
558 /* cleanup */
560 cancel_seek_timer();
562 disable_update_timer();
564 if (reconnect_source_id != 0)
565 g_source_remove(reconnect_source_id);
567 #ifndef NCMPC_MINI
568 if (check_key_bindings_source_id != 0)
569 g_source_remove(check_key_bindings_source_id);
570 #endif
572 close(sigwinch_pipes[0]);
573 close(sigwinch_pipes[1]);
575 ncmpc_lirc_deinit();
577 screen_exit();
578 #ifndef NCMPC_MINI
579 set_xterm_title("");
580 #endif
581 printf("\n");
583 mpdclient_free(mpd);
585 #ifdef ENABLE_LYRICS_SCREEN
586 lyrics_deinit();
587 #endif
589 ncu_deinit();
590 options_deinit();
592 return 0;
593 }