6f662f53c76da970779a0908b504975623c9edca
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 "lyrics.h"
29 #include "screen.h"
30 #include "screen_utils.h"
31 #include "strfsong.h"
32 #include "gcc.h"
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <signal.h>
37 #include <string.h>
39 #define BUFSIZE 1024
41 static const guint idle_interval = 500;
43 static mpdclient_t *mpd = NULL;
44 static gboolean connected = FALSE;
45 static GMainLoop *main_loop;
46 static guint reconnect_source_id, idle_source_id, update_source_id;
48 static const gchar *
49 error_msg(const gchar *msg)
50 {
51 gchar *p;
53 if ((p = strchr(msg, '}')) == NULL)
54 return msg;
56 while (p && *p && (*p=='}' || *p==' '))
57 p++;
59 return p;
60 }
62 static void
63 error_callback(mpd_unused mpdclient_t *c, gint error, const gchar *msg)
64 {
65 error = error & 0xFF;
66 D("Error [%d:%d]> \"%s\"\n", error, GET_ACK_ERROR_CODE(error), msg);
67 switch (error) {
68 case MPD_ERROR_CONNPORT:
69 case MPD_ERROR_NORESPONSE:
70 break;
71 case MPD_ERROR_ACK:
72 screen_status_printf("%s", error_msg(msg));
73 screen_bell();
74 break;
75 default:
76 screen_status_printf("%s", msg);
77 screen_bell();
78 doupdate();
79 connected = FALSE;
80 }
81 }
83 static void
84 update_xterm_title(void)
85 {
86 static char title[BUFSIZE];
87 char tmp[BUFSIZE];
88 mpd_Status *status = NULL;
89 mpd_Song *song = NULL;
91 if (mpd) {
92 status = mpd->status;
93 song = mpd->song;
94 }
96 if (options.xterm_title_format && status && song &&
97 IS_PLAYING(status->state))
98 strfsong(tmp, BUFSIZE, options.xterm_title_format, song);
99 else
100 g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE);
102 if (strncmp(title, tmp, BUFSIZE)) {
103 g_strlcpy(title, tmp, BUFSIZE);
104 set_xterm_title("%s", title);
105 }
106 }
108 static void
109 exit_and_cleanup(void)
110 {
111 screen_exit();
112 set_xterm_title("");
113 printf("\n");
115 if (mpd) {
116 mpdclient_disconnect(mpd);
117 mpdclient_free(mpd);
118 }
120 g_free(options.host);
121 g_free(options.password);
122 g_free(options.list_format);
123 g_free(options.status_format);
124 g_free(options.scroll_sep);
125 }
127 static void
128 catch_sigint(mpd_unused int sig)
129 {
130 g_main_loop_quit(main_loop);
131 }
134 static void
135 catch_sigcont(mpd_unused int sig)
136 {
137 D("catch_sigcont()\n");
138 #ifdef ENABLE_RAW_MODE
139 reset_prog_mode(); /* restore tty modes */
140 refresh();
141 #endif
142 screen_resize();
143 }
145 void
146 sigstop(void)
147 {
148 def_prog_mode(); /* save the tty modes */
149 endwin(); /* end curses mode temporarily */
150 kill(0, SIGSTOP); /* issue SIGSTOP */
151 }
153 static guint timer_sigwinch_id;
155 static gboolean
156 timer_sigwinch(mpd_unused gpointer data)
157 {
158 /* the following causes the screen to flicker. There might be
159 better solutions, but I believe it isn't all that
160 important. */
162 endwin();
163 refresh();
164 screen_resize();
166 return FALSE;
167 }
169 static void
170 catch_sigwinch(mpd_unused int sig)
171 {
172 if (timer_sigwinch_id != 0)
173 g_source_remove(timer_sigwinch_id);
175 timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL);
176 }
178 #ifndef NDEBUG
179 void
180 D(const char *format, ...)
181 {
182 if( options.debug )
183 {
184 gchar *msg;
185 va_list ap;
187 va_start(ap,format);
188 msg = g_strdup_vprintf(format,ap);
189 va_end(ap);
190 fprintf(stderr, "%s", msg);
191 g_free(msg);
192 }
193 }
194 #endif
196 static gboolean
197 timer_mpd_update(gpointer data);
199 /**
200 * This timer is installed when the connection to the MPD server is
201 * broken. It tries to recover by reconnecting periodically.
202 */
203 static gboolean
204 timer_reconnect(mpd_unused gpointer data)
205 {
206 int ret;
208 if (connected)
209 return FALSE;
211 screen_status_printf(_("Connecting to %s... [Press %s to abort]"),
212 options.host, get_key_names(CMD_QUIT,0) );
213 doupdate();
215 mpdclient_disconnect(mpd);
216 ret = mpdclient_connect(mpd,
217 options.host, options.port,
218 1.5,
219 options.password);
220 if (ret != 0) {
221 /* try again in 5 seconds */
222 g_timeout_add(5000, timer_reconnect, NULL);
223 return FALSE;
224 }
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 (0.11.0 needed).\n"),
229 mpd->connection->version[0],
230 mpd->connection->version[1],
231 mpd->connection->version[2]);
232 mpdclient_disconnect(mpd);
233 doupdate();
235 /* try again after 30 seconds */
236 g_timeout_add(30000, timer_reconnect, NULL);
237 return FALSE;
238 }
240 screen_status_printf(_("Connected to %s!"), options.host);
241 doupdate();
243 connected = TRUE;
245 /* update immediately */
246 g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE));
248 reconnect_source_id = 0;
249 return FALSE;
251 }
253 static gboolean
254 timer_mpd_update(gpointer data)
255 {
256 if (connected)
257 mpdclient_update(mpd);
258 else if (reconnect_source_id == 0)
259 reconnect_source_id = g_timeout_add(1000, timer_reconnect,
260 NULL);
262 if (options.enable_xterm_title)
263 update_xterm_title();
265 screen_update(mpd);
267 return GPOINTER_TO_INT(data);
268 }
270 /**
271 * This idle timer is invoked when the user hasn't typed a key for
272 * 500ms. It is used for delayed seeking.
273 */
274 static gboolean
275 timer_idle(mpd_unused gpointer data)
276 {
277 screen_idle(mpd);
278 return TRUE;
279 }
281 static gboolean
282 keyboard_event(mpd_unused GIOChannel *source,
283 mpd_unused GIOCondition condition, mpd_unused gpointer data)
284 {
285 command_t cmd;
287 /* remove the idle timeout; add it later with fresh interval */
288 g_source_remove(idle_source_id);
290 if ((cmd=get_keyboard_command()) != CMD_NONE) {
291 if (cmd == CMD_QUIT) {
292 g_main_loop_quit(main_loop);
293 return FALSE;
294 }
296 screen_cmd(mpd, cmd);
298 if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) {
299 /* make sure we dont update the volume yet */
300 g_source_remove(update_source_id);
301 update_source_id = g_timeout_add((guint)(MPD_UPDATE_TIME * 1000),
302 timer_mpd_update,
303 GINT_TO_POINTER(TRUE));
304 }
305 }
307 screen_update(mpd);
309 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
310 return TRUE;
311 }
313 /**
314 * Check the configured key bindings for errors, and display a status
315 * message every 10 seconds.
316 */
317 static gboolean
318 timer_check_key_bindings(mpd_unused gpointer data)
319 {
320 char buf[256];
321 gboolean key_error;
323 key_error = check_key_bindings(NULL, buf, sizeof(buf));
324 if (!key_error)
325 /* no error: disable this timer for the rest of this
326 process */
327 return FALSE;
329 screen_status_printf("%s", buf);
330 doupdate();
331 return TRUE;
332 }
334 int
335 main(int argc, const char *argv[])
336 {
337 struct sigaction act;
338 const char *charset = NULL;
340 #ifdef HAVE_LOCALE_H
341 /* time and date formatting */
342 setlocale(LC_TIME,"");
343 /* care about sorting order etc */
344 setlocale(LC_COLLATE,"");
345 /* charset */
346 setlocale(LC_CTYPE,"");
347 /* initialize charset conversions */
348 charset_init(g_get_charset(&charset));
349 D("charset: %s\n", charset);
350 #endif
352 /* initialize i18n support */
353 #ifdef ENABLE_NLS
354 setlocale(LC_MESSAGES, "");
355 bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR);
356 bind_textdomain_codeset(GETTEXT_PACKAGE, charset);
357 textdomain(GETTEXT_PACKAGE);
358 #endif
360 /* initialize options */
361 options_init();
363 /* parse command line options - 1 pass get configuration files */
364 options_parse(argc, argv);
366 /* read configuration */
367 read_configuration(&options);
369 /* check key bindings */
370 check_key_bindings(NULL, NULL, 0);
372 /* parse command line options - 2 pass */
373 options_parse(argc, argv);
375 /* setup signal behavior - SIGINT */
376 sigemptyset(&act.sa_mask);
377 act.sa_flags = 0;
378 act.sa_handler = catch_sigint;
379 if (sigaction(SIGINT, &act, NULL) < 0) {
380 perror("signal");
381 exit(EXIT_FAILURE);
382 }
384 /* setup signal behavior - SIGTERM */
385 sigemptyset(&act.sa_mask);
386 act.sa_flags = 0;
387 act.sa_handler = catch_sigint;
388 if (sigaction(SIGTERM, &act, NULL) < 0) {
389 perror("sigaction()");
390 exit(EXIT_FAILURE);
391 }
393 /* setup signal behavior - SIGCONT */
394 sigemptyset(&act.sa_mask);
395 act.sa_flags = 0;
396 act.sa_handler = catch_sigcont;
397 if (sigaction(SIGCONT, &act, NULL) < 0) {
398 perror("sigaction(SIGCONT)");
399 exit(EXIT_FAILURE);
400 }
402 /* setup signal behaviour - SIGHUP*/
403 sigemptyset(&act.sa_mask);
404 act.sa_flags = 0;
405 act.sa_handler = catch_sigint;
406 if (sigaction(SIGHUP, &act, NULL) < 0) {
407 perror("sigaction(SIGHUP)");
408 exit(EXIT_FAILURE);
409 }
411 /* setup SIGWINCH */
413 act.sa_handler = catch_sigwinch;
414 if (sigaction(SIGWINCH, &act, NULL) < 0) {
415 perror("sigaction(SIGWINCH)");
416 exit(EXIT_FAILURE);
417 }
419 ncurses_init();
421 lyrics_init();
423 /* create mpdclient instance */
424 mpd = mpdclient_new();
425 mpdclient_install_error_callback(mpd, error_callback);
427 /* initialize curses */
428 screen_init(mpd);
430 /* the main loop */
431 main_loop = g_main_loop_new(NULL, FALSE);
433 /* watch out for keyboard input */
434 g_io_add_watch(g_io_channel_unix_new(STDIN_FILENO), G_IO_IN,
435 keyboard_event, NULL);
437 /* attempt to connect */
438 reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL);
440 update_source_id = g_timeout_add((guint)(MPD_UPDATE_TIME * 1000),
441 timer_mpd_update,
442 GINT_TO_POINTER(TRUE));
443 g_timeout_add(10000, timer_check_key_bindings, NULL);
444 idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL);
446 g_main_loop_run(main_loop);
447 g_main_loop_unref(main_loop);
449 exit_and_cleanup();
450 }