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 /* time between mpd updates [s] */
49 static const guint update_interval = 500;
51 #define BUFSIZE 1024
53 static const guint idle_interval = 500;
55 static mpdclient_t *mpd = NULL;
56 static gboolean connected = FALSE;
57 static GMainLoop *main_loop;
58 static guint reconnect_source_id, idle_source_id, update_source_id;
60 static const gchar *
61 error_msg(const gchar *msg)
62 {
63 gchar *p;
65 if ((p = strchr(msg, '}')) == NULL)
66 return msg;
68 do {
69 p++;
70 } while (*p == '}' || * p== ' ');
72 return p;
73 }
75 static void
76 error_callback(G_GNUC_UNUSED mpdclient_t *c, gint error, const gchar *_msg)
77 {
78 char *msg = utf8_to_locale(_msg);
80 error = error & 0xFF;
81 switch (error) {
82 case MPD_ERROR_CONNPORT:
83 case MPD_ERROR_NORESPONSE:
84 break;
85 case MPD_ERROR_ACK:
86 screen_status_printf("%s", error_msg(msg));
87 screen_bell();
88 break;
89 default:
90 screen_status_printf("%s", msg);
91 screen_bell();
92 doupdate();
93 connected = FALSE;
94 }
96 g_free(msg);
97 }
99 #ifndef NCMPC_MINI
100 static void
101 update_xterm_title(void)
102 {
103 static char title[BUFSIZE];
104 char tmp[BUFSIZE];
105 mpd_Status *status = NULL;
106 mpd_Song *song = NULL;
108 if (mpd) {
109 status = mpd->status;
110 song = mpd->song;
111 }
113 if (options.xterm_title_format && status && song &&
114 IS_PLAYING(status->state))
115 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
116 else
117 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
119 if (strncmp(title, tmp, BUFSIZE)) {
120 g_strlcpy(title, tmp, BUFSIZE);
121 set_xterm_title("%s", title);
122 }
123 }
124 #endif
126 static void
127 exit_and_cleanup(void)
128 {
129 screen_exit();
130 #ifndef NCMPC_MINI
131 set_xterm_title("");
132 #endif
133 printf("\n");
135 if (mpd) {
136 mpdclient_disconnect(mpd);
137 mpdclient_free(mpd);
138 }
140 g_free(options.host);
141 g_free(options.password);
142 g_free(options.list_format);
143 g_free(options.status_format);
144 #ifndef NCMPC_MINI
145 g_free(options.scroll_sep);
146 #endif
147 }
149 static void
150 catch_sigint(G_GNUC_UNUSED int sig)
151 {
152 g_main_loop_quit(main_loop);
153 }
156 static void
157 catch_sigcont(G_GNUC_UNUSED int sig)
158 {
159 screen_resize(mpd);
160 }
162 void
163 sigstop(void)
164 {
165 def_prog_mode(); /* save the tty modes */
166 endwin(); /* end curses mode temporarily */
167 kill(0, SIGSTOP); /* issue SIGSTOP */
168 }
170 static guint timer_sigwinch_id;
172 static gboolean
173 timer_sigwinch(G_GNUC_UNUSED gpointer data)
174 {
175 /* the following causes the screen to flicker. There might be
176 better solutions, but I believe it isn't all that
177 important. */
179 endwin();
180 refresh();
181 screen_resize(mpd);
183 return FALSE;
184 }
186 static void
187 catch_sigwinch(G_GNUC_UNUSED int sig)
188 {
189 if (timer_sigwinch_id != 0)
190 g_source_remove(timer_sigwinch_id);
192 timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
193 }
195 static gboolean
196 timer_mpd_update(gpointer data);
198 /**
199 * This timer is installed when the connection to the MPD server is
200 * broken. It tries to recover by reconnecting periodically.
201 */
202 static gboolean
203 timer_reconnect(G_GNUC_UNUSED gpointer data)
204 {
205 int ret;
207 if (connected)
208 return FALSE;
210 screen_status_printf(_("Connecting to %s... [Press %s to abort]"),
211 options.host, get_key_names(CMD_QUIT,0) );
212 doupdate();
214 mpdclient_disconnect(mpd);
215 ret = mpdclient_connect(mpd,
216 options.host, options.port,
217 1.5,
218 options.password);
219 if (ret != 0) {
220 /* try again in 5 seconds */
221 g_timeout_add(5000, timer_reconnect, NULL);
222 return FALSE;
223 }
225 #ifndef NCMPC_MINI
226 /* quit if mpd is pre 0.11.0 - song id not supported by mpd */
227 if (MPD_VERSION_LT(mpd, 0, 11, 0)) {
228 screen_status_printf(_("Error: MPD version %d.%d.%d is to old (%s needed)"),
229 mpd->connection->version[0],
230 mpd->connection->version[1],
231 mpd->connection->version[2],
232 "0.11.0");
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(G_GNUC_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(G_GNUC_UNUSED GIOChannel *source,
320 G_GNUC_UNUSED GIOCondition condition,
321 G_GNUC_UNUSED gpointer data)
322 {
323 command_t cmd;
325 begin_input_event();
327 if ((cmd=get_keyboard_command()) != CMD_NONE)
328 if (do_input_event(cmd) != 0)
329 return FALSE;
331 end_input_event();
332 return TRUE;
333 }
335 #ifndef NCMPC_MINI
336 /**
337 * Check the configured key bindings for errors, and display a status
338 * message every 10 seconds.
339 */
340 static gboolean
341 timer_check_key_bindings(G_GNUC_UNUSED gpointer data)
342 {
343 char buf[256];
344 gboolean key_error;
346 key_error = check_key_bindings(NULL, buf, sizeof(buf));
347 if (!key_error)
348 /* no error: disable this timer for the rest of this
349 process */
350 return FALSE;
352 screen_status_printf("%s", buf);
353 doupdate();
354 return TRUE;
355 }
356 #endif
358 int
359 main(int argc, const char *argv[])
360 {
361 struct sigaction act;
362 #if defined(HAVE_LOCALE_H) && !defined(NCMPC_MINI)
363 const char *charset = NULL;
364 #endif
365 GIOChannel *keyboard_channel;
366 #ifdef ENABLE_LIRC
367 int lirc_socket;
368 GIOChannel *lirc_channel = NULL;
369 #endif
371 #if defined(HAVE_LOCALE_H) && !defined(NCMPC_MINI)
372 /* time and date formatting */
373 setlocale(LC_TIME,"");
374 /* care about sorting order etc */
375 setlocale(LC_COLLATE,"");
376 /* charset */
377 setlocale(LC_CTYPE,"");
378 /* initialize charset conversions */
379 charset = charset_init();
381 /* initialize i18n support */
382 #ifdef ENABLE_NLS
383 setlocale(LC_MESSAGES, "");
384 bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
385 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
386 textdomain(GETTEXT_PACKAGE);
387 #endif
388 #endif
390 /* initialize options */
391 options_init();
393 /* parse command line options - 1 pass get configuration files */
394 options_parse(argc, argv);
396 #ifndef NCMPC_MINI
397 /* read configuration */
398 read_configuration();
400 /* check key bindings */
401 check_key_bindings(NULL, NULL, 0);
402 #endif
404 /* parse command line options - 2 pass */
405 options_parse(argc, argv);
407 /* setup signal behavior - SIGINT */
408 sigemptyset(&act.sa_mask);
409 act.sa_flags = 0;
410 act.sa_handler = catch_sigint;
411 if (sigaction(SIGINT, &act, NULL) < 0) {
412 perror("signal");
413 exit(EXIT_FAILURE);
414 }
416 /* setup signal behavior - SIGTERM */
418 act.sa_handler = catch_sigint;
419 if (sigaction(SIGTERM, &act, NULL) < 0) {
420 perror("sigaction()");
421 exit(EXIT_FAILURE);
422 }
424 /* setup signal behavior - SIGCONT */
426 act.sa_handler = catch_sigcont;
427 if (sigaction(SIGCONT, &act, NULL) < 0) {
428 perror("sigaction(SIGCONT)");
429 exit(EXIT_FAILURE);
430 }
432 /* setup signal behaviour - SIGHUP*/
434 act.sa_handler = catch_sigint;
435 if (sigaction(SIGHUP, &act, NULL) < 0) {
436 perror("sigaction(SIGHUP)");
437 exit(EXIT_FAILURE);
438 }
440 /* setup SIGWINCH */
442 act.sa_flags = SA_RESTART;
443 act.sa_handler = catch_sigwinch;
444 if (sigaction(SIGWINCH, &act, NULL) < 0) {
445 perror("sigaction(SIGWINCH)");
446 exit(EXIT_FAILURE);
447 }
449 /* ignore SIGPIPE */
451 act.sa_handler = SIG_IGN;
452 if (sigaction(SIGPIPE, &act, NULL) < 0) {
453 perror("sigaction(SIGPIPE)");
454 exit(EXIT_FAILURE);
455 }
457 ncu_init();
459 #ifdef ENABLE_LYRICS_SCREEN
460 lyrics_init();
461 #endif
463 /* create mpdclient instance */
464 mpd = mpdclient_new();
465 mpdclient_install_error_callback(mpd, error_callback);
467 /* initialize curses */
468 screen_init(mpd);
470 /* the main loop */
471 main_loop = g_main_loop_new(NULL, FALSE);
473 /* watch out for keyboard input */
474 keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
475 g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
477 #ifdef ENABLE_LIRC
478 /* watch out for lirc input */
479 lirc_socket = ncmpc_lirc_open();
480 if (lirc_socket >= 0) {
481 lirc_channel = g_io_channel_unix_new(lirc_socket);
482 g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL);
483 }
484 #endif
486 /* attempt to connect */
487 reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
489 update_source_id = g_timeout_add(update_interval,
490 timer_mpd_update,
491 GINT_TO_POINTER(TRUE));
492 #ifndef NCMPC_MINI
493 g_timeout_add(10000, timer_check_key_bindings, NULL);
494 #endif
495 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
497 screen_paint(mpd);
499 g_main_loop_run(main_loop);
501 /* cleanup */
503 g_main_loop_unref(main_loop);
504 g_io_channel_unref(keyboard_channel);
506 #ifdef ENABLE_LIRC
507 if (lirc_socket >= 0)
508 g_io_channel_unref(lirc_channel);
509 ncmpc_lirc_close();
510 #endif
512 exit_and_cleanup();
514 #ifdef ENABLE_LYRICS_SCREEN
515 lyrics_deinit();
516 #endif
518 ncu_deinit();
520 return 0;
521 }