Code

screen: moved status message functions to screen_message.c
[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 "screen_message.h"
30 #include "strfsong.h"
31 #include "i18n.h"
32 #include "player_command.h"
34 #ifndef NCMPC_MINI
35 #include "conf.h"
36 #endif
38 #ifdef ENABLE_LYRICS_SCREEN
39 #include "lyrics.h"
40 #endif
42 #ifdef ENABLE_LIRC
43 #include "lirc.h"
44 #endif
46 #include <mpd/client.h>
48 #include <stdlib.h>
49 #include <unistd.h>
50 #include <signal.h>
51 #include <string.h>
53 #ifdef ENABLE_LOCALE
54 #include <locale.h>
55 #endif
57 /* time between mpd updates [s] */
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;
66 #ifndef NCMPC_MINI
67 static guint check_key_bindings_source_id;
68 #endif
70 #ifndef NCMPC_MINI
71 static void
72 update_xterm_title(void)
73 {
74         static char title[BUFSIZE];
75         char tmp[BUFSIZE];
76         struct mpd_status *status = NULL;
77         const struct mpd_song *song = NULL;
79         if (mpd) {
80                 status = mpd->status;
81                 song = mpd->song;
82         }
84         if (options.xterm_title_format && status && song &&
85             mpd_status_get_state(status) == MPD_STATE_PLAY)
86                 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
87         else
88                 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
90         if (strncmp(title, tmp, BUFSIZE)) {
91                 g_strlcpy(title, tmp, BUFSIZE);
92                 set_xterm_title("%s", title);
93         }
94 }
95 #endif
97 static void
98 exit_and_cleanup(void)
99 {
100         screen_exit();
101 #ifndef NCMPC_MINI
102         set_xterm_title("");
103 #endif
104         printf("\n");
106         if (mpd) {
107                 mpdclient_disconnect(mpd);
108                 mpdclient_free(mpd);
109         }
112 static void
113 catch_sigint(G_GNUC_UNUSED int sig)
115         g_main_loop_quit(main_loop);
119 static void
120 catch_sigcont(G_GNUC_UNUSED int sig)
122         screen_resize(mpd);
125 void
126 sigstop(void)
128   def_prog_mode();  /* save the tty modes */
129   endwin();         /* end curses mode temporarily */
130   kill(0, SIGSTOP); /* issue SIGSTOP */
133 static guint timer_sigwinch_id;
135 static gboolean
136 timer_sigwinch(G_GNUC_UNUSED gpointer data)
138         /* the following causes the screen to flicker.  There might be
139            better solutions, but I believe it isn't all that
140            important. */
142         endwin();
143         refresh();
144         screen_resize(mpd);
146         return FALSE;
149 static void
150 catch_sigwinch(G_GNUC_UNUSED int sig)
152         if (timer_sigwinch_id != 0)
153                 g_source_remove(timer_sigwinch_id);
155         timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
158 static gboolean
159 timer_mpd_update(gpointer data);
161 /**
162  * This timer is installed when the connection to the MPD server is
163  * broken.  It tries to recover by reconnecting periodically.
164  */
165 static gboolean
166 timer_reconnect(G_GNUC_UNUSED gpointer data)
168         bool success;
170         assert(!mpdclient_is_connected(mpd));
172         screen_status_printf(_("Connecting to %s...  [Press %s to abort]"),
173                              options.host, get_key_names(CMD_QUIT,0) );
174         doupdate();
176         mpdclient_disconnect(mpd);
177         success = mpdclient_connect(mpd,
178                                     options.host, options.port,
179                                     1.5,
180                                     options.password);
181         if (!success) {
182                 /* try again in 5 seconds */
183                 g_timeout_add(5000, timer_reconnect, NULL);
184                 return FALSE;
185         }
187 #ifndef NCMPC_MINI
188         /* quit if mpd is pre 0.11.0 - song id not supported by mpd */
189         if (mpd_connection_cmp_server_version(mpd->connection, 0, 12, 0) < 0) {
190                 const unsigned *version =
191                         mpd_connection_get_server_version(mpd->connection);
192                 screen_status_printf(_("Error: MPD version %d.%d.%d is to old (%s needed)"),
193                                      version[0], version[1], version[2],
194                                      "0.12.0");
195                 mpdclient_disconnect(mpd);
196                 doupdate();
198                 /* try again after 30 seconds */
199                 g_timeout_add(30000, timer_reconnect, NULL);
200                 return FALSE;
201         }
202 #endif
204         screen_status_printf(_("Connected to %s"),
205                              options.host != NULL
206                              ? options.host : "localhost");
207         doupdate();
209         /* update immediately */
210         g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE));
212         reconnect_source_id = 0;
213         return FALSE;
217 static gboolean
218 timer_mpd_update(gpointer data)
220         if (mpdclient_is_connected(mpd))
221                 mpdclient_update(mpd);
222         else if (reconnect_source_id == 0)
223                 reconnect_source_id = g_timeout_add(1000, timer_reconnect,
224                                                     NULL);
226 #ifndef NCMPC_MINI
227         if (options.enable_xterm_title)
228                 update_xterm_title();
229 #endif
231         screen_update(mpd);
233         mpd->events = 0;
235         return GPOINTER_TO_INT(data);
238 void begin_input_event(void)
242 void end_input_event(void)
244         screen_update(mpd);
247 int do_input_event(command_t cmd)
249         if (cmd == CMD_QUIT) {
250                 g_main_loop_quit(main_loop);
251                 return -1;
252         }
254         screen_cmd(mpd, cmd);
256         if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
257                 /* make sure we don't update the volume yet */
258                 g_source_remove(update_source_id);
259                 update_source_id = g_timeout_add(update_interval,
260                                                  timer_mpd_update,
261                                                  GINT_TO_POINTER(TRUE));
262         }
264         return 0;
267 static gboolean
268 keyboard_event(G_GNUC_UNUSED GIOChannel *source,
269                G_GNUC_UNUSED GIOCondition condition,
270                G_GNUC_UNUSED gpointer data)
272         command_t cmd;
274         begin_input_event();
276         if ((cmd=get_keyboard_command()) != CMD_NONE)
277                 if (do_input_event(cmd) != 0)
278                         return FALSE;
280         end_input_event();
281         return TRUE;
284 #ifndef NCMPC_MINI
285 /**
286  * Check the configured key bindings for errors, and display a status
287  * message every 10 seconds.
288  */
289 static gboolean
290 timer_check_key_bindings(G_GNUC_UNUSED gpointer data)
292         char buf[256];
293 #ifdef ENABLE_KEYDEF_SCREEN
294         char comment[64];
295 #endif
296         gboolean key_error;
298         key_error = check_key_bindings(NULL, buf, sizeof(buf));
299         if (!key_error) {
300                 /* no error: disable this timer for the rest of this
301                    process */
302                 check_key_bindings_source_id = 0;
303                 return FALSE;
304         }
306 #ifdef ENABLE_KEYDEF_SCREEN
307         g_strchomp(buf);
308         g_strlcat(buf, " (", sizeof(buf));
309         /* to translators: a key was bound twice in the key editor,
310            and this is a hint for the user what to press to correct
311            that */
312         g_snprintf(comment, sizeof(comment), _("press %s for the key editor"),
313                    get_key_names(CMD_SCREEN_KEYDEF, 0));
314         g_strlcat(buf, comment, sizeof(buf));
315         g_strlcat(buf, ")", sizeof(buf));
316 #endif
318         screen_status_printf("%s", buf);
320         doupdate();
321         return TRUE;
323 #endif
325 int
326 main(int argc, const char *argv[])
328         struct sigaction act;
329 #ifdef ENABLE_LOCALE
330         const char *charset = NULL;
331 #endif
332         GIOChannel *keyboard_channel;
333 #ifdef ENABLE_LIRC
334         int lirc_socket;
335         GIOChannel *lirc_channel = NULL;
336 #endif
338 #ifdef ENABLE_LOCALE
339         /* time and date formatting */
340         setlocale(LC_TIME,"");
341         /* care about sorting order etc */
342         setlocale(LC_COLLATE,"");
343         /* charset */
344         setlocale(LC_CTYPE,"");
345         /* initialize charset conversions */
346         charset = charset_init();
348         /* initialize i18n support */
349 #endif
351 #ifdef ENABLE_NLS
352         setlocale(LC_MESSAGES, "");
353         bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
354 #ifdef ENABLE_LOCALE
355         bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
356 #endif
357         textdomain(GETTEXT_PACKAGE);
358 #endif
360         /* initialize options */
361         options_init();
363         /* parse command line options - 1 pass get configuration files */
364         options_parse(argc, argv);
366 #ifndef NCMPC_MINI
367         /* read configuration */
368         read_configuration();
370         /* check key bindings */
371         check_key_bindings(NULL, NULL, 0);
372 #endif
374         /* parse command line options - 2 pass */
375         options_parse(argc, argv);
377         /* setup signal behavior - SIGINT */
378         sigemptyset(&act.sa_mask);
379         act.sa_flags = 0;
380         act.sa_handler = catch_sigint;
381         if (sigaction(SIGINT, &act, NULL) < 0) {
382                 perror("signal");
383                 exit(EXIT_FAILURE);
384         }
386         /* setup signal behavior - SIGTERM */
388         act.sa_handler = catch_sigint;
389         if (sigaction(SIGTERM, &act, NULL) < 0) {
390                 perror("sigaction()");
391                 exit(EXIT_FAILURE);
392         }
394         /* setup signal behavior - SIGCONT */
396         act.sa_handler = catch_sigcont;
397         if (sigaction(SIGCONT, &act, NULL) < 0) {
398                 perror("sigaction(SIGCONT)");
399                 exit(EXIT_FAILURE);
400         }
402         /* setup signal behaviour - SIGHUP*/
404         act.sa_handler = catch_sigint;
405         if (sigaction(SIGHUP, &act, NULL) < 0) {
406                 perror("sigaction(SIGHUP)");
407                 exit(EXIT_FAILURE);
408         }
410         /* setup SIGWINCH */
412         act.sa_flags = SA_RESTART;
413         act.sa_handler = catch_sigwinch;
414         if (sigaction(SIGWINCH, &act, NULL) < 0) {
415                 perror("sigaction(SIGWINCH)");
416                 exit(EXIT_FAILURE);
417         }
419         /* ignore SIGPIPE */
421         act.sa_handler = SIG_IGN;
422         if (sigaction(SIGPIPE, &act, NULL) < 0) {
423                 perror("sigaction(SIGPIPE)");
424                 exit(EXIT_FAILURE);
425         }
427         ncu_init();
429 #ifdef ENABLE_LYRICS_SCREEN
430         lyrics_init();
431 #endif
433         /* create mpdclient instance */
434         mpd = mpdclient_new();
436         /* initialize curses */
437         screen_init(mpd);
439         /* the main loop */
440         main_loop = g_main_loop_new(NULL, FALSE);
442         /* watch out for keyboard input */
443         keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
444         g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
446 #ifdef ENABLE_LIRC
447         /* watch out for lirc input */
448         lirc_socket = ncmpc_lirc_open();
449         if (lirc_socket >= 0) {
450                 lirc_channel = g_io_channel_unix_new(lirc_socket);
451                 g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL);
452         }
453 #endif
455         /* attempt to connect */
456         reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
458         update_source_id = g_timeout_add(update_interval,
459                                          timer_mpd_update,
460                                          GINT_TO_POINTER(TRUE));
461 #ifndef NCMPC_MINI
462         check_key_bindings_source_id = g_timeout_add(10000, timer_check_key_bindings, NULL);
463 #endif
465         screen_paint(mpd);
467         g_main_loop_run(main_loop);
469         /* cleanup */
471         cancel_seek_timer();
473         g_source_remove(update_source_id);
475 #ifndef NCMPC_MINI
476         if (check_key_bindings_source_id != 0)
477                 g_source_remove(check_key_bindings_source_id);
478 #endif
480         g_main_loop_unref(main_loop);
481         g_io_channel_unref(keyboard_channel);
483 #ifdef ENABLE_LIRC
484         if (lirc_socket >= 0)
485                 g_io_channel_unref(lirc_channel);
486         ncmpc_lirc_close();
487 #endif
489         exit_and_cleanup();
491 #ifdef ENABLE_LYRICS_SCREEN
492         lyrics_deinit();
493 #endif
495         ncu_deinit();
496         options_deinit();
498         return 0;