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 ENABLE_LOCALE
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 #ifdef ENABLE_LOCALE
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 #ifdef ENABLE_LOCALE
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 #endif
388 #ifdef ENABLE_NLS
389 setlocale(LC_MESSAGES, "");
390 bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
391 #ifdef ENABLE_LOCALE
392 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
393 #endif
394 textdomain(GETTEXT_PACKAGE);
395 #endif
397 /* initialize options */
398 options_init();
400 /* parse command line options - 1 pass get configuration files */
401 options_parse(argc, argv);
403 #ifndef NCMPC_MINI
404 /* read configuration */
405 read_configuration();
407 /* check key bindings */
408 check_key_bindings(NULL, NULL, 0);
409 #endif
411 /* parse command line options - 2 pass */
412 options_parse(argc, argv);
414 /* setup signal behavior - SIGINT */
415 sigemptyset(&act.sa_mask);
416 act.sa_flags = 0;
417 act.sa_handler = catch_sigint;
418 if (sigaction(SIGINT, &act, NULL) < 0) {
419 perror("signal");
420 exit(EXIT_FAILURE);
421 }
423 /* setup signal behavior - SIGTERM */
425 act.sa_handler = catch_sigint;
426 if (sigaction(SIGTERM, &act, NULL) < 0) {
427 perror("sigaction()");
428 exit(EXIT_FAILURE);
429 }
431 /* setup signal behavior - SIGCONT */
433 act.sa_handler = catch_sigcont;
434 if (sigaction(SIGCONT, &act, NULL) < 0) {
435 perror("sigaction(SIGCONT)");
436 exit(EXIT_FAILURE);
437 }
439 /* setup signal behaviour - SIGHUP*/
441 act.sa_handler = catch_sigint;
442 if (sigaction(SIGHUP, &act, NULL) < 0) {
443 perror("sigaction(SIGHUP)");
444 exit(EXIT_FAILURE);
445 }
447 /* setup SIGWINCH */
449 act.sa_flags = SA_RESTART;
450 act.sa_handler = catch_sigwinch;
451 if (sigaction(SIGWINCH, &act, NULL) < 0) {
452 perror("sigaction(SIGWINCH)");
453 exit(EXIT_FAILURE);
454 }
456 /* ignore SIGPIPE */
458 act.sa_handler = SIG_IGN;
459 if (sigaction(SIGPIPE, &act, NULL) < 0) {
460 perror("sigaction(SIGPIPE)");
461 exit(EXIT_FAILURE);
462 }
464 ncu_init();
466 #ifdef ENABLE_LYRICS_SCREEN
467 lyrics_init();
468 #endif
470 /* create mpdclient instance */
471 mpd = mpdclient_new();
472 mpdclient_install_error_callback(mpd, error_callback);
474 /* initialize curses */
475 screen_init(mpd);
477 /* the main loop */
478 main_loop = g_main_loop_new(NULL, FALSE);
480 /* watch out for keyboard input */
481 keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
482 g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
484 #ifdef ENABLE_LIRC
485 /* watch out for lirc input */
486 lirc_socket = ncmpc_lirc_open();
487 if (lirc_socket >= 0) {
488 lirc_channel = g_io_channel_unix_new(lirc_socket);
489 g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL);
490 }
491 #endif
493 /* attempt to connect */
494 reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
496 update_source_id = g_timeout_add(update_interval,
497 timer_mpd_update,
498 GINT_TO_POINTER(TRUE));
499 #ifndef NCMPC_MINI
500 g_timeout_add(10000, timer_check_key_bindings, NULL);
501 #endif
502 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
504 screen_paint(mpd);
506 g_main_loop_run(main_loop);
508 /* cleanup */
510 g_main_loop_unref(main_loop);
511 g_io_channel_unref(keyboard_channel);
513 #ifdef ENABLE_LIRC
514 if (lirc_socket >= 0)
515 g_io_channel_unref(lirc_channel);
516 ncmpc_lirc_close();
517 #endif
519 exit_and_cleanup();
521 #ifdef ENABLE_LYRICS_SCREEN
522 lyrics_deinit();
523 #endif
525 ncu_deinit();
527 return 0;
528 }