Code

main: changed "connected" check to assertion
[ncmpc.git] / src / main.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2009 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.
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 gboolean connected = FALSE;
63 static GMainLoop *main_loop;
64 static guint reconnect_source_id, update_source_id;
66 #ifndef NCMPC_MINI
67 static guint check_key_bindings_source_id;
68 #endif
70 static const gchar *
71 error_msg(const gchar *msg)
72 {
73         gchar *p;
75         if ((p = strchr(msg, '}')) == NULL)
76                 return msg;
78         do {
79                 p++;
80         } while (*p == '}' || * p== ' ');
82         return p;
83 }
85 static void
86 error_callback(G_GNUC_UNUSED struct mpdclient *c, gint error, const gchar *_msg)
87 {
88         char *msg = utf8_to_locale(_msg);
90         error = error & 0xFF;
91         switch (error) {
92         case MPD_ERROR_SERVER:
93                 screen_status_printf("%s", error_msg(msg));
94                 screen_bell();
95                 break;
96         default:
97                 screen_status_printf("%s", msg);
98                 screen_bell();
99                 doupdate();
100                 connected = FALSE;
101         }
103         g_free(msg);
106 #ifndef NCMPC_MINI
107 static void
108 update_xterm_title(void)
110         static char title[BUFSIZE];
111         char tmp[BUFSIZE];
112         struct mpd_status *status = NULL;
113         struct mpd_song *song = NULL;
115         if (mpd) {
116                 status = mpd->status;
117                 song = mpd->song;
118         }
120         if (options.xterm_title_format && status && song &&
121             IS_PLAYING(mpd_status_get_state(status)))
122                 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
123         else
124                 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
126         if (strncmp(title, tmp, BUFSIZE)) {
127                 g_strlcpy(title, tmp, BUFSIZE);
128                 set_xterm_title("%s", title);
129         }
131 #endif
133 static void
134 exit_and_cleanup(void)
136         screen_exit();
137 #ifndef NCMPC_MINI
138         set_xterm_title("");
139 #endif
140         printf("\n");
142         if (mpd) {
143                 mpdclient_disconnect(mpd);
144                 mpdclient_free(mpd);
145         }
148 static void
149 catch_sigint(G_GNUC_UNUSED int sig)
151         g_main_loop_quit(main_loop);
155 static void
156 catch_sigcont(G_GNUC_UNUSED int sig)
158         screen_resize(mpd);
161 void
162 sigstop(void)
164   def_prog_mode();  /* save the tty modes */
165   endwin();         /* end curses mode temporarily */
166   kill(0, SIGSTOP); /* issue SIGSTOP */
169 static guint timer_sigwinch_id;
171 static gboolean
172 timer_sigwinch(G_GNUC_UNUSED gpointer data)
174         /* the following causes the screen to flicker.  There might be
175            better solutions, but I believe it isn't all that
176            important. */
178         endwin();
179         refresh();
180         screen_resize(mpd);
182         return FALSE;
185 static void
186 catch_sigwinch(G_GNUC_UNUSED int sig)
188         if (timer_sigwinch_id != 0)
189                 g_source_remove(timer_sigwinch_id);
191         timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
194 static gboolean
195 timer_mpd_update(gpointer data);
197 /**
198  * This timer is installed when the connection to the MPD server is
199  * broken.  It tries to recover by reconnecting periodically.
200  */
201 static gboolean
202 timer_reconnect(G_GNUC_UNUSED gpointer data)
204         int ret;
206         assert(!connected);
208         screen_status_printf(_("Connecting to %s...  [Press %s to abort]"),
209                              options.host, get_key_names(CMD_QUIT,0) );
210         doupdate();
212         mpdclient_disconnect(mpd);
213         ret = mpdclient_connect(mpd,
214                                 options.host, options.port,
215                                 1.5,
216                                 options.password);
217         if (ret != 0) {
218                 /* try again in 5 seconds */
219                 g_timeout_add(5000, timer_reconnect, NULL);
220                 return FALSE;
221         }
223 #ifndef NCMPC_MINI
224         /* quit if mpd is pre 0.11.0 - song id not supported by mpd */
225         if (mpd_connection_cmp_server_version(mpd->connection, 0, 11, 0) < 0) {
226                 const unsigned *version =
227                         mpd_connection_get_server_version(mpd->connection);
228                 screen_status_printf(_("Error: MPD version %d.%d.%d is to old (%s needed)"),
229                                      version[0], version[1], version[2],
230                                      "0.11.0");
231                 mpdclient_disconnect(mpd);
232                 doupdate();
234                 /* try again after 30 seconds */
235                 g_timeout_add(30000, timer_reconnect, NULL);
236                 return FALSE;
237         }
238 #endif
240         screen_status_printf(_("Connected to %s"),
241                              options.host != NULL
242                              ? options.host : "localhost");
243         doupdate();
245         connected = TRUE;
247         /* update immediately */
248         g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE));
250         reconnect_source_id = 0;
251         return FALSE;
255 static gboolean
256 timer_mpd_update(gpointer data)
258         if (connected)
259                 mpdclient_update(mpd);
260         else if (reconnect_source_id == 0)
261                 reconnect_source_id = g_timeout_add(1000, timer_reconnect,
262                                                     NULL);
264 #ifndef NCMPC_MINI
265         if (options.enable_xterm_title)
266                 update_xterm_title();
267 #endif
269         screen_update(mpd);
271         return GPOINTER_TO_INT(data);
274 void begin_input_event(void)
278 void end_input_event(void)
280         screen_update(mpd);
283 int do_input_event(command_t cmd)
285         if (cmd == CMD_QUIT) {
286                 g_main_loop_quit(main_loop);
287                 return -1;
288         }
290         screen_cmd(mpd, cmd);
292         if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
293                 /* make sure we don't update the volume yet */
294                 g_source_remove(update_source_id);
295                 update_source_id = g_timeout_add(update_interval,
296                                                  timer_mpd_update,
297                                                  GINT_TO_POINTER(TRUE));
298         }
300         return 0;
303 static gboolean
304 keyboard_event(G_GNUC_UNUSED GIOChannel *source,
305                G_GNUC_UNUSED GIOCondition condition,
306                G_GNUC_UNUSED gpointer data)
308         command_t cmd;
310         begin_input_event();
312         if ((cmd=get_keyboard_command()) != CMD_NONE)
313                 if (do_input_event(cmd) != 0)
314                         return FALSE;
316         end_input_event();
317         return TRUE;
320 #ifndef NCMPC_MINI
321 /**
322  * Check the configured key bindings for errors, and display a status
323  * message every 10 seconds.
324  */
325 static gboolean
326 timer_check_key_bindings(G_GNUC_UNUSED gpointer data)
328         char buf[256];
329 #ifdef ENABLE_KEYDEF_SCREEN
330         char comment[64];
331 #endif
332         gboolean key_error;
334         key_error = check_key_bindings(NULL, buf, sizeof(buf));
335         if (!key_error) {
336                 /* no error: disable this timer for the rest of this
337                    process */
338                 check_key_bindings_source_id = 0;
339                 return FALSE;
340         }
342 #ifdef ENABLE_KEYDEF_SCREEN
343         g_strchomp(buf);
344         g_strlcat(buf, " (", sizeof(buf));
345         /* to translators: a key was bound twice in the key editor,
346            and this is a hint for the user what to press to correct
347            that */
348         g_snprintf(comment, sizeof(comment), _("press %s for the key editor"),
349                    get_key_names(CMD_SCREEN_KEYDEF, 0));
350         g_strlcat(buf, comment, sizeof(buf));
351         g_strlcat(buf, ")", sizeof(buf));
352 #endif
354         screen_status_printf("%s", buf);
356         doupdate();
357         return TRUE;
359 #endif
361 int
362 main(int argc, const char *argv[])
364         struct sigaction act;
365 #ifdef ENABLE_LOCALE
366         const char *charset = NULL;
367 #endif
368         GIOChannel *keyboard_channel;
369 #ifdef ENABLE_LIRC
370         int lirc_socket;
371         GIOChannel *lirc_channel = NULL;
372 #endif
374 #ifdef ENABLE_LOCALE
375         /* time and date formatting */
376         setlocale(LC_TIME,"");
377         /* care about sorting order etc */
378         setlocale(LC_COLLATE,"");
379         /* charset */
380         setlocale(LC_CTYPE,"");
381         /* initialize charset conversions */
382         charset = charset_init();
384         /* initialize i18n support */
385 #endif
387 #ifdef ENABLE_NLS
388         setlocale(LC_MESSAGES, "");
389         bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
390 #ifdef ENABLE_LOCALE
391         bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
392 #endif
393         textdomain(GETTEXT_PACKAGE);
394 #endif
396         /* initialize options */
397         options_init();
399         /* parse command line options - 1 pass get configuration files */
400         options_parse(argc, argv);
402 #ifndef NCMPC_MINI
403         /* read configuration */
404         read_configuration();
406         /* check key bindings */
407         check_key_bindings(NULL, NULL, 0);
408 #endif
410         /* parse command line options - 2 pass */
411         options_parse(argc, argv);
413         /* setup signal behavior - SIGINT */
414         sigemptyset(&act.sa_mask);
415         act.sa_flags = 0;
416         act.sa_handler = catch_sigint;
417         if (sigaction(SIGINT, &act, NULL) < 0) {
418                 perror("signal");
419                 exit(EXIT_FAILURE);
420         }
422         /* setup signal behavior - SIGTERM */
424         act.sa_handler = catch_sigint;
425         if (sigaction(SIGTERM, &act, NULL) < 0) {
426                 perror("sigaction()");
427                 exit(EXIT_FAILURE);
428         }
430         /* setup signal behavior - SIGCONT */
432         act.sa_handler = catch_sigcont;
433         if (sigaction(SIGCONT, &act, NULL) < 0) {
434                 perror("sigaction(SIGCONT)");
435                 exit(EXIT_FAILURE);
436         }
438         /* setup signal behaviour - SIGHUP*/
440         act.sa_handler = catch_sigint;
441         if (sigaction(SIGHUP, &act, NULL) < 0) {
442                 perror("sigaction(SIGHUP)");
443                 exit(EXIT_FAILURE);
444         }
446         /* setup SIGWINCH */
448         act.sa_flags = SA_RESTART;
449         act.sa_handler = catch_sigwinch;
450         if (sigaction(SIGWINCH, &act, NULL) < 0) {
451                 perror("sigaction(SIGWINCH)");
452                 exit(EXIT_FAILURE);
453         }
455         /* ignore SIGPIPE */
457         act.sa_handler = SIG_IGN;
458         if (sigaction(SIGPIPE, &act, NULL) < 0) {
459                 perror("sigaction(SIGPIPE)");
460                 exit(EXIT_FAILURE);
461         }
463         ncu_init();
465 #ifdef ENABLE_LYRICS_SCREEN
466         lyrics_init();
467 #endif
469         /* create mpdclient instance */
470         mpd = mpdclient_new();
471         mpdclient_install_error_callback(mpd, error_callback);
473         /* initialize curses */
474         screen_init(mpd);
476         /* the main loop */
477         main_loop = g_main_loop_new(NULL, FALSE);
479         /* watch out for keyboard input */
480         keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
481         g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
483 #ifdef ENABLE_LIRC
484         /* watch out for lirc input */
485         lirc_socket = ncmpc_lirc_open();
486         if (lirc_socket >= 0) {
487                 lirc_channel = g_io_channel_unix_new(lirc_socket);
488                 g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL);
489         }
490 #endif
492         /* attempt to connect */
493         reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
495         update_source_id = g_timeout_add(update_interval,
496                                          timer_mpd_update,
497                                          GINT_TO_POINTER(TRUE));
498 #ifndef NCMPC_MINI
499         check_key_bindings_source_id = g_timeout_add(10000, timer_check_key_bindings, NULL);
500 #endif
502         screen_paint(mpd);
504         g_main_loop_run(main_loop);
506         /* cleanup */
508         cancel_seek_timer();
510         g_source_remove(update_source_id);
512 #ifndef NCMPC_MINI
513         if (check_key_bindings_source_id != 0)
514                 g_source_remove(check_key_bindings_source_id);
515 #endif
517         g_main_loop_unref(main_loop);
518         g_io_channel_unref(keyboard_channel);
520 #ifdef ENABLE_LIRC
521         if (lirc_socket >= 0)
522                 g_io_channel_unref(lirc_channel);
523         ncmpc_lirc_close();
524 #endif
526         exit_and_cleanup();
528 #ifdef ENABLE_LYRICS_SCREEN
529         lyrics_deinit();
530 #endif
532         ncu_deinit();
533         options_deinit();
535         return 0;