X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fmain.c;h=044221024ff11bf1f78947a4ebe7e402edb8670b;hb=23c27e8f5e7e428983b95f65a2e205ad1a632c35;hp=0cbe184bca87554ce95c9a114df8820bacf40698;hpb=a537ee8a0acf9fee2347400537dd5f9cd345bc14;p=ncmpc.git diff --git a/src/main.c b/src/main.c index 0cbe184..0442210 100644 --- a/src/main.c +++ b/src/main.c @@ -1,31 +1,33 @@ /* ncmpc (Ncurses MPD Client) - * (c) 2004-2009 The Music Player Daemon Project + * (c) 2004-2010 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ + */ #include "config.h" #include "ncmpc.h" #include "mpdclient.h" +#include "gidle.h" #include "charset.h" #include "options.h" #include "command.h" #include "ncu.h" #include "screen.h" #include "screen_utils.h" +#include "screen_status.h" #include "strfsong.h" #include "i18n.h" #include "player_command.h" @@ -46,6 +48,7 @@ #include #include +#include #include #include @@ -53,7 +56,7 @@ #include #endif -/* time between mpd updates [s] */ +/* time between mpd updates [ms] */ static const guint update_interval = 500; #define BUFSIZE 1024 @@ -61,43 +64,31 @@ static const guint update_interval = 500; static struct mpdclient *mpd = NULL; static GMainLoop *main_loop; static guint reconnect_source_id, update_source_id; +static int sigwinch_pipes[2]; #ifndef NCMPC_MINI static guint check_key_bindings_source_id; #endif -static void -error_callback(G_GNUC_UNUSED struct mpdclient *c, G_GNUC_UNUSED gint error, - const gchar *_msg) -{ - char *msg = utf8_to_locale(_msg); - screen_status_printf("%s", msg); - g_free(msg); - - screen_bell(); - doupdate(); -} - #ifndef NCMPC_MINI static void update_xterm_title(void) { - static char title[BUFSIZE]; - char tmp[BUFSIZE]; struct mpd_status *status = NULL; const struct mpd_song *song = NULL; - if (mpd) { status = mpd->status; song = mpd->song; } + char tmp[BUFSIZE]; if (options.xterm_title_format && status && song && - IS_PLAYING(mpd_status_get_state(status))) + mpd_status_get_state(status) == MPD_STATE_PLAY) strfsong(tmp, BUFSIZE, options.xterm_title_format, song); else g_strlcpy(tmp, PACKAGE " version " VERSION, BUFSIZE); + static char title[BUFSIZE]; if (strncmp(title, tmp, BUFSIZE)) { g_strlcpy(title, tmp, BUFSIZE); set_xterm_title("%s", title); @@ -120,119 +111,291 @@ exit_and_cleanup(void) } } +#ifndef WIN32 static void -catch_sigint(G_GNUC_UNUSED int sig) +catch_sigint(gcc_unused int sig) { g_main_loop_quit(main_loop); } static void -catch_sigcont(G_GNUC_UNUSED int sig) +catch_sigcont(gcc_unused int sig) { - screen_resize(mpd); + char irrelevant = 'a'; + if (1 != write(sigwinch_pipes[1], &irrelevant, 1)) + exit(EXIT_FAILURE); } void sigstop(void) { - def_prog_mode(); /* save the tty modes */ - endwin(); /* end curses mode temporarily */ - kill(0, SIGSTOP); /* issue SIGSTOP */ + 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) +sigwinch_event(gcc_unused GIOChannel *source, + gcc_unused GIOCondition condition, gcc_unused gpointer data) { - /* the following causes the screen to flicker. There might be - better solutions, but I believe it isn't all that - important. */ + char ignoreme[64]; + if (1 > read(sigwinch_pipes[0], ignoreme, 64)) + exit(EXIT_FAILURE); endwin(); refresh(); screen_resize(mpd); - return FALSE; + return TRUE; } static void -catch_sigwinch(G_GNUC_UNUSED int sig) +catch_sigwinch(gcc_unused int sig) { - if (timer_sigwinch_id != 0) - g_source_remove(timer_sigwinch_id); - - timer_sigwinch_id = g_timeout_add(100, timer_sigwinch, NULL); + char irrelevant = 'a'; + if (1 != write(sigwinch_pipes[1], &irrelevant, 1)) + exit(EXIT_FAILURE); } +#endif /* WIN32 */ + +static void +idle_callback(enum mpd_error error, + gcc_unused enum mpd_server_error server_error, + const char *message, enum mpd_idle events, + gcc_unused void *ctx); static gboolean timer_mpd_update(gpointer data); +static void +enable_update_timer(void) +{ + if (update_source_id != 0) + return; + + update_source_id = g_timeout_add(update_interval, + timer_mpd_update, NULL); +} + +static void +disable_update_timer(void) +{ + if (update_source_id == 0) + return; + + g_source_remove(update_source_id); + update_source_id = 0; +} + +static bool +should_enable_update_timer(void) +{ + return (mpdclient_is_connected(mpd) && + (mpd->source == NULL || /* "idle" not supported */ + (mpd->status != NULL && + mpd_status_get_state(mpd->status) == MPD_STATE_PLAY))) +#ifndef NCMPC_MINI + || options.display_time +#endif + ; +} + +static void +auto_update_timer(void) +{ + if (should_enable_update_timer()) + enable_update_timer(); + else + disable_update_timer(); +} + +static void +check_reconnect(void); + +static void +do_mpd_update(void) +{ + if (mpdclient_is_connected(mpd) && + (mpd->source == NULL || mpd->events != 0 || + (mpd->status != NULL && + mpd_status_get_state(mpd->status) == MPD_STATE_PLAY))) + mpdclient_update(mpd); + +#ifndef NCMPC_MINI + if (options.enable_xterm_title) + update_xterm_title(); +#endif + + screen_update(mpd); + mpd->events = 0; + + mpdclient_put_connection(mpd); + check_reconnect(); +} + +#if LIBMPDCLIENT_CHECK_VERSION(2,4,0) + +static char * +settings_name(const struct mpd_settings *settings) +{ + const char *host = mpd_settings_get_host(settings); + if (host == NULL) + host = _("unknown"); + + if (host[0] == '/') + return g_strdup(host); + + unsigned port = mpd_settings_get_port(settings); + if (port == 0 || port == 6600) + return g_strdup(host); + + return g_strdup_printf("%s:%u", host, port); +} + +#endif + +static char * +default_settings_name(void) +{ +#if LIBMPDCLIENT_CHECK_VERSION(2,4,0) + struct mpd_settings *settings = + mpd_settings_new(options.host, options.port, 0, + NULL, options.password); + if (settings == NULL) + return g_strdup(_("unknown")); + + char *name = settings_name(settings); + mpd_settings_free(settings); + + return name; +#else + /* + * localhost is actually not correct, we only know that + * mpd_connection_new() has connected to the "default host". + */ + const char *name = options.host ?: "localhost"; + return g_strdup(name); +#endif +} + /** * 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) +timer_reconnect(gcc_unused gpointer data) { - bool success; - assert(!mpdclient_is_connected(mpd)); + reconnect_source_id = 0; + + char *name = default_settings_name(); screen_status_printf(_("Connecting to %s... [Press %s to abort]"), - options.host, get_key_names(CMD_QUIT,0) ); + name, get_key_names(CMD_QUIT, false)); + g_free(name); doupdate(); mpdclient_disconnect(mpd); - success = mpdclient_connect(mpd, - options.host, options.port, - 1.5, - options.password); - if (!success) { + if (!mpdclient_connect(mpd, options.host, options.port, + options.timeout_ms, + options.password)) { /* try again in 5 seconds */ - g_timeout_add(5000, timer_reconnect, NULL); + reconnect_source_id = g_timeout_add(5000, + timer_reconnect, NULL); return FALSE; } + struct mpd_connection *connection = mpdclient_get_connection(mpd); + #ifndef NCMPC_MINI - /* quit if mpd is pre 0.11.0 - song id not supported by mpd */ - if (mpd_connection_cmp_server_version(mpd->connection, 0, 12, 0) < 0) { + /* quit if mpd is pre 0.14 - song id not supported by mpd */ + if (mpd_connection_cmp_server_version(connection, 0, 16, 0) < 0) { const unsigned *version = - mpd_connection_get_server_version(mpd->connection); - screen_status_printf(_("Error: MPD version %d.%d.%d is to old (%s needed)"), + mpd_connection_get_server_version(connection); + screen_status_printf(_("Error: MPD version %d.%d.%d is too old (%s needed)"), version[0], version[1], version[2], - "0.12.0"); + "0.16.0"); mpdclient_disconnect(mpd); doupdate(); /* try again after 30 seconds */ - g_timeout_add(30000, timer_reconnect, NULL); + reconnect_source_id = g_timeout_add(30000, + timer_reconnect, NULL); return FALSE; } #endif - screen_status_printf(_("Connected to %s"), - options.host != NULL - ? options.host : "localhost"); + mpd->source = mpd_glib_new(connection, + idle_callback, mpd); + + screen_status_clear_message(); doupdate(); /* update immediately */ - g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE)); + mpd->events = MPD_IDLE_ALL; + + do_mpd_update(); + + auto_update_timer(); - reconnect_source_id = 0; return FALSE; +} +static void +check_reconnect(void) +{ + if (!mpdclient_is_connected(mpd) && reconnect_source_id == 0) + /* reconnect when the connection is lost */ + reconnect_source_id = g_timeout_add(1000, timer_reconnect, + NULL); } -static gboolean -timer_mpd_update(gpointer data) +/** + * This function is called by the gidle.c library when MPD sends us an + * idle event (or when the connection dies). + */ +static void +idle_callback(enum mpd_error error, enum mpd_server_error server_error, + const char *message, enum mpd_idle events, + void *ctx) { - if (mpdclient_is_connected(mpd)) - mpdclient_update(mpd); - else if (reconnect_source_id == 0) + struct mpdclient *c = ctx; + + c->idle = false; + + assert(mpdclient_is_connected(c)); + + if (error != MPD_ERROR_SUCCESS) { + if (error == MPD_ERROR_SERVER && + server_error == MPD_SERVER_ERROR_UNKNOWN_CMD) { + /* the "idle" command is not supported - fall + back to timer based polling */ + mpd_glib_free(c->source); + c->source = NULL; + auto_update_timer(); + return; + } + + char *allocated; + if (error == MPD_ERROR_SERVER) + message = allocated = utf8_to_locale(message); + else + allocated = NULL; + screen_status_message(message); + g_free(allocated); + screen_bell(); + doupdate(); + + mpdclient_disconnect(c); + screen_update(mpd); reconnect_source_id = g_timeout_add(1000, timer_reconnect, NULL); + return; + } + + c->events |= events; + mpdclient_update(c); #ifndef NCMPC_MINI if (options.enable_xterm_title) @@ -240,8 +403,24 @@ timer_mpd_update(gpointer data) #endif screen_update(mpd); + c->events = 0; + + mpdclient_put_connection(c); + check_reconnect(); + auto_update_timer(); +} + +static gboolean +timer_mpd_update(gcc_unused gpointer data) +{ + do_mpd_update(); - return GPOINTER_TO_INT(data); + if (should_enable_update_timer()) + return true; + else { + update_source_id = 0; + return false; + } } void begin_input_event(void) @@ -251,6 +430,11 @@ void begin_input_event(void) void end_input_event(void) { screen_update(mpd); + mpd->events = 0; + + mpdclient_put_connection(mpd); + check_reconnect(); + auto_update_timer(); } int do_input_event(command_t cmd) @@ -262,27 +446,22 @@ int do_input_event(command_t cmd) screen_cmd(mpd, cmd); - if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) { + if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) /* make sure we don't 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)); - } + disable_update_timer(); return 0; } static gboolean -keyboard_event(G_GNUC_UNUSED GIOChannel *source, - G_GNUC_UNUSED GIOCondition condition, - G_GNUC_UNUSED gpointer data) +keyboard_event(gcc_unused GIOChannel *source, + gcc_unused GIOCondition condition, + gcc_unused gpointer data) { - command_t cmd; - begin_input_event(); - if ((cmd=get_keyboard_command()) != CMD_NONE) + command_t cmd = get_keyboard_command(); + if (cmd != CMD_NONE) if (do_input_event(cmd) != 0) return FALSE; @@ -296,16 +475,11 @@ keyboard_event(G_GNUC_UNUSED GIOChannel *source, * message every 10 seconds. */ static gboolean -timer_check_key_bindings(G_GNUC_UNUSED gpointer data) +timer_check_key_bindings(gcc_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) { + if (check_key_bindings(NULL, buf, sizeof(buf))) { /* no error: disable this timer for the rest of this process */ check_key_bindings_source_id = 0; @@ -318,8 +492,9 @@ timer_check_key_bindings(G_GNUC_UNUSED gpointer data) /* 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 */ + char comment[64]; g_snprintf(comment, sizeof(comment), _("press %s for the key editor"), - get_key_names(CMD_SCREEN_KEYDEF, 0)); + get_key_names(CMD_SCREEN_KEYDEF, false)); g_strlcat(buf, comment, sizeof(buf)); g_strlcat(buf, ")", sizeof(buf)); #endif @@ -334,17 +509,11 @@ timer_check_key_bindings(G_GNUC_UNUSED gpointer data) int main(int argc, const char *argv[]) { - struct sigaction act; #ifdef ENABLE_LOCALE - const char *charset = NULL; -#endif - GIOChannel *keyboard_channel; -#ifdef ENABLE_LIRC - int lirc_socket; - GIOChannel *lirc_channel = NULL; +#ifndef ENABLE_NLS + gcc_unused #endif - -#ifdef ENABLE_LOCALE + const char *charset = NULL; /* time and date formatting */ setlocale(LC_TIME,""); /* care about sorting order etc */ @@ -383,7 +552,9 @@ main(int argc, const char *argv[]) /* parse command line options - 2 pass */ options_parse(argc, argv); +#ifndef WIN32 /* setup signal behavior - SIGINT */ + struct sigaction act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = catch_sigint; @@ -432,6 +603,7 @@ main(int argc, const char *argv[]) perror("sigaction(SIGPIPE)"); exit(EXIT_FAILURE); } +#endif ncu_init(); @@ -441,7 +613,6 @@ main(int argc, const char *argv[]) /* create mpdclient instance */ mpd = mpdclient_new(); - mpdclient_install_error_callback(mpd, error_callback); /* initialize curses */ screen_init(mpd); @@ -450,24 +621,37 @@ main(int argc, const char *argv[]) main_loop = g_main_loop_new(NULL, FALSE); /* watch out for keyboard input */ - keyboard_channel = g_io_channel_unix_new(STDIN_FILENO); + GIOChannel *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(); + int lirc_socket = ncmpc_lirc_open(); + GIOChannel *lirc_channel = NULL; 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 +#ifndef WIN32 + GIOChannel *sigwinch_channel = NULL; + if (!pipe(sigwinch_pipes) && + !fcntl(sigwinch_pipes[1], F_SETFL, O_NONBLOCK)) { + sigwinch_channel = g_io_channel_unix_new(sigwinch_pipes[0]); + g_io_add_watch(sigwinch_channel, G_IO_IN, sigwinch_event, NULL); + } + else { + perror("sigwinch pipe creation failed"); + exit(EXIT_FAILURE); + } +#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)); + auto_update_timer(); + #ifndef NCMPC_MINI check_key_bindings_source_id = g_timeout_add(10000, timer_check_key_bindings, NULL); #endif @@ -480,7 +664,10 @@ main(int argc, const char *argv[]) cancel_seek_timer(); - g_source_remove(update_source_id); + disable_update_timer(); + + if (reconnect_source_id != 0) + g_source_remove(reconnect_source_id); #ifndef NCMPC_MINI if (check_key_bindings_source_id != 0) @@ -489,6 +676,9 @@ main(int argc, const char *argv[]) g_main_loop_unref(main_loop); g_io_channel_unref(keyboard_channel); + g_io_channel_unref(sigwinch_channel); + close(sigwinch_pipes[0]); + close(sigwinch_pipes[1]); #ifdef ENABLE_LIRC if (lirc_socket >= 0)