X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fmain.c;h=2edf2d4ecc0d3625bb67263a384724f185d19934;hb=607bf2d55826f861d12b774884cb201bc683051c;hp=abe60bf152ecd9951d23fc2bdb781fd0ed0a6d01;hpb=1c515ff7791fd186dc82e80134ac47273c43d442;p=ncmpc.git diff --git a/src/main.c b/src/main.c index abe60bf..2edf2d4 100644 --- a/src/main.c +++ b/src/main.c @@ -1,311 +1,545 @@ -/* - * $Id$ - * - * (c) 2004 by Kalle Wallin - * +/* ncmpc (Ncurses MPD Client) + * (c) 2004-2009 The Music Player Daemon Project + * Project homepage: http://musicpd.org + * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. - * + * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ -#include -#include -#include -#include -#include -#include + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ #include "config.h" #include "ncmpc.h" #include "mpdclient.h" -#include "support.h" +#include "charset.h" #include "options.h" -#include "conf.h" #include "command.h" +#include "ncu.h" #include "screen.h" #include "screen_utils.h" #include "strfsong.h" +#include "i18n.h" + +#ifndef NCMPC_MINI +#include "conf.h" +#endif + +#ifdef ENABLE_LYRICS_SCREEN +#include "lyrics.h" +#endif + +#ifdef ENABLE_LIRC +#include "lirc.h" +#endif + +#include +#include +#include +#include + +#ifdef ENABLE_LOCALE +#include +#endif + +/* time between mpd updates [s] */ +static const guint update_interval = 500; #define BUFSIZE 1024 -static mpdclient_t *mpd = NULL; +static const guint idle_interval = 500; + +static mpdclient_t *mpd = NULL; static gboolean connected = FALSE; -static GTimer *timer = NULL; +static GMainLoop *main_loop; +static guint reconnect_source_id, idle_source_id, update_source_id; -static gchar * -error_msg(gchar *msg) +static const gchar * +error_msg(const gchar *msg) { - gchar *p; + gchar *p; - if( (p=strchr(msg, '}' )) == NULL ) - return msg; - while( p && *p && (*p=='}' || *p==' ') ) - p++; + if ((p = strchr(msg, '}')) == NULL) + return msg; - return p; + do { + p++; + } while (*p == '}' || * p== ' '); + + return p; } static void -error_callback(mpdclient_t *c, gint error, gchar *msg) +error_callback(G_GNUC_UNUSED mpdclient_t *c, gint error, const gchar *_msg) { - gint code = GET_ACK_ERROR_CODE(error); - - error = error & 0xFF; - D("Error [%d:%d]> \"%s\"\n", error, code, msg); - switch(error) - { - case MPD_ERROR_CONNPORT: - case MPD_ERROR_NORESPONSE: - break; - case MPD_ERROR_ACK: - screen_status_printf("%s", error_msg(msg)); - screen_bell(); - break; - default: - screen_status_printf("%s", msg); - screen_bell(); - doupdate(); - connected = FALSE; - } + char *msg = utf8_to_locale(_msg); + + error = error & 0xFF; + switch (error) { + case MPD_ERROR_CONNPORT: + case MPD_ERROR_NORESPONSE: + break; + case MPD_ERROR_ACK: + screen_status_printf("%s", error_msg(msg)); + screen_bell(); + break; + default: + screen_status_printf("%s", msg); + screen_bell(); + doupdate(); + connected = FALSE; + } + + g_free(msg); } +#ifndef NCMPC_MINI static void update_xterm_title(void) { - static char title[BUFSIZE]; - char tmp[BUFSIZE]; - mpd_Status *status = NULL; - mpd_Song *song = NULL; - - if( mpd ) - { - status = mpd->status; - song = mpd->song; - } - - if(options.xterm_title_format && status && song && IS_PLAYING(status->state)) - { - strfsong(tmp, BUFSIZE, options.xterm_title_format, song); - } - else - strncpy(tmp, PACKAGE " version " VERSION, BUFSIZE); - - if( strcmp(title,tmp) ) - { - strncpy(title, tmp, BUFSIZE); - set_xterm_title(title); - } + static char title[BUFSIZE]; + char tmp[BUFSIZE]; + mpd_Status *status = NULL; + mpd_Song *song = NULL; + + if (mpd) { + status = mpd->status; + song = mpd->song; + } + + if (options.xterm_title_format && status && song && + IS_PLAYING(status->state)) + strfsong(tmp, BUFSIZE, options.xterm_title_format, song); + else + g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE); + + if (strncmp(title, tmp, BUFSIZE)) { + g_strlcpy(title, tmp, BUFSIZE); + set_xterm_title("%s", title); + } } +#endif -void +static void exit_and_cleanup(void) { - screen_exit(); - set_xterm_title(""); - printf("\n"); - if( mpd ) - { - mpdclient_disconnect(mpd); - mpd = mpdclient_free(mpd); - } - g_free(options.host); - g_free(options.password); - g_free(options.list_format); - g_free(options.status_format); - if( timer ) - g_timer_destroy(timer); + screen_exit(); +#ifndef NCMPC_MINI + set_xterm_title(""); +#endif + printf("\n"); + + if (mpd) { + mpdclient_disconnect(mpd); + mpdclient_free(mpd); + } + + g_free(options.host); + g_free(options.password); + g_free(options.list_format); + g_free(options.status_format); +#ifndef NCMPC_MINI + g_free(options.scroll_sep); +#endif +} + +static void +catch_sigint(G_GNUC_UNUSED int sig) +{ + g_main_loop_quit(main_loop); +} + + +static void +catch_sigcont(G_GNUC_UNUSED int sig) +{ + screen_resize(mpd); } void -catch_sigint( int sig ) +sigstop(void) +{ + def_prog_mode(); /* save the tty modes */ + endwin(); /* end curses mode temporarily */ + kill(0, SIGSTOP); /* issue SIGSTOP */ +} + +static guint timer_sigwinch_id; + +static gboolean +timer_sigwinch(G_GNUC_UNUSED gpointer data) { - printf("\n%s\n", _("Exiting...")); - exit(EXIT_SUCCESS); + /* the following causes the screen to flicker. There might be + better solutions, but I believe it isn't all that + important. */ + + endwin(); + refresh(); + screen_resize(mpd); + + return FALSE; } -#ifdef DEBUG -void -D(char *format, ...) +static void +catch_sigwinch(G_GNUC_UNUSED int sig) { - if( options.debug ) - { - gchar *msg; - va_list ap; - - va_start(ap,format); - msg = g_strdup_vprintf(format,ap); - va_end(ap); - fprintf(stderr, "%s", msg); - g_free(msg); - } + if (timer_sigwinch_id != 0) + g_source_remove(timer_sigwinch_id); + + timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL); } + +static gboolean +timer_mpd_update(gpointer data); + +/** + * This timer is installed when the connection to the MPD server is + * broken. It tries to recover by reconnecting periodically. + */ +static gboolean +timer_reconnect(G_GNUC_UNUSED gpointer data) +{ + int ret; + + if (connected) + return FALSE; + + screen_status_printf(_("Connecting to %s... [Press %s to abort]"), + options.host, get_key_names(CMD_QUIT,0) ); + doupdate(); + + mpdclient_disconnect(mpd); + ret = mpdclient_connect(mpd, + options.host, options.port, + 1.5, + options.password); + if (ret != 0) { + /* try again in 5 seconds */ + g_timeout_add(5000, timer_reconnect, NULL); + return FALSE; + } + +#ifndef NCMPC_MINI + /* quit if mpd is pre 0.11.0 - song id not supported by mpd */ + if (MPD_VERSION_LT(mpd, 0, 11, 0)) { + screen_status_printf(_("Error: MPD version %d.%d.%d is to old (%s needed)"), + mpd->connection->version[0], + mpd->connection->version[1], + mpd->connection->version[2], + "0.11.0"); + mpdclient_disconnect(mpd); + doupdate(); + + /* try again after 30 seconds */ + g_timeout_add(30000, timer_reconnect, NULL); + return FALSE; + } #endif -int + screen_status_printf(_("Connected to %s"), options.host); + doupdate(); + + connected = TRUE; + + /* update immediately */ + g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE)); + + reconnect_source_id = 0; + return FALSE; + +} + +static gboolean +timer_mpd_update(gpointer data) +{ + if (connected) + mpdclient_update(mpd); + else if (reconnect_source_id == 0) + reconnect_source_id = g_timeout_add(1000, timer_reconnect, + NULL); + +#ifndef NCMPC_MINI + if (options.enable_xterm_title) + update_xterm_title(); +#endif + + screen_update(mpd); + + return GPOINTER_TO_INT(data); +} + +/** + * This idle timer is invoked when the user hasn't typed a key for + * 500ms. It is used for delayed seeking. + */ +static gboolean +timer_idle(G_GNUC_UNUSED gpointer data) +{ + screen_idle(mpd); + return TRUE; +} + +void begin_input_event(void) +{ + /* remove the idle timeout; add it later with fresh interval */ + g_source_remove(idle_source_id); +} + +void end_input_event(void) +{ + screen_update(mpd); + + idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL); +} + +int do_input_event(command_t cmd) +{ + if (cmd == CMD_QUIT) { + g_main_loop_quit(main_loop); + return -1; + } + + screen_cmd(mpd, cmd); + + if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) { + /* make sure we dont update the volume yet */ + g_source_remove(update_source_id); + update_source_id = g_timeout_add(update_interval, + timer_mpd_update, + GINT_TO_POINTER(TRUE)); + } + + return 0; +} + +static gboolean +keyboard_event(G_GNUC_UNUSED GIOChannel *source, + G_GNUC_UNUSED GIOCondition condition, + G_GNUC_UNUSED gpointer data) +{ + command_t cmd; + + begin_input_event(); + + if ((cmd=get_keyboard_command()) != CMD_NONE) + if (do_input_event(cmd) != 0) + return FALSE; + + end_input_event(); + return TRUE; +} + +#ifndef NCMPC_MINI +/** + * Check the configured key bindings for errors, and display a status + * message every 10 seconds. + */ +static gboolean +timer_check_key_bindings(G_GNUC_UNUSED gpointer data) +{ + char buf[256]; +#ifdef ENABLE_KEYDEF_SCREEN + char comment[64]; +#endif + gboolean key_error; + + key_error = check_key_bindings(NULL, buf, sizeof(buf)); + if (!key_error) + /* no error: disable this timer for the rest of this + process */ + return FALSE; + +#ifdef ENABLE_KEYDEF_SCREEN + g_strchomp(buf); + g_strlcat(buf, " (", sizeof(buf)); + /* to translators: a key was bound twice in the key editor, + and this is a hint for the user what to press to correct + that */ + g_snprintf(comment, sizeof(comment), _("press %s for the key editor"), + get_key_names(CMD_SCREEN_KEYDEF, 0)); + g_strlcat(buf, comment, sizeof(buf)); + g_strlcat(buf, ")", sizeof(buf)); +#endif + + screen_status_printf("%s", buf); + + doupdate(); + return TRUE; +} +#endif + +int main(int argc, const char *argv[]) { - options_t *options; - struct sigaction act; - const char *charset = NULL; - gboolean key_error; - -#ifdef HAVE_LOCALE_H - /* time and date formatting */ - setlocale(LC_TIME,""); - /* charset */ - setlocale(LC_CTYPE,""); - /* initialize charset conversions */ - charset_init(g_get_charset(&charset)); - D("charset: %s\n", charset); + struct sigaction act; +#ifdef ENABLE_LOCALE + const char *charset = NULL; +#endif + GIOChannel *keyboard_channel; +#ifdef ENABLE_LIRC + int lirc_socket; + GIOChannel *lirc_channel = NULL; +#endif + +#ifdef ENABLE_LOCALE + /* time and date formatting */ + setlocale(LC_TIME,""); + /* care about sorting order etc */ + setlocale(LC_COLLATE,""); + /* charset */ + setlocale(LC_CTYPE,""); + /* initialize charset conversions */ + charset = charset_init(); + + /* initialize i18n support */ #endif - /* initialize i18n support */ #ifdef ENABLE_NLS - setlocale(LC_MESSAGES, ""); - bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR); - bind_textdomain_codeset(GETTEXT_PACKAGE, charset); - textdomain(GETTEXT_PACKAGE); + setlocale(LC_MESSAGES, ""); + bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR); +#ifdef ENABLE_LOCALE + bind_textdomain_codeset(GETTEXT_PACKAGE, charset); +#endif + textdomain(GETTEXT_PACKAGE); +#endif + + /* initialize options */ + options_init(); + + /* parse command line options - 1 pass get configuration files */ + options_parse(argc, argv); + +#ifndef NCMPC_MINI + /* read configuration */ + read_configuration(); + + /* check key bindings */ + check_key_bindings(NULL, NULL, 0); #endif - /* initialize options */ - options = options_init(); - - /* parse command line options - 1 pass get configuration files */ - options_parse(argc, argv); - - /* read configuration */ - read_configuration(options); - - /* check key bindings */ - key_error = check_key_bindings(NULL, NULL, 0); - - /* parse command line options - 2 pass */ - options_parse(argc, argv); - - /* setup signal behavior - SIGINT */ - sigemptyset( &act.sa_mask ); - act.sa_flags = 0; - act.sa_handler = catch_sigint; - if( sigaction( SIGINT, &act, NULL )<0 ) - { - perror("signal"); - exit(EXIT_FAILURE); - } - /* setup signal behavior - SIGTERM */ - sigemptyset( &act.sa_mask ); - act.sa_flags = 0; - act.sa_handler = catch_sigint; - if( sigaction( SIGTERM, &act, NULL )<0 ) - { - perror("sigaction()"); - exit(EXIT_FAILURE); - } - - /* install exit function */ - atexit(exit_and_cleanup); - - /* connect to our music player daemon */ - mpd = mpdclient_new(); - if( mpdclient_connect(mpd, - options->host, - options->port, - 10.0, - options->password) ) - { - exit(EXIT_FAILURE); - } - connected = TRUE; - D("Connected to MPD version %d.%d.%d\n", - mpd->connection->version[0], - mpd->connection->version[1], - mpd->connection->version[2]); - - /* quit if mpd is pre 0.11.0 - song id not supported by mpd */ - if( MPD_VERSION_LT(mpd, 0,11,0) ) - { - fprintf(stderr, - _("Error: MPD version %d.%d.%d is to old (0.11.0 needed).\n"), - mpd->connection->version[0], - mpd->connection->version[1], - mpd->connection->version[2]); - exit(EXIT_FAILURE); - } - - /* initialize curses */ - screen_init(mpd); - - /* install error callback function */ - mpdclient_install_error_callback(mpd, error_callback); - - /* initialize timer */ - timer = g_timer_new(); - - connected = TRUE; - while( connected || options->reconnect ) - { - static gdouble t = G_MAXDOUBLE; - - if( key_error ) - { - char buf[BUFSIZE]; - - key_error=check_key_bindings(NULL, buf, BUFSIZE); - screen_status_printf("%s", buf); + /* parse command line options - 2 pass */ + options_parse(argc, argv); + + /* setup signal behavior - SIGINT */ + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = catch_sigint; + if (sigaction(SIGINT, &act, NULL) < 0) { + perror("signal"); + exit(EXIT_FAILURE); + } + + /* setup signal behavior - SIGTERM */ + + act.sa_handler = catch_sigint; + if (sigaction(SIGTERM, &act, NULL) < 0) { + perror("sigaction()"); + exit(EXIT_FAILURE); } - if( connected && (t>=MPD_UPDATE_TIME || mpd->need_update) ) - { - mpdclient_update(mpd); - g_timer_start(timer); + /* setup signal behavior - SIGCONT */ + + act.sa_handler = catch_sigcont; + if (sigaction(SIGCONT, &act, NULL) < 0) { + perror("sigaction(SIGCONT)"); + exit(EXIT_FAILURE); } - if( connected ) - { - command_t cmd; - - screen_update(mpd); - if( (cmd=get_keyboard_command()) != CMD_NONE ) - { - screen_cmd(mpd, cmd); - if( cmd==CMD_VOLUME_UP || cmd==CMD_VOLUME_DOWN) - /* make shure we dont update the volume yet */ - g_timer_start(timer); - } - else - screen_idle(mpd); + /* setup signal behaviour - SIGHUP*/ + + act.sa_handler = catch_sigint; + if (sigaction(SIGHUP, &act, NULL) < 0) { + perror("sigaction(SIGHUP)"); + exit(EXIT_FAILURE); } - else if( options->reconnect ) - { - screen_status_printf(_("Connecting to %s... [Press %s to abort]"), - options->host, get_key_names(CMD_QUIT,0) ); - - if( get_keyboard_command_with_timeout(MPD_RECONNECT_TIME)==CMD_QUIT) - exit(EXIT_SUCCESS); - - if( mpdclient_connect(mpd, - options->host, - options->port, - 1.5, - options->password) == 0 ) - { - screen_status_printf(_("Connected to %s!"), options->host); - connected = TRUE; - } - doupdate(); + + /* setup SIGWINCH */ + + act.sa_flags = SA_RESTART; + act.sa_handler = catch_sigwinch; + if (sigaction(SIGWINCH, &act, NULL) < 0) { + perror("sigaction(SIGWINCH)"); + exit(EXIT_FAILURE); + } + + /* ignore SIGPIPE */ + + act.sa_handler = SIG_IGN; + if (sigaction(SIGPIPE, &act, NULL) < 0) { + perror("sigaction(SIGPIPE)"); + exit(EXIT_FAILURE); } - if( options->enable_xterm_title ) - update_xterm_title(); - t = g_timer_elapsed(timer, NULL); - } - exit(EXIT_FAILURE); + + ncu_init(); + +#ifdef ENABLE_LYRICS_SCREEN + lyrics_init(); +#endif + + /* create mpdclient instance */ + mpd = mpdclient_new(); + mpdclient_install_error_callback(mpd, error_callback); + + /* initialize curses */ + screen_init(mpd); + + /* the main loop */ + main_loop = g_main_loop_new(NULL, FALSE); + + /* watch out for keyboard input */ + keyboard_channel = g_io_channel_unix_new(STDIN_FILENO); + g_io_add_watch(keyboard_channel, G_IO_IN, keyboard_event, NULL); + +#ifdef ENABLE_LIRC + /* watch out for lirc input */ + lirc_socket = ncmpc_lirc_open(); + if (lirc_socket >= 0) { + lirc_channel = g_io_channel_unix_new(lirc_socket); + g_io_add_watch(lirc_channel, G_IO_IN, lirc_event, NULL); + } +#endif + + /* attempt to connect */ + reconnect_source_id = g_timeout_add(1, timer_reconnect, NULL); + + update_source_id = g_timeout_add(update_interval, + timer_mpd_update, + GINT_TO_POINTER(TRUE)); +#ifndef NCMPC_MINI + g_timeout_add(10000, timer_check_key_bindings, NULL); +#endif + idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL); + + screen_paint(mpd); + + g_main_loop_run(main_loop); + + /* cleanup */ + + g_main_loop_unref(main_loop); + g_io_channel_unref(keyboard_channel); + +#ifdef ENABLE_LIRC + if (lirc_socket >= 0) + g_io_channel_unref(lirc_channel); + ncmpc_lirc_close(); +#endif + + exit_and_cleanup(); + +#ifdef ENABLE_LYRICS_SCREEN + lyrics_deinit(); +#endif + + ncu_deinit(); + + return 0; }