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"
31 #ifndef NCMPC_MINI
32 #include "conf.h"
33 #endif
35 #ifdef ENABLE_LYRICS_SCREEN
36 #include "lyrics.h"
37 #endif
39 #ifdef ENABLE_LIRC
40 #include "lirc.h"
41 #endif
43 #include <stdlib.h>
44 #include <unistd.h>
45 #include <signal.h>
46 #include <string.h>
48 #ifdef HAVE_LOCALE_H
49 #include <locale.h>
50 #endif
52 /* time between mpd updates [s] */
53 static const guint update_interval = 500;
55 #define BUFSIZE 1024
57 static const guint idle_interval = 500;
59 static mpdclient_t *mpd = NULL;
60 static gboolean connected = FALSE;
61 static GMainLoop *main_loop;
62 static guint reconnect_source_id, idle_source_id, update_source_id;
64 static const gchar *
65 error_msg(const gchar *msg)
66 {
67 gchar *p;
69 if ((p = strchr(msg, '}')) == NULL)
70 return msg;
72 do {
73 p++;
74 } while (*p == '}' || * p== ' ');
76 return p;
77 }
79 static void
80 error_callback(G_GNUC_UNUSED mpdclient_t *c, gint error, const gchar *_msg)
81 {
82 char *msg = utf8_to_locale(_msg);
84 error = error & 0xFF;
85 switch (error) {
86 case MPD_ERROR_CONNPORT:
87 case MPD_ERROR_NORESPONSE:
88 break;
89 case MPD_ERROR_ACK:
90 screen_status_printf("%s", error_msg(msg));
91 screen_bell();
92 break;
93 default:
94 screen_status_printf("%s", msg);
95 screen_bell();
96 doupdate();
97 connected = FALSE;
98 }
100 g_free(msg);
101 }
103 #ifndef NCMPC_MINI
104 static void
105 update_xterm_title(void)
106 {
107 static char title[BUFSIZE];
108 char tmp[BUFSIZE];
109 mpd_Status *status = NULL;
110 mpd_Song *song = NULL;
112 if (mpd) {
113 status = mpd->status;
114 song = mpd->song;
115 }
117 if (options.xterm_title_format && status && song &&
118 IS_PLAYING(status->state))
119 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
120 else
121 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
123 if (strncmp(title, tmp, BUFSIZE)) {
124 g_strlcpy(title, tmp, BUFSIZE);
125 set_xterm_title("%s", title);
126 }
127 }
128 #endif
130 static void
131 exit_and_cleanup(void)
132 {
133 screen_exit();
134 #ifndef NCMPC_MINI
135 set_xterm_title("");
136 #endif
137 printf("\n");
139 if (mpd) {
140 mpdclient_disconnect(mpd);
141 mpdclient_free(mpd);
142 }
144 g_free(options.host);
145 g_free(options.password);
146 g_free(options.list_format);
147 g_free(options.status_format);
148 #ifndef NCMPC_MINI
149 g_free(options.scroll_sep);
150 #endif
151 }
153 static void
154 catch_sigint(G_GNUC_UNUSED int sig)
155 {
156 g_main_loop_quit(main_loop);
157 }
160 static void
161 catch_sigcont(G_GNUC_UNUSED int sig)
162 {
163 screen_resize(mpd);
164 }
166 void
167 sigstop(void)
168 {
169 def_prog_mode(); /* save the tty modes */
170 endwin(); /* end curses mode temporarily */
171 kill(0, SIGSTOP); /* issue SIGSTOP */
172 }
174 static guint timer_sigwinch_id;
176 static gboolean
177 timer_sigwinch(G_GNUC_UNUSED gpointer data)
178 {
179 /* the following causes the screen to flicker. There might be
180 better solutions, but I believe it isn't all that
181 important. */
183 endwin();
184 refresh();
185 screen_resize(mpd);
187 return FALSE;
188 }
190 static void
191 catch_sigwinch(G_GNUC_UNUSED int sig)
192 {
193 if (timer_sigwinch_id != 0)
194 g_source_remove(timer_sigwinch_id);
196 timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
197 }
199 static gboolean
200 timer_mpd_update(gpointer data);
202 /**
203 * This timer is installed when the connection to the MPD server is
204 * broken. It tries to recover by reconnecting periodically.
205 */
206 static gboolean
207 timer_reconnect(G_GNUC_UNUSED gpointer data)
208 {
209 int ret;
211 if (connected)
212 return FALSE;
214 screen_status_printf(_("Connecting to %s... [Press %s to abort]"),
215 options.host, get_key_names(CMD_QUIT,0) );
216 doupdate();
218 mpdclient_disconnect(mpd);
219 ret = mpdclient_connect(mpd,
220 options.host, options.port,
221 1.5,
222 options.password);
223 if (ret != 0) {
224 /* try again in 5 seconds */
225 g_timeout_add(5000, timer_reconnect, NULL);
226 return FALSE;
227 }
229 #ifndef NCMPC_MINI
230 /* quit if mpd is pre 0.11.0 - song id not supported by mpd */
231 if (MPD_VERSION_LT(mpd, 0, 11, 0)) {
232 screen_status_printf(_("Error: MPD version %d.%d.%d is to old (%s needed)"),
233 mpd->connection->version[0],
234 mpd->connection->version[1],
235 mpd->connection->version[2],
236 "0.11.0");
237 mpdclient_disconnect(mpd);
238 doupdate();
240 /* try again after 30 seconds */
241 g_timeout_add(30000, timer_reconnect, NULL);
242 return FALSE;
243 }
244 #endif
246 screen_status_printf(_("Connected to %s"), options.host);
247 doupdate();
249 connected = TRUE;
251 /* update immediately */
252 g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE));
254 reconnect_source_id = 0;
255 return FALSE;
257 }
259 static gboolean
260 timer_mpd_update(gpointer data)
261 {
262 if (connected)
263 mpdclient_update(mpd);
264 else if (reconnect_source_id == 0)
265 reconnect_source_id = g_timeout_add(1000, timer_reconnect,
266 NULL);
268 #ifndef NCMPC_MINI
269 if (options.enable_xterm_title)
270 update_xterm_title();
271 #endif
273 screen_update(mpd);
275 return GPOINTER_TO_INT(data);
276 }
278 /**
279 * This idle timer is invoked when the user hasn't typed a key for
280 * 500ms. It is used for delayed seeking.
281 */
282 static gboolean
283 timer_idle(G_GNUC_UNUSED gpointer data)
284 {
285 screen_idle(mpd);
286 return TRUE;
287 }
289 void begin_input_event(void)
290 {
291 /* remove the idle timeout; add it later with fresh interval */
292 g_source_remove(idle_source_id);
293 }
295 void end_input_event(void)
296 {
297 screen_update(mpd);
299 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
300 }
302 int do_input_event(command_t cmd)
303 {
304 if (cmd == CMD_QUIT) {
305 g_main_loop_quit(main_loop);
306 return -1;
307 }
309 screen_cmd(mpd, cmd);
311 if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
312 /* make sure we dont update the volume yet */
313 g_source_remove(update_source_id);
314 update_source_id = g_timeout_add(update_interval,
315 timer_mpd_update,
316 GINT_TO_POINTER(TRUE));
317 }
319 return 0;
320 }
322 static gboolean
323 keyboard_event(G_GNUC_UNUSED GIOChannel *source,
324 G_GNUC_UNUSED GIOCondition condition,
325 G_GNUC_UNUSED gpointer data)
326 {
327 command_t cmd;
329 begin_input_event();
331 if ((cmd=get_keyboard_command()) != CMD_NONE)
332 if (do_input_event(cmd) != 0)
333 return FALSE;
335 end_input_event();
336 return TRUE;
337 }
339 #ifndef NCMPC_MINI
340 /**
341 * Check the configured key bindings for errors, and display a status
342 * message every 10 seconds.
343 */
344 static gboolean
345 timer_check_key_bindings(G_GNUC_UNUSED gpointer data)
346 {
347 char buf[256];
348 gboolean key_error;
350 key_error = check_key_bindings(NULL, buf, sizeof(buf));
351 if (!key_error)
352 /* no error: disable this timer for the rest of this
353 process */
354 return FALSE;
356 screen_status_printf("%s", buf);
357 doupdate();
358 return TRUE;
359 }
360 #endif
362 int
363 main(int argc, const char *argv[])
364 {
365 struct sigaction act;
366 #if defined(HAVE_LOCALE_H) && !defined(NCMPC_MINI)
367 const char *charset = NULL;
368 #endif
369 GIOChannel *keyboard_channel;
370 #ifdef ENABLE_LIRC
371 int lirc_socket;
372 GIOChannel *lirc_channel = NULL;
373 #endif
375 #if defined(HAVE_LOCALE_H) && !defined(NCMPC_MINI)
376 /* time and date formatting */
377 setlocale(LC_TIME,"");
378 /* care about sorting order etc */
379 setlocale(LC_COLLATE,"");
380 /* charset */
381 setlocale(LC_CTYPE,"");
382 /* initialize charset conversions */
383 charset = charset_init();
385 /* initialize i18n support */
386 #ifdef ENABLE_NLS
387 setlocale(LC_MESSAGES, "");
388 bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
389 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
390 textdomain(GETTEXT_PACKAGE);
391 #endif
392 #endif
394 /* initialize options */
395 options_init();
397 /* parse command line options - 1 pass get configuration files */
398 options_parse(argc, argv);
400 #ifndef NCMPC_MINI
401 /* read configuration */
402 read_configuration();
404 /* check key bindings */
405 check_key_bindings(NULL, NULL, 0);
406 #endif
408 /* parse command line options - 2 pass */
409 options_parse(argc, argv);
411 /* setup signal behavior - SIGINT */
412 sigemptyset(&act.sa_mask);
413 act.sa_flags = 0;
414 act.sa_handler = catch_sigint;
415 if (sigaction(SIGINT, &act, NULL) < 0) {
416 perror("signal");
417 exit(EXIT_FAILURE);
418 }
420 /* setup signal behavior - SIGTERM */
422 act.sa_handler = catch_sigint;
423 if (sigaction(SIGTERM, &act, NULL) < 0) {
424 perror("sigaction()");
425 exit(EXIT_FAILURE);
426 }
428 /* setup signal behavior - SIGCONT */
430 act.sa_handler = catch_sigcont;
431 if (sigaction(SIGCONT, &act, NULL) < 0) {
432 perror("sigaction(SIGCONT)");
433 exit(EXIT_FAILURE);
434 }
436 /* setup signal behaviour - SIGHUP*/
438 act.sa_handler = catch_sigint;
439 if (sigaction(SIGHUP, &act, NULL) < 0) {
440 perror("sigaction(SIGHUP)");
441 exit(EXIT_FAILURE);
442 }
444 /* setup SIGWINCH */
446 act.sa_flags = SA_RESTART;
447 act.sa_handler = catch_sigwinch;
448 if (sigaction(SIGWINCH, &act, NULL) < 0) {
449 perror("sigaction(SIGWINCH)");
450 exit(EXIT_FAILURE);
451 }
453 /* ignore SIGPIPE */
455 act.sa_handler = SIG_IGN;
456 if (sigaction(SIGPIPE, &act, NULL) < 0) {
457 perror("sigaction(SIGPIPE)");
458 exit(EXIT_FAILURE);
459 }
461 ncu_init();
463 #ifdef ENABLE_LYRICS_SCREEN
464 lyrics_init();
465 #endif
467 /* create mpdclient instance */
468 mpd = mpdclient_new();
469 mpdclient_install_error_callback(mpd, error_callback);
471 /* initialize curses */
472 screen_init(mpd);
474 /* the main loop */
475 main_loop = g_main_loop_new(NULL, FALSE);
477 /* watch out for keyboard input */
478 keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
479 g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
481 #ifdef ENABLE_LIRC
482 /* watch out for lirc input */
483 lirc_socket = ncmpc_lirc_open();
484 if (lirc_socket >= 0) {
485 lirc_channel = g_io_channel_unix_new(lirc_socket);
486 g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL);
487 }
488 #endif
490 /* attempt to connect */
491 reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
493 update_source_id = g_timeout_add(update_interval,
494 timer_mpd_update,
495 GINT_TO_POINTER(TRUE));
496 #ifndef NCMPC_MINI
497 g_timeout_add(10000, timer_check_key_bindings, NULL);
498 #endif
499 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
501 screen_paint(mpd);
503 g_main_loop_run(main_loop);
505 /* cleanup */
507 g_main_loop_unref(main_loop);
508 g_io_channel_unref(keyboard_channel);
510 #ifdef ENABLE_LIRC
511 if (lirc_socket >= 0)
512 g_io_channel_unref(lirc_channel);
513 ncmpc_lirc_close();
514 #endif
516 exit_and_cleanup();
518 #ifdef ENABLE_LYRICS_SCREEN
519 lyrics_deinit();
520 #endif
522 ncu_deinit();
524 return 0;
525 }