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 "command.h"
25 #include "ncu.h"
26 #include "screen.h"
27 #include "screen_utils.h"
28 #include "strfsong.h"
29 #include "i18n.h"
30 #include "gcc.h"
32 #ifndef NCMPC_MINI
33 #include "conf.h"
34 #endif
36 #ifdef ENABLE_LYRICS_SCREEN
37 #include "lyrics.h"
38 #endif
40 #ifdef ENABLE_LIRC
41 #include "lirc.h"
42 #endif
44 #include <stdlib.h>
45 #include <unistd.h>
46 #include <signal.h>
47 #include <string.h>
49 /* time between mpd updates [s] */
50 static const guint update_interval = 500;
52 #define BUFSIZE 1024
54 static const guint idle_interval = 500;
56 static mpdclient_t *mpd = NULL;
57 static gboolean connected = FALSE;
58 static GMainLoop *main_loop;
59 static guint reconnect_source_id, idle_source_id, update_source_id;
61 static const gchar *
62 error_msg(const gchar *msg)
63 {
64 gchar *p;
66 if ((p = strchr(msg, '}')) == NULL)
67 return msg;
69 do {
70 p++;
71 } while (*p == '}' || * p== ' ');
73 return p;
74 }
76 static void
77 error_callback(mpd_unused mpdclient_t *c, gint error, const gchar *_msg)
78 {
79 char *msg = utf8_to_locale(_msg);
81 error = error & 0xFF;
82 switch (error) {
83 case MPD_ERROR_CONNPORT:
84 case MPD_ERROR_NORESPONSE:
85 break;
86 case MPD_ERROR_ACK:
87 screen_status_printf("%s", error_msg(msg));
88 screen_bell();
89 break;
90 default:
91 screen_status_printf("%s", msg);
92 screen_bell();
93 doupdate();
94 connected = FALSE;
95 }
97 g_free(msg);
98 }
100 #ifndef NCMPC_MINI
101 static void
102 update_xterm_title(void)
103 {
104 static char title[BUFSIZE];
105 char tmp[BUFSIZE];
106 mpd_Status *status = NULL;
107 mpd_Song *song = NULL;
109 if (mpd) {
110 status = mpd->status;
111 song = mpd->song;
112 }
114 if (options.xterm_title_format && status && song &&
115 IS_PLAYING(status->state))
116 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
117 else
118 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
120 if (strncmp(title, tmp, BUFSIZE)) {
121 g_strlcpy(title, tmp, BUFSIZE);
122 set_xterm_title("%s", title);
123 }
124 }
125 #endif
127 static void
128 exit_and_cleanup(void)
129 {
130 screen_exit();
131 #ifndef NCMPC_MINI
132 set_xterm_title("");
133 #endif
134 printf("\n");
136 if (mpd) {
137 mpdclient_disconnect(mpd);
138 mpdclient_free(mpd);
139 }
141 g_free(options.host);
142 g_free(options.password);
143 g_free(options.list_format);
144 g_free(options.status_format);
145 #ifndef NCMPC_MINI
146 g_free(options.scroll_sep);
147 #endif
148 }
150 static void
151 catch_sigint(mpd_unused int sig)
152 {
153 g_main_loop_quit(main_loop);
154 }
157 static void
158 catch_sigcont(mpd_unused int sig)
159 {
160 screen_resize(mpd);
161 }
163 void
164 sigstop(void)
165 {
166 def_prog_mode(); /* save the tty modes */
167 endwin(); /* end curses mode temporarily */
168 kill(0, SIGSTOP); /* issue SIGSTOP */
169 }
171 static guint timer_sigwinch_id;
173 static gboolean
174 timer_sigwinch(mpd_unused gpointer data)
175 {
176 /* the following causes the screen to flicker. There might be
177 better solutions, but I believe it isn't all that
178 important. */
180 endwin();
181 refresh();
182 screen_resize(mpd);
184 return FALSE;
185 }
187 static void
188 catch_sigwinch(mpd_unused int sig)
189 {
190 if (timer_sigwinch_id != 0)
191 g_source_remove(timer_sigwinch_id);
193 timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
194 }
196 static gboolean
197 timer_mpd_update(gpointer data);
199 /**
200 * This timer is installed when the connection to the MPD server is
201 * broken. It tries to recover by reconnecting periodically.
202 */
203 static gboolean
204 timer_reconnect(mpd_unused gpointer data)
205 {
206 int ret;
208 if (connected)
209 return FALSE;
211 screen_status_printf(_("Connecting to %s... [Press %s to abort]"),
212 options.host, get_key_names(CMD_QUIT,0) );
213 doupdate();
215 mpdclient_disconnect(mpd);
216 ret = mpdclient_connect(mpd,
217 options.host, options.port,
218 1.5,
219 options.password);
220 if (ret != 0) {
221 /* try again in 5 seconds */
222 g_timeout_add(5000, timer_reconnect, NULL);
223 return FALSE;
224 }
226 #ifndef NCMPC_MINI
227 /* quit if mpd is pre 0.11.0 - song id not supported by mpd */
228 if (MPD_VERSION_LT(mpd, 0, 11, 0)) {
229 screen_status_printf(_("Error: MPD version %d.%d.%d is to old (0.11.0 needed).\n"),
230 mpd->connection->version[0],
231 mpd->connection->version[1],
232 mpd->connection->version[2]);
233 mpdclient_disconnect(mpd);
234 doupdate();
236 /* try again after 30 seconds */
237 g_timeout_add(30000, timer_reconnect, NULL);
238 return FALSE;
239 }
240 #endif
242 screen_status_printf(_("Connected to %s!"), options.host);
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;
253 }
255 static gboolean
256 timer_mpd_update(gpointer data)
257 {
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);
272 }
274 /**
275 * This idle timer is invoked when the user hasn't typed a key for
276 * 500ms. It is used for delayed seeking.
277 */
278 static gboolean
279 timer_idle(mpd_unused gpointer data)
280 {
281 screen_idle(mpd);
282 return TRUE;
283 }
285 void begin_input_event(void)
286 {
287 /* remove the idle timeout; add it later with fresh interval */
288 g_source_remove(idle_source_id);
289 }
291 void end_input_event(void)
292 {
293 screen_update(mpd);
295 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
296 }
298 int do_input_event(command_t cmd)
299 {
300 if (cmd == CMD_QUIT) {
301 g_main_loop_quit(main_loop);
302 return -1;
303 }
305 screen_cmd(mpd, cmd);
307 if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
308 /* make sure we dont update the volume yet */
309 g_source_remove(update_source_id);
310 update_source_id = g_timeout_add(update_interval,
311 timer_mpd_update,
312 GINT_TO_POINTER(TRUE));
313 }
315 return 0;
316 }
318 static gboolean
319 keyboard_event(mpd_unused GIOChannel *source,
320 mpd_unused GIOCondition condition, mpd_unused gpointer data)
321 {
322 command_t cmd;
324 begin_input_event();
326 if ((cmd=get_keyboard_command()) != CMD_NONE)
327 if (do_input_event(cmd) != 0)
328 return FALSE;
330 end_input_event();
331 return TRUE;
332 }
334 #ifndef NCMPC_MINI
335 /**
336 * Check the configured key bindings for errors, and display a status
337 * message every 10 seconds.
338 */
339 static gboolean
340 timer_check_key_bindings(mpd_unused gpointer data)
341 {
342 char buf[256];
343 gboolean key_error;
345 key_error = check_key_bindings(NULL, buf, sizeof(buf));
346 if (!key_error)
347 /* no error: disable this timer for the rest of this
348 process */
349 return FALSE;
351 screen_status_printf("%s", buf);
352 doupdate();
353 return TRUE;
354 }
355 #endif
357 int
358 main(int argc, const char *argv[])
359 {
360 struct sigaction act;
361 #if defined(HAVE_LOCALE_H) && !defined(NCMPC_MINI)
362 const char *charset = NULL;
363 #endif
364 GIOChannel *keyboard_channel;
365 #ifdef ENABLE_LIRC
366 int lirc_socket;
367 GIOChannel *lirc_channel = NULL;
368 #endif
370 #if defined(HAVE_LOCALE_H) && !defined(NCMPC_MINI)
371 /* time and date formatting */
372 setlocale(LC_TIME,"");
373 /* care about sorting order etc */
374 setlocale(LC_COLLATE,"");
375 /* charset */
376 setlocale(LC_CTYPE,"");
377 /* initialize charset conversions */
378 charset = charset_init();
380 /* initialize i18n support */
381 #ifdef ENABLE_NLS
382 setlocale(LC_MESSAGES, "");
383 bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
384 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
385 textdomain(GETTEXT_PACKAGE);
386 #endif
387 #endif
389 /* initialize options */
390 options_init();
392 /* parse command line options - 1 pass get configuration files */
393 options_parse(argc, argv);
395 /* read configuration */
396 #ifndef NCMPC_MINI
397 read_configuration();
398 #endif
400 /* check key bindings */
401 check_key_bindings(NULL, NULL, 0);
403 /* parse command line options - 2 pass */
404 options_parse(argc, argv);
406 /* setup signal behavior - SIGINT */
407 sigemptyset(&act.sa_mask);
408 act.sa_flags = 0;
409 act.sa_handler = catch_sigint;
410 if (sigaction(SIGINT, &act, NULL) < 0) {
411 perror("signal");
412 exit(EXIT_FAILURE);
413 }
415 /* setup signal behavior - SIGTERM */
417 act.sa_handler = catch_sigint;
418 if (sigaction(SIGTERM, &act, NULL) < 0) {
419 perror("sigaction()");
420 exit(EXIT_FAILURE);
421 }
423 /* setup signal behavior - SIGCONT */
425 act.sa_handler = catch_sigcont;
426 if (sigaction(SIGCONT, &act, NULL) < 0) {
427 perror("sigaction(SIGCONT)");
428 exit(EXIT_FAILURE);
429 }
431 /* setup signal behaviour - SIGHUP*/
433 act.sa_handler = catch_sigint;
434 if (sigaction(SIGHUP, &act, NULL) < 0) {
435 perror("sigaction(SIGHUP)");
436 exit(EXIT_FAILURE);
437 }
439 /* setup SIGWINCH */
441 act.sa_flags = SA_RESTART;
442 act.sa_handler = catch_sigwinch;
443 if (sigaction(SIGWINCH, &act, NULL) < 0) {
444 perror("sigaction(SIGWINCH)");
445 exit(EXIT_FAILURE);
446 }
448 /* ignore SIGPIPE */
450 act.sa_handler = SIG_IGN;
451 if (sigaction(SIGPIPE, &act, NULL) < 0) {
452 perror("sigaction(SIGPIPE)");
453 exit(EXIT_FAILURE);
454 }
456 ncu_init();
458 #ifdef ENABLE_LYRICS_SCREEN
459 lyrics_init();
460 #endif
462 /* create mpdclient instance */
463 mpd = mpdclient_new();
464 mpdclient_install_error_callback(mpd, error_callback);
466 /* initialize curses */
467 screen_init(mpd);
469 /* the main loop */
470 main_loop = g_main_loop_new(NULL, FALSE);
472 /* watch out for keyboard input */
473 keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
474 g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
476 #ifdef ENABLE_LIRC
477 /* watch out for lirc input */
478 lirc_socket = ncmpc_lirc_open();
479 if (lirc_socket >= 0) {
480 lirc_channel = g_io_channel_unix_new(lirc_socket);
481 g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL);
482 }
483 #endif
485 /* attempt to connect */
486 reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
488 update_source_id = g_timeout_add(update_interval,
489 timer_mpd_update,
490 GINT_TO_POINTER(TRUE));
491 #ifndef NCMPC_MINI
492 g_timeout_add(10000, timer_check_key_bindings, NULL);
493 #endif
494 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
496 screen_paint(mpd);
498 g_main_loop_run(main_loop);
500 /* cleanup */
502 g_main_loop_unref(main_loop);
503 g_io_channel_unref(keyboard_channel);
505 #ifdef ENABLE_LIRC
506 if (lirc_socket >= 0)
507 g_io_channel_unref(lirc_channel);
508 ncmpc_lirc_close();
509 #endif
511 exit_and_cleanup();
512 ncu_deinit();
514 return 0;
515 }