1 /*
2 * $Id$
3 *
4 * (c) 2004 by Kalle Wallin <kaw@linux.se>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 */
21 #include "config.h"
22 #include "ncmpc.h"
23 #include "mpdclient.h"
24 #include "support.h"
25 #include "options.h"
26 #include "conf.h"
27 #include "command.h"
28 #include "ncu.h"
29 #include "screen.h"
30 #include "screen_utils.h"
31 #include "strfsong.h"
32 #include "gcc.h"
34 #ifdef ENABLE_LYRICS_SCREEN
35 #include "lyrics.h"
36 #endif
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <signal.h>
41 #include <string.h>
43 #define BUFSIZE 1024
45 static const guint idle_interval = 500;
47 static mpdclient_t *mpd = NULL;
48 static gboolean connected = FALSE;
49 static GMainLoop *main_loop;
50 static guint reconnect_source_id, idle_source_id, update_source_id;
52 static const gchar *
53 error_msg(const gchar *msg)
54 {
55 gchar *p;
57 if ((p = strchr(msg, '}')) == NULL)
58 return msg;
60 while (p && *p && (*p=='}' || *p==' '))
61 p++;
63 return p;
64 }
66 static void
67 error_callback(mpd_unused mpdclient_t *c, gint error, const gchar *msg)
68 {
69 error = error & 0xFF;
70 switch (error) {
71 case MPD_ERROR_CONNPORT:
72 case MPD_ERROR_NORESPONSE:
73 break;
74 case MPD_ERROR_ACK:
75 screen_status_printf("%s", error_msg(msg));
76 screen_bell();
77 break;
78 default:
79 screen_status_printf("%s", msg);
80 screen_bell();
81 doupdate();
82 connected = FALSE;
83 }
84 }
86 static void
87 update_xterm_title(void)
88 {
89 static char title[BUFSIZE];
90 char tmp[BUFSIZE];
91 mpd_Status *status = NULL;
92 mpd_Song *song = NULL;
94 if (mpd) {
95 status = mpd->status;
96 song = mpd->song;
97 }
99 if (options.xterm_title_format && status && song &&
100 IS_PLAYING(status->state))
101 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
102 else
103 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
105 if (strncmp(title, tmp, BUFSIZE)) {
106 g_strlcpy(title, tmp, BUFSIZE);
107 set_xterm_title("%s", title);
108 }
109 }
111 static void
112 exit_and_cleanup(void)
113 {
114 screen_exit();
115 set_xterm_title("");
116 printf("\n");
118 if (mpd) {
119 mpdclient_disconnect(mpd);
120 mpdclient_free(mpd);
121 }
123 g_free(options.host);
124 g_free(options.password);
125 g_free(options.list_format);
126 g_free(options.status_format);
127 g_free(options.scroll_sep);
128 }
130 static void
131 catch_sigint(mpd_unused int sig)
132 {
133 g_main_loop_quit(main_loop);
134 }
137 static void
138 catch_sigcont(mpd_unused int sig)
139 {
140 #ifdef ENABLE_RAW_MODE
141 reset_prog_mode(); /* restore tty modes */
142 refresh();
143 #endif
144 screen_resize();
145 }
147 void
148 sigstop(void)
149 {
150 def_prog_mode(); /* save the tty modes */
151 endwin(); /* end curses mode temporarily */
152 kill(0, SIGSTOP); /* issue SIGSTOP */
153 }
155 static guint timer_sigwinch_id;
157 static gboolean
158 timer_sigwinch(mpd_unused gpointer data)
159 {
160 /* the following causes the screen to flicker. There might be
161 better solutions, but I believe it isn't all that
162 important. */
164 endwin();
165 refresh();
166 screen_resize();
168 return FALSE;
169 }
171 static void
172 catch_sigwinch(mpd_unused int sig)
173 {
174 if (timer_sigwinch_id != 0)
175 g_source_remove(timer_sigwinch_id);
177 timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
178 }
180 static gboolean
181 timer_mpd_update(gpointer data);
183 /**
184 * This timer is installed when the connection to the MPD server is
185 * broken. It tries to recover by reconnecting periodically.
186 */
187 static gboolean
188 timer_reconnect(mpd_unused gpointer data)
189 {
190 int ret;
192 if (connected)
193 return FALSE;
195 screen_status_printf(_("Connecting to %s... [Press %s to abort]"),
196 options.host, get_key_names(CMD_QUIT,0) );
197 doupdate();
199 mpdclient_disconnect(mpd);
200 ret = mpdclient_connect(mpd,
201 options.host, options.port,
202 1.5,
203 options.password);
204 if (ret != 0) {
205 /* try again in 5 seconds */
206 g_timeout_add(5000, timer_reconnect, NULL);
207 return FALSE;
208 }
210 /* quit if mpd is pre 0.11.0 - song id not supported by mpd */
211 if (MPD_VERSION_LT(mpd, 0, 11, 0)) {
212 screen_status_printf(_("Error: MPD version %d.%d.%d is to old (0.11.0 needed).\n"),
213 mpd->connection->version[0],
214 mpd->connection->version[1],
215 mpd->connection->version[2]);
216 mpdclient_disconnect(mpd);
217 doupdate();
219 /* try again after 30 seconds */
220 g_timeout_add(30000, timer_reconnect, NULL);
221 return FALSE;
222 }
224 screen_status_printf(_("Connected to %s!"), options.host);
225 doupdate();
227 connected = TRUE;
229 /* update immediately */
230 g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE));
232 reconnect_source_id = 0;
233 return FALSE;
235 }
237 static gboolean
238 timer_mpd_update(gpointer data)
239 {
240 if (connected)
241 mpdclient_update(mpd);
242 else if (reconnect_source_id == 0)
243 reconnect_source_id = g_timeout_add(1000, timer_reconnect,
244 NULL);
246 if (options.enable_xterm_title)
247 update_xterm_title();
249 screen_update(mpd);
251 return GPOINTER_TO_INT(data);
252 }
254 /**
255 * This idle timer is invoked when the user hasn't typed a key for
256 * 500ms. It is used for delayed seeking.
257 */
258 static gboolean
259 timer_idle(mpd_unused gpointer data)
260 {
261 screen_idle(mpd);
262 return TRUE;
263 }
265 static gboolean
266 keyboard_event(mpd_unused GIOChannel *source,
267 mpd_unused GIOCondition condition, mpd_unused gpointer data)
268 {
269 command_t cmd;
271 /* remove the idle timeout; add it later with fresh interval */
272 g_source_remove(idle_source_id);
274 if ((cmd=get_keyboard_command()) != CMD_NONE) {
275 if (cmd == CMD_QUIT) {
276 g_main_loop_quit(main_loop);
277 return FALSE;
278 }
280 screen_cmd(mpd, cmd);
282 if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
283 /* make sure we dont update the volume yet */
284 g_source_remove(update_source_id);
285 update_source_id = g_timeout_add((guint)(MPD_UPDATE_TIME * 1000),
286 timer_mpd_update,
287 GINT_TO_POINTER(TRUE));
288 }
289 }
291 screen_update(mpd);
293 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
294 return TRUE;
295 }
297 /**
298 * Check the configured key bindings for errors, and display a status
299 * message every 10 seconds.
300 */
301 static gboolean
302 timer_check_key_bindings(mpd_unused gpointer data)
303 {
304 char buf[256];
305 gboolean key_error;
307 key_error = check_key_bindings(NULL, buf, sizeof(buf));
308 if (!key_error)
309 /* no error: disable this timer for the rest of this
310 process */
311 return FALSE;
313 screen_status_printf("%s", buf);
314 doupdate();
315 return TRUE;
316 }
318 int
319 main(int argc, const char *argv[])
320 {
321 struct sigaction act;
322 const char *charset = NULL;
323 GIOChannel *keyboard_channel;
325 #ifdef HAVE_LOCALE_H
326 /* time and date formatting */
327 setlocale(LC_TIME,"");
328 /* care about sorting order etc */
329 setlocale(LC_COLLATE,"");
330 /* charset */
331 setlocale(LC_CTYPE,"");
332 /* initialize charset conversions */
333 charset_init(g_get_charset(&charset));
334 #endif
336 /* initialize i18n support */
337 #ifdef ENABLE_NLS
338 setlocale(LC_MESSAGES, "");
339 bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
340 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
341 textdomain(GETTEXT_PACKAGE);
342 #endif
344 /* initialize options */
345 options_init();
347 /* parse command line options - 1 pass get configuration files */
348 options_parse(argc, argv);
350 /* read configuration */
351 read_configuration(&options);
353 /* check key bindings */
354 check_key_bindings(NULL, NULL, 0);
356 /* parse command line options - 2 pass */
357 options_parse(argc, argv);
359 /* setup signal behavior - SIGINT */
360 sigemptyset(&act.sa_mask);
361 act.sa_flags = 0;
362 act.sa_handler = catch_sigint;
363 if (sigaction(SIGINT, &act, NULL) < 0) {
364 perror("signal");
365 exit(EXIT_FAILURE);
366 }
368 /* setup signal behavior - SIGTERM */
369 sigemptyset(&act.sa_mask);
370 act.sa_flags = 0;
371 act.sa_handler = catch_sigint;
372 if (sigaction(SIGTERM, &act, NULL) < 0) {
373 perror("sigaction()");
374 exit(EXIT_FAILURE);
375 }
377 /* setup signal behavior - SIGCONT */
378 sigemptyset(&act.sa_mask);
379 act.sa_flags = 0;
380 act.sa_handler = catch_sigcont;
381 if (sigaction(SIGCONT, &act, NULL) < 0) {
382 perror("sigaction(SIGCONT)");
383 exit(EXIT_FAILURE);
384 }
386 /* setup signal behaviour - SIGHUP*/
387 sigemptyset(&act.sa_mask);
388 act.sa_flags = 0;
389 act.sa_handler = catch_sigint;
390 if (sigaction(SIGHUP, &act, NULL) < 0) {
391 perror("sigaction(SIGHUP)");
392 exit(EXIT_FAILURE);
393 }
395 /* setup SIGWINCH */
397 act.sa_handler = catch_sigwinch;
398 if (sigaction(SIGWINCH, &act, NULL) < 0) {
399 perror("sigaction(SIGWINCH)");
400 exit(EXIT_FAILURE);
401 }
403 /* ignore SIGPIPE */
405 act.sa_flags = SA_RESTART;
406 act.sa_handler = SIG_IGN;
407 if (sigaction(SIGWINCH, &act, NULL) < 0) {
408 perror("sigaction(SIGWINCH)");
409 exit(EXIT_FAILURE);
410 }
412 ncu_init();
414 #ifdef ENABLE_LYRICS_SCREEN
415 lyrics_init();
416 #endif
418 /* create mpdclient instance */
419 mpd = mpdclient_new();
420 mpdclient_install_error_callback(mpd, error_callback);
422 /* initialize curses */
423 screen_init(mpd);
425 /* the main loop */
426 main_loop = g_main_loop_new(NULL, FALSE);
428 /* watch out for keyboard input */
429 keyboard_channel = g_io_channel_unix_new(STDIN_FILENO);
430 g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL);
432 /* attempt to connect */
433 reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
435 update_source_id = g_timeout_add((guint)(MPD_UPDATE_TIME * 1000),
436 timer_mpd_update,
437 GINT_TO_POINTER(TRUE));
438 g_timeout_add(10000, timer_check_key_bindings, NULL);
439 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
441 g_main_loop_run(main_loop);
443 /* cleanup */
445 g_main_loop_unref(main_loop);
446 g_io_channel_unref(keyboard_channel);
448 exit_and_cleanup();
449 ncu_deinit();
451 return 0;
452 }