X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fmain.c;h=85548bec1ebaa6376f01f52dd228713bfc08abda;hb=8e3947607bc07f0f181c3b0d8d677e3af0acbf8c;hp=da9f55389a321abcdf7340fcf07f524c585aa1df;hpb=c85a3c8f1d28929bb18afc72ec3abd3dd9ed4b7c;p=ncmpc.git diff --git a/src/main.c b/src/main.c index da9f553..85548be 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,6 @@ -/* - * (c) 2004 by Kalle Wallin +/* ncmpc (Ncurses MPD Client) + * (c) 2004-2017 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 @@ -10,323 +11,433 @@ * 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 * + * 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 "callbacks.h" #include "charset.h" #include "options.h" -#include "conf.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 "gcc.h" +#include "player_command.h" +#include "lirc.h" + +#ifndef NCMPC_MINI +#include "conf.h" +#endif #ifdef ENABLE_LYRICS_SCREEN #include "lyrics.h" #endif +#include + #include #include +#include #include #include -/* time between mpd updates [s] */ +#ifdef ENABLE_LOCALE +#include +#endif + +/* time between mpd updates [ms] */ static const guint update_interval = 500; #define BUFSIZE 1024 -static const guint idle_interval = 500; - -static mpdclient_t *mpd = NULL; -static gboolean connected = FALSE; +static struct mpdclient *mpd = NULL; static GMainLoop *main_loop; -static guint reconnect_source_id, idle_source_id, update_source_id; - -static const gchar * -error_msg(const gchar *msg) -{ - gchar *p; +static guint reconnect_source_id, update_source_id; +static int sigwinch_pipes[2]; - if ((p = strchr(msg, '}')) == NULL) - return msg; - - while (p && *p && (*p=='}' || *p==' ')) - p++; - - return p; -} - -static void -error_callback(mpd_unused mpdclient_t *c, gint error, const gchar *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; - } -} +#ifndef NCMPC_MINI +static guint check_key_bindings_source_id; +#endif +#ifndef NCMPC_MINI static void update_xterm_title(void) { - static char title[BUFSIZE]; - char tmp[BUFSIZE]; - mpd_Status *status = NULL; - mpd_Song *song = NULL; + const struct mpd_song *song = mpd->song; - if (mpd) { - status = mpd->status; - song = mpd->song; - } - - if (options.xterm_title_format && status && song && - IS_PLAYING(status->state)) + char tmp[BUFSIZE]; + if (options.xterm_title_format && mpd->playing && song) 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); } } +#endif +#ifndef WIN32 static void -exit_and_cleanup(void) -{ - screen_exit(); - set_xterm_title(""); - 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); - g_free(options.scroll_sep); -} - -static void -catch_sigint(mpd_unused int sig) +catch_sigint(gcc_unused int sig) { g_main_loop_quit(main_loop); } static void -catch_sigcont(mpd_unused int sig) +catch_sigcont(gcc_unused int sig) { -#ifdef ENABLE_RAW_MODE - reset_prog_mode(); /* restore tty modes */ - refresh(); -#endif - screen_resize(mpd); + if (1 != write(sigwinch_pipes[1], "", 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(mpd_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(mpd_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); + if (1 != write(sigwinch_pipes[1], "", 1)) + exit(EXIT_FAILURE); } +#endif /* WIN32 */ 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 mpd->playing +#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->events != 0 || mpd->playing)) + 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(); +} + +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); +} + +static char * +default_settings_name(void) +{ + 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; +} + /** * This timer is installed when the connection to the MPD server is * broken. It tries to recover by reconnecting periodically. */ static gboolean -timer_reconnect(mpd_unused gpointer data) +timer_reconnect(gcc_unused gpointer data) { - int ret; + assert(mpdclient_is_dead(mpd)); - if (connected) - return FALSE; + 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); - 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; - } + mpdclient_connect(mpd, options.host, options.port, + options.timeout_ms, + options.password); + + return FALSE; +} - /* 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 (0.11.0 needed).\n"), - mpd->connection->version[0], - mpd->connection->version[1], - mpd->connection->version[2]); +static void +check_reconnect(void) +{ + if (mpdclient_is_dead(mpd) && reconnect_source_id == 0) + /* reconnect when the connection is lost */ + reconnect_source_id = g_timeout_add(1000, timer_reconnect, + NULL); +} + +void +mpdclient_connected_callback(void) +{ + assert(reconnect_source_id == 0); + +#ifndef NCMPC_MINI + /* quit if mpd is pre 0.14 - song id not supported by mpd */ + struct mpd_connection *connection = mpdclient_get_connection(mpd); + if (mpd_connection_cmp_server_version(connection, 0, 16, 0) < 0) { + const unsigned *version = + 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.16.0"); mpdclient_disconnect(mpd); doupdate(); /* try again after 30 seconds */ - g_timeout_add(30000, timer_reconnect, NULL); - return FALSE; + reconnect_source_id = g_timeout_add(30000, + timer_reconnect, NULL); + return; } +#endif - screen_status_printf(_("Connected to %s!"), options.host); + screen_status_clear_message(); doupdate(); - connected = TRUE; - /* update immediately */ - g_timeout_add(1, timer_mpd_update, GINT_TO_POINTER(FALSE)); + mpd->events = MPD_IDLE_ALL; - reconnect_source_id = 0; - return FALSE; + do_mpd_update(); + auto_update_timer(); } -static gboolean -timer_mpd_update(gpointer data) +void +mpdclient_failed_callback(void) { - if (connected) - mpdclient_update(mpd); - else if (reconnect_source_id == 0) - reconnect_source_id = g_timeout_add(1000, timer_reconnect, - NULL); + assert(reconnect_source_id == 0); - if (options.enable_xterm_title) - update_xterm_title(); + /* try again in 5 seconds */ + reconnect_source_id = g_timeout_add(5000, + timer_reconnect, NULL); +} + +void +mpdclient_lost_callback(void) +{ + assert(reconnect_source_id == 0); screen_update(mpd); - return GPOINTER_TO_INT(data); + reconnect_source_id = g_timeout_add(1000, timer_reconnect, NULL); } /** - * This idle timer is invoked when the user hasn't typed a key for - * 500ms. It is used for delayed seeking. + * This function is called by the gidle.c library when MPD sends us an + * idle event (or when the connection dies). */ -static gboolean -timer_idle(mpd_unused gpointer data) +void +mpdclient_idle_callback(gcc_unused enum mpd_idle events) { - screen_idle(mpd); - return TRUE; +#ifndef NCMPC_MINI + if (options.enable_xterm_title) + update_xterm_title(); +#endif + + screen_update(mpd); + auto_update_timer(); } static gboolean -keyboard_event(mpd_unused GIOChannel *source, - mpd_unused GIOCondition condition, mpd_unused gpointer data) +timer_mpd_update(gcc_unused gpointer data) { - command_t cmd; + do_mpd_update(); - /* remove the idle timeout; add it later with fresh interval */ - g_source_remove(idle_source_id); + if (should_enable_update_timer()) + return true; + else { + update_source_id = 0; + return false; + } +} - if ((cmd=get_keyboard_command()) != CMD_NONE) { - if (cmd == CMD_QUIT) { - g_main_loop_quit(main_loop); - return FALSE; - } +void begin_input_event(void) +{ +} + +void end_input_event(void) +{ + screen_update(mpd); + mpd->events = 0; - screen_cmd(mpd, cmd); + mpdclient_put_connection(mpd); + check_reconnect(); + auto_update_timer(); +} - 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)); - } +int do_input_event(command_t cmd) +{ + if (cmd == CMD_QUIT) { + g_main_loop_quit(main_loop); + return -1; } - screen_update(mpd); + screen_cmd(mpd, cmd); + + if (cmd == CMD_VOLUME_UP || cmd == CMD_VOLUME_DOWN) + /* make sure we don't update the volume yet */ + disable_update_timer(); + + return 0; +} - idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL); +static gboolean +keyboard_event(gcc_unused GIOChannel *source, + gcc_unused GIOCondition condition, + gcc_unused gpointer data) +{ + begin_input_event(); + + command_t cmd = get_keyboard_command(); + if (cmd != 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(mpd_unused gpointer data) +timer_check_key_bindings(gcc_unused gpointer data) { char buf[256]; - 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; 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 */ + char comment[64]; + g_snprintf(comment, sizeof(comment), _("press %s for the key editor"), + get_key_names(CMD_SCREEN_KEYDEF, false)); + 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[]) { - struct sigaction act; -#ifdef HAVE_LOCALE_H - const char *charset = NULL; +#ifdef ENABLE_LOCALE +#ifndef ENABLE_NLS + gcc_unused #endif - GIOChannel *keyboard_channel; - -#ifdef HAVE_LOCALE_H + const char *charset = NULL; /* time and date formatting */ setlocale(LC_TIME,""); /* care about sorting order etc */ @@ -337,12 +448,15 @@ main(int argc, const char *argv[]) charset = charset_init(); /* initialize i18n support */ +#endif + #ifdef ENABLE_NLS setlocale(LC_MESSAGES, ""); bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR); +#ifdef ENABLE_LOCALE bind_textdomain_codeset(GETTEXT_PACKAGE, charset); - textdomain(GETTEXT_PACKAGE); #endif + textdomain(GETTEXT_PACKAGE); #endif /* initialize options */ @@ -351,16 +465,20 @@ main(int argc, const char *argv[]) /* 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 /* 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; @@ -409,6 +527,7 @@ main(int argc, const char *argv[]) perror("sigaction(SIGPIPE)"); exit(EXIT_FAILURE); } +#endif ncu_init(); @@ -418,7 +537,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); @@ -427,29 +545,73 @@ 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); + g_io_channel_unref(keyboard_channel); + + /* watch out for lirc input */ + ncmpc_lirc_init(); + +#ifndef WIN32 + if (!pipe(sigwinch_pipes) && + !fcntl(sigwinch_pipes[1], F_SETFL, O_NONBLOCK)) { + GIOChannel *sigwinch_channel = g_io_channel_unix_new(sigwinch_pipes[0]); + g_io_add_watch(sigwinch_channel, G_IO_IN, sigwinch_event, NULL); + g_io_channel_unref(sigwinch_channel); + } + 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)); - g_timeout_add(10000, timer_check_key_bindings, NULL); - idle_source_id = g_timeout_add(idle_interval, timer_idle, NULL); + auto_update_timer(); + +#ifndef NCMPC_MINI + check_key_bindings_source_id = g_timeout_add(10000, timer_check_key_bindings, NULL); +#endif screen_paint(mpd); g_main_loop_run(main_loop); + g_main_loop_unref(main_loop); /* cleanup */ - g_main_loop_unref(main_loop); - g_io_channel_unref(keyboard_channel); + cancel_seek_timer(); + + disable_update_timer(); + + if (reconnect_source_id != 0) + g_source_remove(reconnect_source_id); + +#ifndef NCMPC_MINI + if (check_key_bindings_source_id != 0) + g_source_remove(check_key_bindings_source_id); +#endif + + close(sigwinch_pipes[0]); + close(sigwinch_pipes[1]); + + ncmpc_lirc_deinit(); + + screen_exit(); +#ifndef NCMPC_MINI + set_xterm_title(""); +#endif + printf("\n"); + + mpdclient_free(mpd); + +#ifdef ENABLE_LYRICS_SCREEN + lyrics_deinit(); +#endif - exit_and_cleanup(); ncu_deinit(); + options_deinit(); return 0; }