Code

native LIRC support for ncmpc
[ncmpc.git] / src / main.c
1 /*
2  * (c) 2004 by Kalle Wallin <kaw@linux.se>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16  *
17  */
19 #include "config.h"
20 #include "ncmpc.h"
21 #include "mpdclient.h"
22 #include "charset.h"
23 #include "options.h"
24 #include "conf.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 "gcc.h"
33 #ifdef ENABLE_LYRICS_SCREEN
34 #include "lyrics.h"
35 #endif
37 #ifdef ENABLE_LIRC
38 #include "lirc.h"
39 #endif
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <signal.h>
44 #include <string.h>
46 /* time between mpd updates [s] */
47 static const guint update_interval = 500;
49 #define BUFSIZE 1024
51 static const guint idle_interval = 500;
53 static mpdclient_t *mpd = NULL;
54 static gboolean connected = FALSE;
55 static GMainLoop *main_loop;
56 static guint reconnect_source_id, idle_source_id, update_source_id;
58 static const gchar *
59 error_msg(const gchar *msg)
60 {
61         gchar *p;
63         if ((p = strchr(msg, '}')) == NULL)
64                 return msg;
66         while (p && *p && (*p=='}' || *p==' '))
67                 p++;
69         return p;
70 }
72 static void
73 error_callback(mpd_unused mpdclient_t *c, gint error, const gchar *msg)
74 {
75         error = error & 0xFF;
76         switch (error) {
77         case MPD_ERROR_CONNPORT:
78         case MPD_ERROR_NORESPONSE:
79                 break;
80         case MPD_ERROR_ACK:
81                 screen_status_printf("%s", error_msg(msg));
82                 screen_bell();
83                 break;
84         default:
85                 screen_status_printf("%s", msg);
86                 screen_bell();
87                 doupdate();
88                 connected = FALSE;
89         }
90 }
92 static void
93 update_xterm_title(void)
94 {
95         static char title[BUFSIZE];
96         char tmp[BUFSIZE];
97         mpd_Status *status = NULL;
98         mpd_Song *song = NULL;
100         if (mpd) {
101                 status = mpd->status;
102                 song = mpd->song;
103         }
105         if (options.xterm_title_format && status && song &&
106             IS_PLAYING(status->state))
107                 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
108         else
109                 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
111         if (strncmp(title, tmp, BUFSIZE)) {
112                 g_strlcpy(title, tmp, BUFSIZE);
113                 set_xterm_title("%s", title);
114         }
117 static void
118 exit_and_cleanup(void)
120         screen_exit();
121         set_xterm_title("");
122         printf("\n");
124         if (mpd) {
125                 mpdclient_disconnect(mpd);
126                 mpdclient_free(mpd);
127         }
129         g_free(options.host);
130         g_free(options.password);
131         g_free(options.list_format);
132         g_free(options.status_format);
133         g_free(options.scroll_sep);
136 static void
137 catch_sigint(mpd_unused int sig)
139         g_main_loop_quit(main_loop);
143 static void
144 catch_sigcont(mpd_unused int sig)
146         screen_resize(mpd);
149 void
150 sigstop(void)
152   def_prog_mode();  /* save the tty modes */
153   endwin();         /* end curses mode temporarily */
154   kill(0, SIGSTOP); /* issue SIGSTOP */
157 static guint timer_sigwinch_id;
159 static gboolean
160 timer_sigwinch(mpd_unused gpointer data)
162         /* the following causes the screen to flicker.  There might be
163            better solutions, but I believe it isn't all that
164            important. */
166         endwin();
167         refresh();
168         screen_resize(mpd);
170         return FALSE;
173 static void
174 catch_sigwinch(mpd_unused int sig)
176         if (timer_sigwinch_id != 0)
177                 g_source_remove(timer_sigwinch_id);
179         timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
182 static gboolean
183 timer_mpd_update(gpointer data);
185 /**
186  * This timer is installed when the connection to the MPD server is
187  * broken.  It tries to recover by reconnecting periodically.
188  */
189 static gboolean
190 timer_reconnect(mpd_unused gpointer data)
192         int ret;
194         if (connected)
195                 return FALSE;
197         screen_status_printf(_("Connecting to %s...  [Press %s to abort]"),
198                              options.host, get_key_names(CMD_QUIT,0) );
199         doupdate();
201         mpdclient_disconnect(mpd);
202         ret = mpdclient_connect(mpd,
203                                 options.host, options.port,
204                                 1.5,
205                                 options.password);
206         if (ret != 0) {
207                 /* try again in 5 seconds */
208                 g_timeout_add(5000, timer_reconnect, NULL);
209                 return FALSE;
210         }
212         /* quit if mpd is pre 0.11.0 - song id not supported by mpd */
213         if (MPD_VERSION_LT(mpd, 0, 11, 0)) {
214                 screen_status_printf(_("Error: MPD version %d.%d.%d is to old (0.11.0 needed).\n"),
215                                      mpd->connection->version[0],
216                                      mpd->connection->version[1],
217                                      mpd->connection->version[2]);
218                 mpdclient_disconnect(mpd);
219                 doupdate();
221                 /* try again after 30 seconds */
222                 g_timeout_add(30000, timer_reconnect, NULL);
223                 return FALSE;
224         }
226         screen_status_printf(_("Connected to %s!"), options.host);
227         doupdate();
229         connected = TRUE;
231         /* update immediately */
232         g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE));
234         reconnect_source_id = 0;
235         return FALSE;
239 static gboolean
240 timer_mpd_update(gpointer data)
242         if (connected)
243                 mpdclient_update(mpd);
244         else if (reconnect_source_id == 0)
245                 reconnect_source_id = g_timeout_add(1000, timer_reconnect,
246                                                     NULL);
248         if (options.enable_xterm_title)
249                 update_xterm_title();
251         screen_update(mpd);
253         return GPOINTER_TO_INT(data);
256 /**
257  * This idle timer is invoked when the user hasn't typed a key for
258  * 500ms.  It is used for delayed seeking.
259  */
260 static gboolean
261 timer_idle(mpd_unused gpointer data)
263         screen_idle(mpd);
264         return TRUE;
267 static gboolean
268 keyboard_event(mpd_unused GIOChannel *source,
269                mpd_unused GIOCondition condition, mpd_unused gpointer data)
271         command_t cmd;
273         /* remove the idle timeout; add it later with fresh interval */
274         g_source_remove(idle_source_id);
276         if ((cmd=get_keyboard_command()) != CMD_NONE) {
277                 if (cmd == CMD_QUIT) {
278                         g_main_loop_quit(main_loop);
279                         return FALSE;
280                 }
282                 screen_cmd(mpd, cmd);
284                 if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
285                         /* make sure we dont update the volume yet */
286                         g_source_remove(update_source_id);
287                         update_source_id = g_timeout_add(update_interval,
288                                                          timer_mpd_update,
289                                                          GINT_TO_POINTER(TRUE));
290                 }
291         }
293         screen_update(mpd);
295         idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
296         return TRUE;
299 #ifdef ENABLE_LIRC
300 static gboolean
301 lirc_event(mpd_unused GIOChannel *source,
302            mpd_unused GIOCondition condition, mpd_unused gpointer data)
304         command_t cmd;
306         /* remove the idle timeout; add it later with fresh interval */
307         g_source_remove(idle_source_id);
309         if ((cmd = ncmpc_lirc_get_command()) != CMD_NONE) {
310                 if (cmd == CMD_QUIT) {
311                         g_main_loop_quit(main_loop);
312                         return FALSE;
313                 }
315                 screen_cmd(mpd, cmd);
317                 if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
318                         /* make sure we dont update the volume yet */
319                         g_source_remove(update_source_id);
320                         update_source_id = g_timeout_add(update_interval,
321                                                          timer_mpd_update,
322                                                          GINT_TO_POINTER(TRUE));
323                 }
324         }
326         screen_update(mpd);
328         idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
329         return TRUE;
331 #endif
333 /**
334  * Check the configured key bindings for errors, and display a status
335  * message every 10 seconds.
336  */
337 static gboolean
338 timer_check_key_bindings(mpd_unused gpointer data)
340         char buf[256];
341         gboolean key_error;
343         key_error = check_key_bindings(NULL, buf, sizeof(buf));
344         if (!key_error)
345                 /* no error: disable this timer for the rest of this
346                    process */
347                 return FALSE;
349         screen_status_printf("%s", buf);
350         doupdate();
351         return TRUE;
354 int
355 main(int argc, const char *argv[])
357         struct sigaction act;
358 #ifdef HAVE_LOCALE_H
359         const char *charset = NULL;
360 #endif
361         GIOChannel *keyboard_channel;
362 #ifdef ENABLE_LIRC
363         int lirc_socket;
364         GIOChannel *lirc_channel = NULL;
365 #endif
367 #ifdef HAVE_LOCALE_H
368         /* time and date formatting */
369         setlocale(LC_TIME,"");
370         /* care about sorting order etc */
371         setlocale(LC_COLLATE,"");
372         /* charset */
373         setlocale(LC_CTYPE,"");
374         /* initialize charset conversions */
375         charset = charset_init();
377         /* initialize i18n support */
378 #ifdef ENABLE_NLS
379         setlocale(LC_MESSAGES, "");
380         bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
381         bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
382         textdomain(GETTEXT_PACKAGE);
383 #endif
384 #endif
386         /* initialize options */
387         options_init();
389         /* parse command line options - 1 pass get configuration files */
390         options_parse(argc, argv);
392         /* read configuration */
393         read_configuration();
395         /* check key bindings */
396         check_key_bindings(NULL, NULL, 0);
398         /* parse command line options - 2 pass */
399         options_parse(argc, argv);
401         /* setup signal behavior - SIGINT */
402         sigemptyset(&act.sa_mask);
403         act.sa_flags = 0;
404         act.sa_handler = catch_sigint;
405         if (sigaction(SIGINT, &act, NULL) < 0) {
406                 perror("signal");
407                 exit(EXIT_FAILURE);
408         }
410         /* setup signal behavior - SIGTERM */
412         act.sa_handler = catch_sigint;
413         if (sigaction(SIGTERM, &act, NULL) < 0) {
414                 perror("sigaction()");
415                 exit(EXIT_FAILURE);
416         }
418         /* setup signal behavior - SIGCONT */
420         act.sa_handler = catch_sigcont;
421         if (sigaction(SIGCONT, &act, NULL) < 0) {
422                 perror("sigaction(SIGCONT)");
423                 exit(EXIT_FAILURE);
424         }
426         /* setup signal behaviour - SIGHUP*/
428         act.sa_handler = catch_sigint;
429         if (sigaction(SIGHUP, &act, NULL) < 0) {
430                 perror("sigaction(SIGHUP)");
431                 exit(EXIT_FAILURE);
432         }
434         /* setup SIGWINCH */
436         act.sa_flags = SA_RESTART;
437         act.sa_handler = catch_sigwinch;
438         if (sigaction(SIGWINCH, &act, NULL) < 0) {
439                 perror("sigaction(SIGWINCH)");
440                 exit(EXIT_FAILURE);
441         }
443         /* ignore SIGPIPE */
445         act.sa_handler = SIG_IGN;
446         if (sigaction(SIGPIPE, &act, NULL) < 0) {
447                 perror("sigaction(SIGPIPE)");
448                 exit(EXIT_FAILURE);
449         }
451         ncu_init();
453 #ifdef ENABLE_LYRICS_SCREEN
454         lyrics_init();
455 #endif
457         /* create mpdclient instance */
458         mpd = mpdclient_new();
459         mpdclient_install_error_callback(mpd, error_callback);
461         /* initialize curses */
462         screen_init(mpd);
464         /* the main loop */
465         main_loop = g_main_loop_new(NULL, FALSE);
467         /* watch out for keyboard input */
468         keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
469         g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
471 #ifdef ENABLE_LIRC
472         /* watch out for lirc input */
473         lirc_socket = ncmpc_lirc_open();
474         if (lirc_socket >= 0) {
475                 lirc_channel = g_io_channel_unix_new(lirc_socket);
476                 g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL);
477         }
478 #endif
480         /* attempt to connect */
481         reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
483         update_source_id = g_timeout_add(update_interval,
484                                          timer_mpd_update,
485                                          GINT_TO_POINTER(TRUE));
486         g_timeout_add(10000, timer_check_key_bindings, NULL);
487         idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
489         screen_paint(mpd);
491         g_main_loop_run(main_loop);
493         /* cleanup */
495         g_main_loop_unref(main_loop);
496         g_io_channel_unref(keyboard_channel);
498 #ifdef ENABLE_LIRC
499         if (lirc_socket >= 0)
500                 g_io_channel_unref(lirc_channel);
501         ncmpc_lirc_close();
502 #endif
504         exit_and_cleanup();
505         ncu_deinit();
507         return 0;