Logo Search packages:      
Sourcecode: cmus version File versions

ui_curses.c

/*
 * Copyright 2004-2005 Timo Hirvonen
 *
 * 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 "ui_curses.h"
#include "cmdline.h"
#include "search_mode.h"
#include "command_mode.h"
#include "options.h"
#include "play_queue.h"
#include "browser.h"
#include "filters.h"
#include "cmus.h"
#include "player.h"
#include "utils.h"
#include "lib.h"
#include "pl.h"
#include "xmalloc.h"
#include "xstrjoin.h"
#include "window.h"
#include "format_print.h"
#include "misc.h"
#include "prog.h"
#include "uchar.h"
#include "spawn.h"
#include "server.h"
#include "keys.h"
#include "debug.h"
#include "help.h"

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <dirent.h>
#include <locale.h>
#include <langinfo.h>
#include <iconv.h>
#include <signal.h>
#include <stdarg.h>

#if defined(__sun__) || defined(__CYGWIN__)
/* TIOCGWINSZ */
#include <termios.h>
#include <ncurses.h>
#else
#include <curses.h>
#endif

/* defined in <term.h> but without const */
char *tgetstr(const char *id, char **area);
char *tgoto(const char *cap, int col, int row);

/* globals. documented in ui_curses.h */

int cmus_running = 1;
int ui_initialized = 0;
enum ui_input_mode input_mode = NORMAL_MODE;
int cur_view = TREE_VIEW;
struct searchable *searchable;
char *lib_filename = NULL;
char *pl_filename = NULL;

/* ------------------------------------------------------------------------- */

static char *lib_autosave_filename;
static char *pl_autosave_filename;

/* shown error message and time stamp
 * error is cleared if it is older than 3s and key was pressed
 */
static char error_buf[512];
static time_t error_time = 0;
/* info messages are displayed in different color */
static int msg_is_error;
static int error_count = 0;

static char *server_address = NULL;

static char *charset = NULL;
static char print_buffer[512];

/* destination buffer for utf8_encode and utf8_decode */
static char conv_buffer[512];

#define print_buffer_size (sizeof(print_buffer) - 1)
static int using_utf8;

static const char *t_ts;
static const char *t_fs;

static int tree_win_x = 0;
static int tree_win_y = 0;
static int tree_win_w = 0;

static int track_win_x = 0;
static int track_win_y = 0;
static int track_win_w = 0;

static int cursor_x;
static int cursor_y;

enum {
      CURSED_WIN,
      CURSED_WIN_CUR,
      CURSED_WIN_SEL,
      CURSED_WIN_SEL_CUR,

      CURSED_WIN_ACTIVE,
      CURSED_WIN_ACTIVE_CUR,
      CURSED_WIN_ACTIVE_SEL,
      CURSED_WIN_ACTIVE_SEL_CUR,

      CURSED_SEPARATOR,
      CURSED_WIN_TITLE,
      CURSED_COMMANDLINE,
      CURSED_STATUSLINE,

      CURSED_TITLELINE,
      CURSED_DIR,
      CURSED_ERROR,
      CURSED_INFO,

      NR_CURSED
};

static unsigned char cursed_to_bg_idx[NR_CURSED] = {
      COLOR_WIN_BG,
      COLOR_WIN_BG,
      COLOR_WIN_INACTIVE_SEL_BG,
      COLOR_WIN_INACTIVE_CUR_SEL_BG,

      COLOR_WIN_BG,
      COLOR_WIN_BG,
      COLOR_WIN_SEL_BG,
      COLOR_WIN_CUR_SEL_BG,

      COLOR_WIN_BG,
      COLOR_WIN_TITLE_BG,
      COLOR_CMDLINE_BG,
      COLOR_STATUSLINE_BG,

      COLOR_TITLELINE_BG,
      COLOR_WIN_BG,
      COLOR_CMDLINE_BG,
      COLOR_CMDLINE_BG
};

static unsigned char cursed_to_fg_idx[NR_CURSED] = {
      COLOR_WIN_FG,
      COLOR_WIN_CUR,
      COLOR_WIN_INACTIVE_SEL_FG,
      COLOR_WIN_INACTIVE_CUR_SEL_FG,

      COLOR_WIN_FG,
      COLOR_WIN_CUR,
      COLOR_WIN_SEL_FG,
      COLOR_WIN_CUR_SEL_FG,

      COLOR_SEPARATOR,
      COLOR_WIN_TITLE_FG,
      COLOR_CMDLINE_FG,
      COLOR_STATUSLINE_FG,

      COLOR_TITLELINE_FG,
      COLOR_WIN_DIR,
      COLOR_ERROR,
      COLOR_INFO
};

/* index is CURSED_*, value is fucking color pair */
static int pairs[NR_CURSED];

enum {
      TF_ARTIST,
      TF_ALBUM,
      TF_DISC,
      TF_TRACK,
      TF_TITLE,
      TF_YEAR,
      TF_GENRE,
      TF_DURATION,
      TF_PATHFILE,
      TF_FILE,
      NR_TFS
};

static struct format_option track_fopts[NR_TFS + 1] = {
      DEF_FO_STR('a'),
      DEF_FO_STR('l'),
      DEF_FO_INT('D'),
      DEF_FO_INT('n'),
      DEF_FO_STR('t'),
      DEF_FO_STR('y'),
      DEF_FO_STR('g'),
      DEF_FO_TIME('d'),
      DEF_FO_STR('f'),
      DEF_FO_STR('F'),
      DEF_FO_END
};

enum {
      SF_STATUS,
      SF_POSITION,
      SF_DURATION,
      SF_TOTAL,
      SF_VOLUME,
      SF_LVOLUME,
      SF_RVOLUME,
      SF_BUFFER,
      SF_REPEAT,
      SF_CONTINUE,
      SF_SHUFFLE,
      SF_PLAYLISTMODE,
      NR_SFS
};

static struct format_option status_fopts[NR_SFS + 1] = {
      DEF_FO_STR('s'),
      DEF_FO_TIME('p'),
      DEF_FO_TIME('d'),
      DEF_FO_TIME('t'),
      DEF_FO_INT('v'),
      DEF_FO_INT('l'),
      DEF_FO_INT('r'),
      DEF_FO_INT('b'),
      DEF_FO_STR('R'),
      DEF_FO_STR('C'),
      DEF_FO_STR('S'),
      DEF_FO_STR('L'),
      DEF_FO_END
};

static void utf8_encode(const char *buffer)
{
      static iconv_t cd = (iconv_t)-1;
      size_t is, os;
      const char *i;
      char *o;
      int rc;

      if (cd == (iconv_t)-1) {
            d_print("iconv_open(UTF-8, %s)\n", charset);
            cd = iconv_open("UTF-8", charset);
            if (cd == (iconv_t)-1) {
                  d_print("iconv_open failed: %s\n", strerror(errno));
                  return;
            }
      }
      i = buffer;
      o = conv_buffer;
      is = strlen(i);
      os = sizeof(conv_buffer) - 1;
      rc = iconv(cd, (void *)&i, &is, &o, &os);
      *o = 0;
      if (rc == -1) {
            d_print("iconv failed: %s\n", strerror(errno));
            return;
      }
}

static void utf8_decode(const char *buffer)
{
      static iconv_t cd = (iconv_t)-1;
      size_t is, os;
      const char *i;
      char *o;
      int rc;

      if (cd == (iconv_t)-1) {
            d_print("iconv_open(%s, UTF-8)\n", charset);
            cd = iconv_open(charset, "UTF-8");
            if (cd == (iconv_t)-1) {
                  d_print("iconv_open failed: %s\n", strerror(errno));
                  return;
            }
      }
      i = buffer;
      o = conv_buffer;
      is = strlen(i);
      os = sizeof(conv_buffer) - 1;
      rc = iconv(cd, (void *)&i, &is, &o, &os);
      *o = 0;
      if (rc == -1) {
            d_print("iconv failed: %s\n", strerror(errno));
            return;
      }
}

/* screen updates {{{ */

static void dump_print_buffer(int row, int col)
{
      if (using_utf8) {
            mvaddstr(row, col, print_buffer);
      } else {
            utf8_decode(print_buffer);
            mvaddstr(row, col, conv_buffer);
      }
}

/* print @str into @buf
 *
 * if @str is shorter than @width pad with spaces
 * if @str is wider than @width truncate and add "..."
 */
static int format_str(char *buf, const char *str, int width)
{
      int s = 0, d = 0, ellipsis_pos = 0, cut_double_width = 0;

      while (1) {
            uchar u;
            int w;

            u_get_char(str, &s, &u);
            if (u == 0) {
                  memset(buf + d, ' ', width);
                  d += width;
                  break;
            }

            w = u_char_width(u);
            if (width == 3)
                  ellipsis_pos = d;
            if (width == 4 && w == 2) {
                  /* can't cut double-width char */
                  ellipsis_pos = d + 1;
                  cut_double_width = 1;
            }

            width -= w;
            if (width < 0) {
                  /* does not fit */
                  d = ellipsis_pos;
                  if (cut_double_width) {
                        /* first half of the double-width char */
                        buf[d - 1] = ' ';
                  }
                  buf[d++] = '.';
                  buf[d++] = '.';
                  buf[d++] = '.';
                  break;
            }
            u_set_char(buf, &d, u);
      }
      return d;
}

static void sprint(int row, int col, const char *str, int width)
{
      int pos = 0;

      print_buffer[pos++] = ' ';
      pos += format_str(print_buffer + pos, str, width - 2);
      print_buffer[pos++] = ' ';
      print_buffer[pos] = 0;
      dump_print_buffer(row, col);
}

static void sprint_ascii(int row, int col, const char *str, int len)
{
      int l;

      l = strlen(str);
      len -= 2;

      print_buffer[0] = ' ';
      if (l > len) {
            memcpy(print_buffer + 1, str, len - 3);
            print_buffer[len - 2] = '.';
            print_buffer[len - 1] = '.';
            print_buffer[len - 0] = '.';
      } else {
            memcpy(print_buffer + 1, str, l);
            memset(print_buffer + 1 + l, ' ', len - l);
      }
      print_buffer[len + 1] = ' ';
      print_buffer[len + 2] = 0;
      mvaddstr(row, col, print_buffer);
}

static void print_tree(struct window *win, int row, struct iter *iter)
{
      const char *str;
      struct artist *artist;
      struct album *album;
      struct iter sel;
      int current, selected, active, pos;

      artist = iter_to_artist(iter);
      album = iter_to_album(iter);
      current = 0;
      if (lib_cur_track) {
            if (album) {
                  current = CUR_ALBUM == album;
            } else {
                  current = CUR_ARTIST == artist;
            }
      }
      window_get_sel(win, &sel);
      selected = iters_equal(iter, &sel);
      active = lib_cur_win == lib_tree_win;
      bkgdset(pairs[(active << 2) | (selected << 1) | current]);

      if (active && selected) {
            cursor_x = 0;
            cursor_y = 1 + row;
      }

      pos = 0;
      print_buffer[pos++] = ' ';
      str = artist->name;
      if (album) {
            print_buffer[pos++] = ' ';
            print_buffer[pos++] = ' ';
            str = album->name;
      }
      pos += format_str(print_buffer + pos, str, tree_win_w - pos - 1);
      print_buffer[pos++] = ' ';
      print_buffer[pos++] = 0;
      dump_print_buffer(tree_win_y + row + 1, tree_win_x);
}

static inline void fopt_set_str(struct format_option *fopt, const char *str)
{
      BUG_ON(fopt->type != FO_STR);
      if (str) {
            fopt->fo_str = str;
            fopt->empty = 0;
      } else {
            fopt->empty = 1;
      }
}

static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
{
      BUG_ON(fopt->type != FO_INT);
      fopt->fo_int = value;
      fopt->empty = empty;
}

static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
{
      BUG_ON(fopt->type != FO_TIME);
      fopt->fo_time = value;
      fopt->empty = empty;
}

static void fill_track_fopts_track_info(struct track_info *info)
{
      char *filename;
      int num, disc;

      if (using_utf8) {
            filename = info->filename;
      } else {
            utf8_encode(info->filename);
            filename = conv_buffer;
      }
      disc = comments_get_int(info->comments, "discnumber");
      num = comments_get_int(info->comments, "tracknumber");

      fopt_set_str(&track_fopts[TF_ARTIST], comments_get_val(info->comments, "artist"));
      fopt_set_str(&track_fopts[TF_ALBUM], comments_get_val(info->comments, "album"));
      fopt_set_int(&track_fopts[TF_DISC], disc, disc == -1);
      fopt_set_int(&track_fopts[TF_TRACK], num, num == -1);
      fopt_set_str(&track_fopts[TF_TITLE], comments_get_val(info->comments, "title"));
      fopt_set_str(&track_fopts[TF_YEAR], comments_get_val(info->comments, "date"));
      fopt_set_str(&track_fopts[TF_GENRE], comments_get_val(info->comments, "genre"));
      fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
      fopt_set_str(&track_fopts[TF_PATHFILE], filename);
      if (is_url(info->filename)) {
            fopt_set_str(&track_fopts[TF_FILE], filename);
      } else {
            const char *f;

            f = strrchr(filename, '/');
            if (f) {
                  fopt_set_str(&track_fopts[TF_FILE], f + 1);
            } else {
                  fopt_set_str(&track_fopts[TF_FILE], filename);
            }
      }
}

static void print_track(struct window *win, int row, struct iter *iter)
{
      struct tree_track *track;
      struct iter sel;
      int current, selected, active;

      track = iter_to_tree_track(iter);
      current = lib_cur_track == track;
      window_get_sel(win, &sel);
      selected = iters_equal(iter, &sel);
      active = lib_cur_win == lib_track_win;
      bkgdset(pairs[(active << 2) | (selected << 1) | current]);

      if (active && selected) {
            cursor_x = track_win_x;
            cursor_y = 1 + row;
      }

      fill_track_fopts_track_info(tree_track_info(track));

      if (track_info_has_tag(tree_track_info(track))) {
            format_print(print_buffer, track_win_w, track_win_format, track_fopts);
      } else {
            format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
      }
      dump_print_buffer(track_win_y + row + 1, track_win_x);
}

/* used by print_editable only */
static struct simple_track *current_track;

static void print_editable(struct window *win, int row, struct iter *iter)
{
      struct simple_track *track;
      struct iter sel;
      int current, selected, active;

      track = iter_to_simple_track(iter);
      current = current_track == track;
      window_get_sel(win, &sel);
      selected = iters_equal(iter, &sel);

      if (selected) {
            cursor_x = 0;
            cursor_y = 1 + row;
      }

      active = 1;
      if (!selected && track->marked) {
            selected = 1;
            active = 0;
      }

      bkgdset(pairs[(active << 2) | (selected << 1) | current]);

      fill_track_fopts_track_info(track->info);

      if (track_info_has_tag(track->info)) {
            format_print(print_buffer, COLS, list_win_format, track_fopts);
      } else {
            format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
      }
      dump_print_buffer(row + 1, 0);
}

static void print_browser(struct window *win, int row, struct iter *iter)
{
      struct browser_entry *e;
      struct iter sel;
      int selected;

      e = iter_to_browser_entry(iter);
      window_get_sel(win, &sel);
      selected = iters_equal(iter, &sel);
      if (selected) {
            int active = 1;
            int current = 0;

            bkgdset(pairs[(active << 2) | (selected << 1) | current]);
      } else {
            if (e->type == BROWSER_ENTRY_DIR) {
                  bkgdset(pairs[CURSED_DIR]);
            } else {
                  bkgdset(pairs[CURSED_WIN]);
            }
      }

      if (selected) {
            cursor_x = 0;
            cursor_y = 1 + row;
      }

      /* file name encoding == terminal encoding. no need to convert */
      if (using_utf8) {
            sprint(row + 1, 0, e->name, COLS);
      } else {
            sprint_ascii(row + 1, 0, e->name, COLS);
      }
}

static void print_filter(struct window *win, int row, struct iter *iter)
{
      char buf[256];
      struct filter_entry *e = iter_to_filter_entry(iter);
      struct iter sel;
      /* window active? */
      int active = 1;
      /* row selected? */
      int selected;
      /* is the filter currently active? */
      int current = !!e->act_stat;
      const char stat_chars[3] = " *!";
      int ch1, ch2, ch3, pos;

      window_get_sel(win, &sel);
      selected = iters_equal(iter, &sel);
      bkgdset(pairs[(active << 2) | (selected << 1) | current]);

      if (selected) {
            cursor_x = 0;
            cursor_y = 1 + row;
      }

      ch1 = ' ';
      ch3 = ' ';
      if (e->sel_stat != e->act_stat) {
            ch1 = '[';
            ch3 = ']';
      }
      ch2 = stat_chars[e->sel_stat];
      snprintf(buf, sizeof(buf), "%c%c%c%-15s  %s", ch1, ch2, ch3, e->name, e->filter);
      pos = format_str(print_buffer, buf, COLS - 1);
      print_buffer[pos++] = ' ';
      print_buffer[pos] = 0;
      dump_print_buffer(row + 1, 0);
}

static void print_help(struct window *win, int row, struct iter *iter)
{
      struct iter sel;
      int selected;
      int pos;
      int active = 1;
      char buf[512];
      const struct help_entry *e = iter_to_help_entry(iter);
      const struct cmus_opt *opt;

      window_get_sel(win, &sel);
      selected = iters_equal(iter, &sel);
      bkgdset(pairs[(active << 2) | (selected << 1)]);

      if (selected) {
            cursor_x = 0;
            cursor_y = 1 + row;
      }

      switch (e->type) {
      case HE_TEXT:
            snprintf(buf, sizeof(buf), " %s", e->text);
            break;
      case HE_BOUND:
            snprintf(buf, sizeof(buf), " %-8s %-14s %s",
                  key_context_names[e->binding->ctx],
                  e->binding->key->name,
                  e->binding->cmd);
            break;
      case HE_UNBOUND:
            snprintf(buf, sizeof(buf), " %s", e->command->name);
            break;
      case HE_OPTION:
            opt = e->option;
            snprintf(buf, sizeof(buf), " %-29s ", opt->name);
            opt->get(opt->id, buf + strlen(buf));
            break;
      }
      pos = format_str(print_buffer, buf, COLS - 1);
      print_buffer[pos++] = ' ';
      print_buffer[pos] = 0;
      dump_print_buffer(row + 1, 0);
}

static void update_window(struct window *win, int x, int y, int w, const char *title,
            void (*print)(struct window *, int, struct iter *))
{
      struct iter iter;
      int nr_rows;
      int c, i;

      win->changed = 0;

      bkgdset(pairs[CURSED_WIN_TITLE]);
      c = snprintf(print_buffer, w + 1, " %s", title);
      if (c > w)
            c = w;
      memset(print_buffer + c, ' ', w - c + 1);
      print_buffer[w] = 0;
      dump_print_buffer(y, x);
      nr_rows = window_get_nr_rows(win);
      i = 0;
      if (window_get_top(win, &iter)) {
            while (i < nr_rows) {
                  print(win, i, &iter);
                  i++;
                  if (!window_get_next(win, &iter))
                        break;
            }
      }

      bkgdset(pairs[0]);
      memset(print_buffer, ' ', w);
      print_buffer[w] = 0;
      while (i < nr_rows) {
            dump_print_buffer(y + i + 1, x);
            i++;
      }
}

static void update_tree_window(void)
{
      update_window(lib_tree_win, tree_win_x, tree_win_y,
                  tree_win_w, "Artist / Album", print_tree);
}

static void update_track_window(void)
{
      char title[512];

      /* it doesn't matter what format options we use because the format
       * string does not contain any format charaters */
      format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
      update_window(lib_track_win, track_win_x, track_win_y,
                  track_win_w, title, print_track);
}

static const char *pretty(const char *path)
{
      static int home_len = -1;
      static char buf[256];

      if (home_len == -1)
            home_len = strlen(home_dir);

      if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
            return path;

      buf[0] = '~';
      strcpy(buf + 1, path + home_len);
      return buf;
}

static const char * const sorted_names[2] = { "", "sorted by " };

static void update_editable_window(struct editable *e, const char *title, const char *filename)
{
      char buf[512];
      int pos;

      if (filename) {
            if (using_utf8) {
                  /* already UTF-8 */
            } else {
                  utf8_encode(filename);
                  filename = conv_buffer;
            }
            snprintf(buf, sizeof(buf), "%s %s - %d tracks", title,
                        pretty(filename), e->nr_tracks);
      } else {
            snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
      }

      if (e->nr_marked) {
            pos = strlen(buf);
            snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
      }
      pos = strlen(buf);
      snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
                  sorted_names[e->sort_str[0] != 0], e->sort_str);

      update_window(e->win, 0, 0, COLS, buf, &print_editable);
}

static void update_sorted_window(void)
{
      current_track = (struct simple_track *)lib_cur_track;
      update_editable_window(&lib_editable, "Library", lib_filename);
}

static void update_pl_window(void)
{
      current_track = pl_cur_track;
      update_editable_window(&pl_editable, "Playlist", pl_filename);
}

static void update_play_queue_window(void)
{
      current_track = NULL;
      update_editable_window(&pq_editable, "Play Queue", NULL);
}

static void update_browser_window(void)
{
      char title[512];
      char *dirname;

      if (using_utf8) {
            /* already UTF-8 */
            dirname = browser_dir;
      } else {
            utf8_encode(browser_dir);
            dirname = conv_buffer;
      }
      snprintf(title, sizeof(title), "Browser - %s", dirname);
      update_window(browser_win, 0, 0, COLS, title, print_browser);
}

static void update_filters_window(void)
{
      update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
}

static void update_help_window(void)
{
      update_window(help_win, 0, 0, COLS, "Settings", print_help);
}

static void draw_separator(void)
{
      int row;

      bkgdset(pairs[CURSED_WIN_TITLE]);
      mvaddch(0, tree_win_w, ' ');
      bkgdset(pairs[CURSED_SEPARATOR]);
      for (row = 1; row < LINES - 3; row++)
            mvaddch(row, tree_win_w, ACS_VLINE);
}

static void do_update_view(int full)
{
      cursor_x = -1;
      cursor_y = -1;

      switch (cur_view) {
      case TREE_VIEW:
            editable_lock();
            if (full || lib_tree_win->changed)
                  update_tree_window();
            if (full || lib_track_win->changed)
                  update_track_window();
            editable_unlock();
            draw_separator();
            break;
      case SORTED_VIEW:
            editable_lock();
            update_sorted_window();
            editable_unlock();
            break;
      case PLAYLIST_VIEW:
            editable_lock();
            update_pl_window();
            editable_unlock();
            break;
      case QUEUE_VIEW:
            editable_lock();
            update_play_queue_window();
            editable_unlock();
            break;
      case BROWSER_VIEW:
            update_browser_window();
            break;
      case FILTERS_VIEW:
            update_filters_window();
            break;
      case HELP_VIEW:
            update_help_window();
            break;
      }
}

static void do_update_statusline(void)
{
      static const char *status_strs[] = { ".", ">", "|" };
      static const char *cont_strs[] = { " ", "C" };
      static const char *repeat_strs[] = { " ", "R" };
      static const char *shuffle_strs[] = { " ", "S" };
      int buffer_fill, vol, vol_left, vol_right;
      int duration = -1;
      char *msg;
      char format[80];

      editable_lock();
      fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib_editable.total_time :
                  pl_editable.total_time, 0);
      editable_unlock();

      fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
      fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
      fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);

      player_info_lock();

      if (player_info.ti)
            duration = player_info.ti->duration;

      if (volume_max == 0) {
            vol_left = vol_right = vol = -1;
      } else {
            vol_left = scale_to_percentage(player_info.vol_left, volume_max);
            vol_right = scale_to_percentage(player_info.vol_right, volume_max);
            vol = (vol_left + vol_right + 1) / 2;
      }
      buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);

      fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);

      if (show_remaining_time && duration != -1) {
            fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
      } else {
            fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
      }

      fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
      fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
      fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
      fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
      fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
      fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);

      strcpy(format, " %s %p ");
      if (duration != -1)
            strcat(format, "/ %d ");
      strcat(format, "- %t ");
      if (volume_max != 0) {
            if (player_info.vol_left != player_info.vol_right) {
                  strcat(format, "vol: %l,%r ");
            } else {
                  strcat(format, "vol: %v ");
            }
      }
      if (player_info.ti && is_url(player_info.ti->filename))
            strcat(format, "buf: %b ");
      strcat(format, "%=");
      if (play_library) {
            /* artist/album modes work only in lib */
            if (shuffle) {
                  /* shuffle overrides sorted mode */
                  strcat(format, "%L from library");
            } else if (play_sorted) {
                  strcat(format, "%L from sorted library");
            } else {
                  strcat(format, "%L from library");
            }
      } else {
            strcat(format, "playlist");
      }
      strcat(format, " | %1C%1R%1S ");
      format_print(print_buffer, COLS, format, status_fopts);

      msg = player_info.error_msg;
      player_info.error_msg = NULL;

      player_info_unlock();

      bkgdset(pairs[CURSED_STATUSLINE]);
      dump_print_buffer(LINES - 2, 0);

      if (msg) {
            error_msg("%s", msg);
            free(msg);
      }
}

static void dump_buffer(const char *buffer)
{
      if (using_utf8) {
            addstr(buffer);
      } else {
            utf8_decode(buffer);
            addstr(conv_buffer);
      }
}

static void do_update_commandline(void)
{
      char *str;
      int w, idx;
      char ch;

      move(LINES - 1, 0);
      if (error_buf[0]) {
            if (msg_is_error) {
                  bkgdset(pairs[CURSED_ERROR]);
            } else {
                  bkgdset(pairs[CURSED_INFO]);
            }
            addstr(error_buf);
            clrtoeol();
            return;
      }
      bkgdset(pairs[CURSED_COMMANDLINE]);
      if (input_mode == NORMAL_MODE) {
            clrtoeol();
            return;
      }

      str = cmdline.line;
      if (!using_utf8) {
            /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
             * characters are invalid UTF-8 so it really is in locale's
             * encoding.
             *
             * This code should be safe because cmdline.bpos ==
             * cmdline.cpos as every non-ASCII character is counted as one
             * invalid UTF-8 byte.
             *
             * NOTE: This has nothing to do with widths of printed
             * characters.  I.e. even if there were control characters
             * (displayed as <xx>) there would be no problem because bpos
             * still equals to cpos, I think.
             */
            utf8_encode(cmdline.line);
            str = conv_buffer;
      }

      /* COMMAND_MODE or SEARCH_MODE */
      w = u_str_width(str);
      ch = ':';
      if (input_mode == SEARCH_MODE)
            ch = search_direction == SEARCH_FORWARD ? '/' : '?';

      if (w <= COLS - 2) {
            addch(ch);
            idx = u_copy_chars(print_buffer, str, &w);
            print_buffer[idx] = 0;
            dump_buffer(print_buffer);
            clrtoeol();
      } else {
            /* keep cursor as far right as possible */
            int skip, width, cw;

            /* cursor pos (width, not chars. doesn't count the ':') */
            cw = u_str_nwidth(str, cmdline.cpos);

            skip = cw + 2 - COLS;
            if (skip > 0) {
                  /* skip the ':' */
                  skip--;

                  /* skip rest (if any) */
                  idx = u_skip_chars(str, &skip);

                  width = COLS;
                  idx = u_copy_chars(print_buffer, str + idx, &width);
                  while (width < COLS) {
                        /* cursor is at end of the buffer
                         * print 1, 2 or 3 spaces
                         *
                         * To clarify:
                         *
                         * If the last _skipped_ character was double-width we may need
                         * to print 2 spaces.
                         *
                         * If the last _skipped_ character was invalid UTF-8 we may need
                         * to print 3 spaces.
                         */
                        print_buffer[idx++] = ' ';
                        width++;
                  }
                  print_buffer[idx] = 0;
                  dump_buffer(print_buffer);
            } else {
                  /* print ':' + COLS - 1 chars */
                  addch(ch);
                  width = COLS - 1;
                  idx = u_copy_chars(print_buffer, str, &width);
                  print_buffer[idx] = 0;
                  dump_buffer(print_buffer);
            }
      }
}

/* lock player_info! */
static const char *get_stream_title(void)
{
      static char stream_title[255 * 16 + 1];
      char *ptr, *title;

      ptr = strstr(player_info.metadata, "StreamTitle='");
      if (ptr == NULL)
            return NULL;
      ptr += 13;
      title = ptr;
      while (*ptr) {
            if (*ptr == '\'' && *(ptr + 1) == ';') {
                  memcpy(stream_title, title, ptr - title);
                  stream_title[ptr - title] = 0;
                  return stream_title;
            }
            ptr++;
      }
      return NULL;
}

static void set_title(const char *title)
{
      if (!set_term_title)
            return;

      if (t_ts) {
            printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
            fflush(stdout);
      }
}

static void do_update_titleline(void)
{
      bkgdset(pairs[CURSED_TITLELINE]);
      player_info_lock();
      if (player_info.ti) {
            int i, use_alt_format = 0;
            char *wtitle;

            fill_track_fopts_track_info(player_info.ti);
            if (is_url(player_info.ti->filename)) {
                  const char *title = get_stream_title();

                  if (title == NULL)
                        use_alt_format = 1;
                  fopt_set_str(&track_fopts[TF_TITLE], title);
            } else {
                  use_alt_format = !track_info_has_tag(player_info.ti);
            }

            if (use_alt_format) {
                  format_print(print_buffer, COLS, current_alt_format, track_fopts);
            } else {
                  format_print(print_buffer, COLS, current_format, track_fopts);
            }
            dump_print_buffer(LINES - 3, 0);

            /* set window title */
            if (use_alt_format) {
                  format_print(print_buffer, sizeof(print_buffer) - 1,
                              window_title_alt_format, track_fopts);
            } else {
                  format_print(print_buffer, sizeof(print_buffer) - 1,
                              window_title_format, track_fopts);
            }

            /* remove whitespace */
            i = strlen(print_buffer) - 1;
            while (i > 0 && print_buffer[i] == ' ')
                  i--;
            print_buffer[i + 1] = 0;

            if (using_utf8) {
                  wtitle = print_buffer;
            } else {
                  utf8_decode(print_buffer);
                  wtitle = conv_buffer;
            }

            set_title(wtitle);
      } else {
            move(LINES - 3, 0);
            clrtoeol();

            set_title("cmus " VERSION);
      }
      player_info_unlock();
}

static int cmdline_cursor_column(void)
{
      char *str;
      int cw, skip, s;

      str = cmdline.line;
      if (!using_utf8) {
            /* see do_update_commandline */
            utf8_encode(cmdline.line);
            str = conv_buffer;
      }

      /* width of the text in the buffer before cursor */
      cw = u_str_nwidth(str, cmdline.cpos);

      if (1 + cw < COLS) {
            /* whole line is visible */
            return 1 + cw;
      }

      /* beginning of cmdline is not visible */

      /* check if the first visible char in cmdline would be halved
       * double-width character (or invalid byte <xx>) which is not possible.
       * we need to skip the whole character and move cursor to COLS - 2
       * column. */
      skip = cw + 2 - COLS;

      /* skip the ':' */
      skip--;

      /* skip rest */
      s = skip;
      u_skip_chars(str, &s);
      if (s > skip) {
            /* the last skipped char was double-width or <xx> */
            return COLS - 1 - (s - skip);
      }
      return COLS - 1;
}

static void post_update(void)
{
      /* refresh makes cursor visible at least for urxvt */
      if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
            move(LINES - 1, cmdline_cursor_column());
            refresh();
            curs_set(1);
      } else {
            if (cursor_x >= 0) {
                  move(cursor_y, cursor_x);
            } else {
                  move(LINES - 1, 0);
            }
            refresh();
            curs_set(0);
      }
}

void update_titleline(void)
{
      curs_set(0);
      do_update_titleline();
      post_update();
}

void update_full(void)
{
      if (!ui_initialized)
            return;

      curs_set(0);

      do_update_view(1);
      do_update_titleline();
      do_update_statusline();
      do_update_commandline();

      post_update();
}

static void update_commandline(void)
{
      curs_set(0);
      do_update_commandline();
      post_update();
}

void update_statusline(void)
{
      if (!ui_initialized)
            return;

      curs_set(0);
      do_update_statusline();
      post_update();
}

void info_msg(const char *format, ...)
{
      va_list ap;

      va_start(ap, format);
      vsnprintf(error_buf, sizeof(error_buf), format, ap);
      va_end(ap);

      msg_is_error = 0;

      update_commandline();
}

void error_msg(const char *format, ...)
{
      va_list ap;

      strcpy(error_buf, "Error: ");
      va_start(ap, format);
      vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
      va_end(ap);

      d_print("%s\n", error_buf);

      msg_is_error = 1;
      error_count++;

      if (ui_initialized) {
            error_time = time(NULL);
            update_commandline();
      } else {
            warn("%s\n", error_buf);
            error_buf[0] = 0;
      }
}

int yes_no_query(const char *format, ...)
{
      char buffer[512];
      va_list ap;
      int ret = 0;

      va_start(ap, format);
      vsnprintf(buffer, sizeof(buffer), format, ap);
      va_end(ap);

      move(LINES - 1, 0);
      bkgdset(pairs[CURSED_INFO]);

      /* no need to convert buffer.
       * it is always encoded in the right charset (assuming filenames are
       * encoded in same charset as LC_CTYPE).
       */

      addstr(buffer);
      clrtoeol();
      refresh();

      while (1) {
            int ch = getch();

            if (ch == ERR || ch == 0)
                  continue;
            if (ch == 'y')
                  ret = 1;
            break;
      }
      update_commandline();
      return ret;
}

void search_not_found(void)
{
      const char *what = "Track";

      if (search_restricted) {
            switch (cur_view) {
            case TREE_VIEW:
                  what = "Artist/album";
                  break;
            case SORTED_VIEW:
            case PLAYLIST_VIEW:
            case QUEUE_VIEW:
                  what = "Title";
                  break;
            case BROWSER_VIEW:
                  what = "File/Directory";
                  break;
            case FILTERS_VIEW:
                  what = "Filter";
                  break;
            case HELP_VIEW:
                  what = "Binding/command/option";
                  break;
            }
      } else {
            switch (cur_view) {
            case TREE_VIEW:
            case SORTED_VIEW:
            case PLAYLIST_VIEW:
            case QUEUE_VIEW:
                  what = "Track";
                  break;
            case BROWSER_VIEW:
                  what = "File/Directory";
                  break;
            case FILTERS_VIEW:
                  what = "Filter";
                  break;
            case HELP_VIEW:
                  what = "Binding/command/option";
                  break;
            }
      }
      info_msg("%s not found: %s", what, search_str ? : "");
}

void set_view(int view)
{
      if (view == cur_view)
            return;

      cur_view = view;
      switch (cur_view) {
      case TREE_VIEW:
            searchable = tree_searchable;
            break;
      case SORTED_VIEW:
            searchable = lib_editable.searchable;
            break;
      case PLAYLIST_VIEW:
            searchable = pl_editable.searchable;
            break;
      case QUEUE_VIEW:
            searchable = pq_editable.searchable;
            break;
      case BROWSER_VIEW:
            searchable = browser_searchable;
            break;
      case FILTERS_VIEW:
            searchable = filters_searchable;
            break;
      case HELP_VIEW:
            searchable = help_searchable;
            update_help_window();
            break;
      }

      curs_set(0);
      do_update_view(1);
      post_update();
}

void enter_command_mode(void)
{
      error_buf[0] = 0;
      error_time = 0;
      input_mode = COMMAND_MODE;
      update_commandline();
}

void enter_search_mode(void)
{
      error_buf[0] = 0;
      error_time = 0;
      input_mode = SEARCH_MODE;
      search_direction = SEARCH_FORWARD;
      update_commandline();
}

void enter_search_backward_mode(void)
{
      error_buf[0] = 0;
      error_time = 0;
      input_mode = SEARCH_MODE;
      search_direction = SEARCH_BACKWARD;
      update_commandline();
}

void update_colors(void)
{
      int i;

      if (!ui_initialized)
            return;

      for (i = 0; i < NR_CURSED; i++) {
            int bg = colors[cursed_to_bg_idx[i]];
            int fg = colors[cursed_to_fg_idx[i]];
            int pair = i + 1;

            if (fg >= 8 && fg <= 15) {
                  /* fg colors 8..15 are special (0..7 + bold) */
                  init_pair(pair, fg & 7, bg);
                  pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0);
            } else {
                  init_pair(pair, fg, bg);
                  pairs[i] = COLOR_PAIR(pair);
            }
      }
}

static void clear_error(void)
{
      time_t t = time(NULL);

      /* prevent accidental clearing of error messages */
      if (t - error_time < 2)
            return;

      if (error_buf[0]) {
            error_time = 0;
            error_buf[0] = 0;
            update_commandline();
      }
}

/* screen updates }}} */

static void spawn_status_program(void)
{
      static const char *status_strs[] = { "stopped", "playing", "paused" };
      const char *stream_title = NULL;
      char *argv[32];
      int i, status;

      if (status_display_program == NULL || status_display_program[0] == 0)
            return;

      player_info_lock();
      status = player_info.status;
      if (status == 1)
            stream_title = get_stream_title();

      i = 0;
      argv[i++] = xstrdup(status_display_program);

      argv[i++] = xstrdup("status");
      argv[i++] = xstrdup(status_strs[status]);
      if (player_info.ti) {
            static const char *keys[] = {
                  "artist", "album", "discnumber", "tracknumber", "title", "date", NULL
            };
            int j;

            if (is_url(player_info.ti->filename)) {
                  argv[i++] = xstrdup("url");
                  argv[i++] = xstrdup(player_info.ti->filename);
                  if (stream_title) {
                        argv[i++] = xstrdup("title");
                        argv[i++] = xstrdup(stream_title);
                  }
            } else {
                  char buf[32];

                  argv[i++] = xstrdup("file");
                  argv[i++] = xstrdup(player_info.ti->filename);
                  for (j = 0; keys[j]; j++) {
                        const char *key = keys[j];
                        const char *val;

                        val = comments_get_val(player_info.ti->comments, key);
                        if (val) {
                              argv[i++] = xstrdup(key);
                              argv[i++] = xstrdup(val);
                        }
                  }
                  snprintf(buf, sizeof(buf), "%d", player_info.ti->duration);
                  argv[i++] = xstrdup("duration");
                  argv[i++] = xstrdup(buf);
            }
      }
      argv[i++] = NULL;
      player_info_unlock();

      if (spawn(argv, &status) == -1)
            error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
      for (i = 0; argv[i]; i++)
            free(argv[i]);
}

static int ctrl_c_pressed = 0;

static void sig_int(int sig)
{
      ctrl_c_pressed = 1;
}

static int needs_to_resize = 1;

static void sig_winch(int sig)
{
      needs_to_resize = 1;
}

static int get_window_size(int *lines, int *columns)
{
      struct winsize ws;

      if (ioctl(0, TIOCGWINSZ, &ws) == -1)
            return -1;
      *columns = ws.ws_col;
      *lines = ws.ws_row;
      return 0;
}

static void resize_tree_view(int w, int h)
{
      tree_win_w = w / 3;
      track_win_w = w - tree_win_w - 1;
      if (tree_win_w < 8)
            tree_win_w = 8;
      if (track_win_w < 8)
            track_win_w = 8;
      tree_win_x = 0;
      tree_win_y = 0;
      track_win_x = tree_win_w + 1;
      track_win_y = 0;

      h--;
      window_set_nr_rows(lib_tree_win, h);
      window_set_nr_rows(lib_track_win, h);
}

static void update(void)
{
      int needs_view_update = 0;
      int needs_title_update = 0;
      int needs_status_update = 0;
      int needs_command_update = 0;
      int needs_spawn = 0;

      if (needs_to_resize) {
            int w, h;
            int columns, lines;

            if (get_window_size(&lines, &columns) == 0) {
                  needs_to_resize = 0;
                  resizeterm(lines, columns);
                  w = COLS;
                  h = LINES - 3;
                  if (w < 16)
                        w = 16;
                  if (h < 8)
                        h = 8;
                  editable_lock();
                  resize_tree_view(w, h);
                  window_set_nr_rows(lib_editable.win, h - 1);
                  window_set_nr_rows(pl_editable.win, h - 1);
                  window_set_nr_rows(pq_editable.win, h - 1);
                  window_set_nr_rows(filters_win, h - 1);
                  window_set_nr_rows(help_win, h - 1);
                  window_set_nr_rows(browser_win, h - 1);
                  editable_unlock();
                  needs_title_update = 1;
                  needs_status_update = 1;
                  needs_command_update = 1;
            }
      }

      player_info_lock();
      editable_lock();

      needs_spawn = player_info.status_changed || player_info.file_changed ||
            player_info.metadata_changed;

      if (player_info.file_changed) {
            player_info.file_changed = 0;
            needs_title_update = 1;
            needs_status_update = 1;
      }
      if (player_info.metadata_changed) {
            player_info.metadata_changed = 0;
            needs_title_update = 1;
      }
      if (player_info.position_changed || player_info.status_changed || player_info.vol_changed) {
            player_info.position_changed = 0;
            player_info.status_changed = 0;
            player_info.vol_changed = 0;

            needs_status_update = 1;
      }
      switch (cur_view) {
      case TREE_VIEW:
            needs_view_update += lib_tree_win->changed || lib_track_win->changed;
            break;
      case SORTED_VIEW:
            needs_view_update += lib_editable.win->changed;
            break;
      case PLAYLIST_VIEW:
            needs_view_update += pl_editable.win->changed;
            break;
      case QUEUE_VIEW:
            needs_view_update += pq_editable.win->changed;
            break;
      case BROWSER_VIEW:
            needs_view_update += browser_win->changed;
            break;
      case FILTERS_VIEW:
            needs_view_update += filters_win->changed;
            break;
      case HELP_VIEW:
            needs_view_update += help_win->changed;
            break;
      }

      /* total time changed? */
      if (play_library) {
            needs_status_update += lib_editable.win->changed;
            lib_editable.win->changed = 0;
      } else {
            needs_status_update += pl_editable.win->changed;
            lib_editable.win->changed = 0;
      }

      editable_unlock();
      player_info_unlock();

      if (needs_spawn)
            spawn_status_program();

      if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
            curs_set(0);

            if (needs_view_update)
                  do_update_view(0);
            if (needs_title_update)
                  do_update_titleline();
            if (needs_status_update)
                  do_update_statusline();
            if (needs_command_update)
                  do_update_commandline();
            post_update();
      }
}

static void handle_ch(uchar ch)
{
      clear_error();
      if (input_mode == NORMAL_MODE) {
            normal_mode_ch(ch);
      } else if (input_mode == COMMAND_MODE) {
            command_mode_ch(ch);
            update_commandline();
      } else if (input_mode == SEARCH_MODE) {
            search_mode_ch(ch);
            update_commandline();
      }
}

static void handle_key(int key)
{
      clear_error();
      if (input_mode == NORMAL_MODE) {
            normal_mode_key(key);
      } else if (input_mode == COMMAND_MODE) {
            command_mode_key(key);
            update_commandline();
      } else if (input_mode == SEARCH_MODE) {
            search_mode_key(key);
            update_commandline();
      }
}

static void u_getch(void)
{
      int key;
      int bit = 7;
      int mask = (1 << 7);
      uchar u, ch;

      key = getch();
      if (key == ERR || key == 0)
            return;

      if (key > 255) {
            handle_key(key);
            return;
      }

      ch = (unsigned char)key;
      while (bit > 0 && ch & mask) {
            mask >>= 1;
            bit--;
      }
      if (bit == 7) {
            /* ascii */
            u = ch;
      } else {
            int count;

            u = ch & ((1 << bit) - 1);
            count = 6 - bit;
            while (count) {
                  key = getch();
                  if (key == ERR || key == 0)
                        return;

                  ch = (unsigned char)key;
                  u = (u << 6) | (ch & 63);
                  count--;
            }
      }
      handle_ch(u);
}

static void main_loop(void)
{
      int rc, fd_high;

      fd_high = server_socket;
      while (cmus_running) {
            fd_set set;
            struct timeval tv;

            update();

            FD_ZERO(&set);
            FD_SET(0, &set);
            FD_SET(server_socket, &set);

            /* Timeout must be so small that screen updates seem instant.
             * Only affects changes done in other threads (worker, player).
             *
             * Too small timeout makes window updates too fast (wastes CPU).
             *
             * Too large timeout makes status line (position) updates too slow.
             * The timeout is accuracy of player position.
             */
            tv.tv_sec = 0;
            tv.tv_usec = 100e3;
            rc = select(fd_high + 1, &set, NULL, NULL, &tv);
            if (rc <= 0) {
                  if (ctrl_c_pressed) {
                        handle_ch(0x03);
                        ctrl_c_pressed = 0;
                  }
                  continue;
            }

            if (FD_ISSET(server_socket, &set))
                  server_serve();
            if (FD_ISSET(0, &set)) {
                  if (using_utf8) {
                        u_getch();
                  } else {
                        int key = getch();

                        if (key != ERR && key != 0) {
                              if (key > 255) {
                                    handle_key(key);
                              } else {
                                    uchar ch = key;

                                    if (ch > 0x7f)
                                          ch |= U_INVALID_MASK;
                                    handle_ch(ch);
                              }
                        }
                  }
            }
      }
}

static int get_next(struct track_info **ti)
{
      struct track_info *info;

      editable_lock();
      info = play_queue_remove();
      if (info == NULL) {
            if (play_library) {
                  info = lib_set_next();
            } else {
                  info = pl_set_next();
            }
      }
      editable_unlock();

      if (info == NULL)
            return -1;

      *ti = info;
      return 0;
}

static const struct player_callbacks player_callbacks = {
      .get_next = get_next
};

static void init_curses(void)
{
      struct sigaction act;
      char *ptr, *term;

      sigemptyset(&act.sa_mask);
      act.sa_flags = 0;
      act.sa_handler = sig_int;
      sigaction(SIGINT, &act, NULL);

      sigemptyset(&act.sa_mask);
      act.sa_flags = 0;
      act.sa_handler = sig_winch;
      sigaction(SIGWINCH, &act, NULL);

      initscr();

      /* turn off kb buffering */
      cbreak();

      keypad(stdscr, TRUE);

      /* wait max 5 * 0.1 s if there are no keys available
       * doesn't really matter because we use select()
       */
      halfdelay(5);

      noecho();
      if (has_colors()) {
            start_color();
            use_default_colors();
      }
      d_print("Number of supported colors: %d\n", COLORS);
      ui_initialized = 1;

      /* this was disabled while initializing because it needs to be
       * called only once after all colors have been set
       */
      update_colors();

      ptr = print_buffer;
      t_ts = tgetstr("ts", &ptr);
      t_fs = tgetstr("fs", &ptr);
      d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);

      if (!t_fs)
            t_ts = NULL;

      if (!t_ts && (term = getenv("TERM"))) {
            /*
             * Eterm:            Eterm
             * aterm:            rxvt
             * mlterm:           xterm
             * terminal (xfce):  xterm
             * urxvt:            rxvt-unicode
             * xterm:            xterm, xterm-{,16,88,256}color
             */
            if (!strcmp(term, "screen")) {
                  t_ts = "\033_";
                  t_fs = "\033\\";
            } else if (!strncmp(term, "xterm", 5) ||
                     !strncmp(term, "rxvt", 4) ||
                     !strcmp(term, "Eterm")) {
                  /* \033]1;  change icon
                   * \033]2;  change title
                   * \033]0;  change both
                   */
                  t_ts = "\033]0;";
                  t_fs = "\007";
            }
      }
}

static void init_all(void)
{
      server_init(server_address);

      /* does not select output plugin */
      player_init(&player_callbacks);

      /* plugins have been loaded so we know what plugin options are available */
      options_add();

      lib_init();
      searchable = tree_searchable;
      pl_init();
      cmus_init();
      browser_init();
      filters_init();
      help_init();
      cmdline_init();
      commands_init();
      search_mode_init();

      /* almost everything must be initialized now */
      options_load();

      /* finally we can set the output plugin */
      player_set_op(output_plugin);

      player_get_volume(&player_info.vol_left, &player_info.vol_right);

      lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
      pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
      pl_filename = xstrdup(pl_autosave_filename);
      lib_filename = xstrdup(lib_autosave_filename);

      cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB);
      cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL);

      if (error_count) {
            char buf[16];

            warn("Press <enter> to continue.");
            fgets(buf, sizeof(buf), stdin);
      }
      help_add_all_unbound();

      init_curses();
}

static void exit_all(void)
{
      endwin();

      options_exit();

      server_exit();
      cmus_exit();
      cmus_save(lib_for_each, lib_autosave_filename);
      cmus_save(pl_for_each, pl_autosave_filename);

      player_exit();
      commands_exit();
      search_mode_exit();
      filters_exit();
      help_exit();
      browser_exit();
}

enum {
      FLAG_LISTEN,
      FLAG_PLUGINS,
      FLAG_HELP,
      FLAG_VERSION,
      NR_FLAGS
};

static struct option options[NR_FLAGS + 1] = {
      { 0, "listen", 1 },
      { 0, "plugins", 0 },
      { 0, "help", 0 },
      { 0, "version", 0 },
      { 0, NULL, 0 }
};

static const char *usage =
"Usage: %s [OPTION]...\n"
"Curses based music player.\n"
"\n"
"      --listen ADDR   listen on ADDR instead of ~/.cmus/socket\n"
"                      ADDR is either a UNIX socket or host[:port]\n"
"                      WARNING: using TCP/IP is insecure!\n"
"      --plugins       list available plugins and exit\n"
"      --help          display this help and exit\n"
"      --version       " VERSION "\n"
"\n"
"Use cmus-remote to control cmus from command line.\n"
"Report bugs to <cmus-devel@lists.sourceforge.net>.\n";

int main(int argc, char *argv[])
{
      int list_plugins = 0;

      program_name = argv[0];
      argv++;
      while (1) {
            int idx;
            char *arg;

            idx = get_option(&argv, options, &arg);
            if (idx < 0)
                  break;

            switch (idx) {
            case FLAG_HELP:
                  printf(usage, program_name);
                  return 0;
            case FLAG_VERSION:
                  printf("cmus " VERSION "\nCopyright 2004-2006 Timo Hirvonen\n");
                  return 0;
            case FLAG_PLUGINS:
                  list_plugins = 1;
                  break;
            case FLAG_LISTEN:
                  server_address = xstrdup(arg);
                  break;
            }
      }

      setlocale(LC_CTYPE, "");
#ifdef CODESET
      charset = nl_langinfo(CODESET);
#else
      charset = "ISO-8859-1";
#endif
      if (strcmp(charset, "UTF-8") == 0) {
            using_utf8 = 1;
      } else {
            using_utf8 = 0;
      }
      misc_init();
      if (server_address == NULL)
            server_address = xstrjoin(cmus_config_dir, "/socket");
      debug_init();
      d_print("charset = '%s'\n", charset);

      player_load_plugins();
      if (list_plugins) {
            player_dump_plugins();
            return 0;
      }
      init_all();
      main_loop();
      exit_all();
      return 0;
}

Generated by  Doxygen 1.6.0   Back to index