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 }
115 }
117 static void
118 exit_and_cleanup(void)
119 {
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);
134 }
136 static void
137 catch_sigint(mpd_unused int sig)
138 {
139 g_main_loop_quit(main_loop);
140 }
143 static void
144 catch_sigcont(mpd_unused int sig)
145 {
146 screen_resize(mpd);
147 }
149 void
150 sigstop(void)
151 {
152 def_prog_mode(); /* save the tty modes */
153 endwin(); /* end curses mode temporarily */
154 kill(0, SIGSTOP); /* issue SIGSTOP */
155 }
157 static guint timer_sigwinch_id;
159 static gboolean
160 timer_sigwinch(mpd_unused gpointer data)
161 {
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;
171 }
173 static void
174 catch_sigwinch(mpd_unused int sig)
175 {
176 if (timer_sigwinch_id != 0)
177 g_source_remove(timer_sigwinch_id);
179 timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
180 }
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)
191 {
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;
237 }
239 static gboolean
240 timer_mpd_update(gpointer data)
241 {
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);
254 }
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)
262 {
263 screen_idle(mpd);
264 return TRUE;
265 }
267 static gboolean
268 keyboard_event(mpd_unused GIOChannel *source,
269 mpd_unused GIOCondition condition, mpd_unused gpointer data)
270 {
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;
297 }
299 #ifdef ENABLE_LIRC
300 static gboolean
301 lirc_event(mpd_unused GIOChannel *source,
302 mpd_unused GIOCondition condition, mpd_unused gpointer data)
303 {
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;
330 }
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)
339 {
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;
352 }
354 int
355 main(int argc, const char *argv[])
356 {
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;
508 }