summaryrefslogtreecommitdiffstats
path: root/src/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c985
1 files changed, 985 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..e61665c
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,985 @@
+#define _XOPEN_SOURCE 600
+#define _DEFAULT_SOURCE
+
+#include "util.h"
+#include "list.h"
+#include "history.h"
+#include "tag.h"
+#include "track.h"
+#include "player.h"
+#include "listnav.h"
+#include "ref.h"
+
+#include "mpd/player.h"
+#include "curses.h"
+
+#include <dirent.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#undef KEY_ENTER
+#define KEY_ENTER '\n'
+#define KEY_SPACE ' '
+#define KEY_ESC '\x1b'
+#define KEY_TAB '\t'
+#define KEY_CTRL(c) ((c) & ~0x60)
+
+#define ATTR_ON(win, attr) wattr_on(win, attr, NULL)
+#define ATTR_OFF(win, attr) wattr_off(win, attr, NULL)
+
+enum {
+ STYLE_DEFAULT,
+ STYLE_TITLE,
+ STYLE_PANE_SEP,
+ STYLE_ITEM_SEL,
+ STYLE_ITEM_HOVER,
+ STYLE_ITEM_HOVER_SEL,
+ STYLE_COUNT
+};
+
+enum {
+ IMODE_EXECUTE,
+ IMODE_SEARCH
+};
+
+struct pane;
+
+typedef int (*pane_handler)(wint_t c);
+typedef void (*pane_updater)(struct pane *pane, int sel);
+typedef wchar_t *(*completion_generator)(const wchar_t *text,
+ int fwd, int state);
+typedef int (*cmd_handler)(const char *args);
+
+struct pane {
+ WINDOW *win;
+ int sx, sy, ex, ey;
+ int w, h;
+ int active;
+
+ pane_handler handle;
+ pane_updater update;
+};
+
+struct cmd {
+ const wchar_t *name;
+ cmd_handler func;
+};
+
+struct autoplay {
+ int enabled;
+ int shuffle;
+};
+
+static const char player_state_chars[] = {
+ [PLAYER_STATE_PAUSED] = '|',
+ [PLAYER_STATE_PLAYING] = '>',
+ [PLAYER_STATE_STOPPED] = '#'
+};
+
+static int style_attrs[STYLE_COUNT];
+
+static const char *datadir;
+static int scrw, scrh;
+static int quit;
+
+static struct pane *pane_sel, *pane_top_sel;
+static struct pane pane_left, pane_right, pane_bot;
+static struct pane *const panes[] = {
+ &pane_left,
+ &pane_right,
+ &pane_bot
+};
+
+static struct inputln completion_query = { 0 };
+static int completion_reset = 1;
+static completion_generator completion;
+
+static struct pane *cmd_pane;
+static struct history search_history, command_history;
+static struct history *history;
+static int cmd_show, cmd_mode;
+
+static struct autoplay autoplay;
+
+static struct pane *tag_pane;
+static struct listnav tag_nav;
+static struct link tags;
+static struct link tags_sel;
+
+static struct pane *track_pane;
+static struct listnav track_nav;
+static struct link playlist;
+static struct link tracks;
+
+static void init(void);
+static void cleanup(void *arg, int code);
+
+static void data_load(void);
+static void tracks_load(struct tag *tag);
+
+static void data_save(void);
+static void tracks_save(struct tag *tag);
+
+static void resize(void);
+static void pane_resize(struct pane *pane,
+ int sx, int sy, int ex, int ey);
+
+static void pane_init(struct pane *p, pane_handler handle, pane_updater update);
+static void pane_title(struct pane *pane, const char *title, int highlight);
+
+static void style_init(int style, int fg, int bg, int attr);
+static void style_on(WINDOW *win, int style);
+static void style_off(WINDOW *win, int style);
+
+static wchar_t *command_name_generator(const wchar_t *text, int fwd, int state);
+static wchar_t *track_name_generator(const wchar_t *text, int fwd, int state);
+
+static void select_current_tag(void);
+static int tag_input(wint_t c);
+static void tag_vis(struct pane *pane, int sel);
+
+static int track_input(wint_t c);
+static void track_vis(struct pane *pane, int sel);
+
+static int cmd_input(wint_t c);
+static void cmd_vis(struct pane *pane, int sel);
+
+static void queue_hover(void);
+static void main_input(wint_t c);
+static void main_vis(void);
+
+static int usercmd_save(const char *args);
+
+static void update_player(void);
+
+const struct cmd cmds[] = {
+ { L"save", usercmd_save },
+};
+
+void
+init(void)
+{
+ quit = 0;
+
+ setlocale(LC_ALL, "");
+
+ /* TODO handle as character intead of signal */
+ signal(SIGINT, exit);
+ atexit((void*) cleanup); /* this works */
+
+ history = &command_history;
+ history_init(&search_history);
+ history_init(&command_history);
+
+ datadir = getenv("TMUS_DATA");
+ ASSERT(datadir != NULL);
+ data_load();
+
+ player_init();
+
+ /* ncurses init */
+ initscr();
+ raw();
+ noecho();
+ halfdelay(1);
+ intrflush(stdscr, FALSE);
+ keypad(stdscr, TRUE);
+ start_color();
+ curs_set(0);
+ ESCDELAY = 0;
+
+ memset(style_attrs, 0, sizeof(style_attrs));
+ style_init(STYLE_DEFAULT, COLOR_WHITE, COLOR_BLACK, 0);
+ style_init(STYLE_TITLE, COLOR_WHITE, COLOR_BLUE, A_BOLD);
+ style_init(STYLE_PANE_SEP, COLOR_BLUE, COLOR_BLACK, 0);
+ style_init(STYLE_ITEM_SEL, COLOR_YELLOW, COLOR_BLACK, A_BOLD);
+ style_init(STYLE_ITEM_HOVER, COLOR_WHITE, COLOR_BLUE, 0);
+ style_init(STYLE_ITEM_HOVER_SEL, COLOR_YELLOW, COLOR_BLUE, A_BOLD);
+
+ pane_init((tag_pane = &pane_left), tag_input, tag_vis);
+ pane_init((track_pane = &pane_right), track_input, track_vis);
+ pane_init((cmd_pane = &pane_bot), cmd_input, cmd_vis);
+
+ pane_sel = &pane_left;
+ pane_top_sel = pane_sel;
+
+ playlist = LIST_HEAD;
+ tags_sel = LIST_HEAD;
+
+ autoplay.enabled = 0;
+ autoplay.shuffle = 0;
+
+ listnav_init(&tag_nav);
+ listnav_init(&track_nav);
+}
+
+void
+cleanup(void* arg, int code)
+{
+ if (code) return;
+
+ delwin(pane_left.win);
+ delwin(pane_right.win);
+ delwin(pane_bot.win);
+ endwin();
+
+ data_save();
+
+ player_free();
+
+ history_free(&search_history);
+ history_free(&command_history);
+}
+
+void
+data_load(void)
+{
+ struct dirent *ent;
+ struct tag *tag;
+ DIR *dir;
+
+ tags = LIST_HEAD;
+
+ dir = opendir(datadir);
+ ASSERT(dir != NULL);
+ while ((ent = readdir(dir))) {
+ if (ent->d_type != DT_DIR)
+ continue;
+ if (!strcmp(ent->d_name, "."))
+ continue;
+ if (!strcmp(ent->d_name, ".."))
+ continue;
+
+ tag = malloc(sizeof(struct tag));
+ ASSERT(tag != NULL);
+ tag->fname = strdup(ent->d_name);
+ ASSERT(tag->fname != NULL);
+ tag->fpath = aprintf("%s/%s", datadir, ent->d_name);
+ ASSERT(tag->fpath != NULL);
+ tag->name = sanitized(tag->fname);
+ ASSERT(tag->name != NULL);
+ tag->link = LINK_EMPTY;
+ list_push_back(&tags, LINK(tag));
+
+ tracks_load(tag);
+ }
+ closedir(dir);
+}
+
+void
+tracks_load(struct tag *tag)
+{
+ struct dirent *ent;
+ struct track *track;
+ struct ref *ref;
+ DIR *dir;
+
+ dir = opendir(tag->fpath);
+ ASSERT(dir != NULL);
+ while ((ent = readdir(dir))) {
+ if (ent->d_type != DT_REG)
+ continue;
+ if (!strcmp(ent->d_name, "."))
+ continue;
+ if (!strcmp(ent->d_name, ".."))
+ continue;
+
+ track = track_init(tag->fpath, ent->d_name);
+ ref = ref_init(tag);
+ list_push_back(&track->tags, LINK(ref));
+ list_push_back(&tracks, LINK(track));
+ }
+ closedir(dir);
+}
+
+void
+data_save(void)
+{
+
+}
+
+void
+tracks_save(struct tag *tag)
+{
+
+}
+
+void
+resize(void)
+{
+ int i, leftw;
+
+ getmaxyx(stdscr, scrh, scrw);
+
+ if (scrw < 10 || scrh < 4) {
+ clear();
+ printw("Term too small..");
+ refresh();
+ usleep(10000);
+ }
+
+ leftw = MIN(40, 0.3f * scrw);
+ pane_resize(&pane_left, 0, 0, leftw, scrh - 3);
+ pane_resize(&pane_right, pane_left.ex + 1, 0, scrw, scrh - 3);
+ pane_resize(&pane_bot, 0, scrh - 3, scrw, scrh);
+}
+
+void
+pane_resize(struct pane *pane, int sx, int sy, int ex, int ey)
+{
+ pane->sx = sx;
+ pane->sy = sy;
+ pane->ex = ex;
+ pane->ey = ey;
+ pane->w = pane->ex - pane->sx;
+ pane->h = pane->ey - pane->sy;
+
+ pane->active = (pane->w > 0 && pane->h > 0);
+ if (pane->active) {
+ wresize(pane->win, pane->h, pane->w);
+ mvwin(pane->win, pane->sy, pane->sx);
+ redrawwin(pane->win);
+ }
+}
+
+void
+pane_init(struct pane *pane, pane_handler handle, pane_updater update)
+{
+ pane->win = newwin(1, 1, 0, 0);
+ ASSERT(pane->win != NULL);
+ pane->handle = handle;
+ pane->update = update;
+}
+
+void
+pane_title(struct pane *pane, const char *title, int highlight)
+{
+ wmove(pane->win, 0, 0);
+
+ style_on(pane->win, STYLE_TITLE);
+ if (highlight) ATTR_ON(pane->win, A_STANDOUT);
+
+ wprintw(pane->win, " %-*.*s", pane->w - 1, pane->w - 1, title);
+
+ if (highlight) ATTR_OFF(pane->win, A_STANDOUT);
+ style_off(pane->win, STYLE_TITLE);
+}
+
+void
+style_init(int style, int fg, int bg, int attr)
+{
+ style_attrs[style] = attr;
+ init_pair(style, fg, bg);
+}
+
+void
+style_on(WINDOW *win, int style)
+{
+ ATTR_ON(win, COLOR_PAIR(style) | style_attrs[style]);
+}
+
+void
+style_off(WINDOW *win, int style)
+{
+ ATTR_OFF(win, COLOR_PAIR(style) | style_attrs[style]);
+}
+
+wchar_t *
+command_name_generator(const wchar_t *text, int fwd, int reset)
+{
+ static int index, len;
+ int dir;
+
+ dir = fwd ? 1 : -1;
+
+ if (reset) {
+ index = 0;
+ len = wcslen(text);
+ } else if (index >= -1 && index <= ARRLEN(cmds)) {
+ index += dir;
+ }
+
+ while (index >= 0 && index < ARRLEN(cmds)) {
+ if (!wcsncmp(cmds[index].name, text, len))
+ return wcsdup(cmds[index].name);
+ index += dir;
+ }
+
+ return NULL;
+}
+
+wchar_t *
+track_name_generator(const wchar_t *text, int fwd, int reset)
+{
+ static struct link *cur;
+ struct track *track;
+
+ if (reset) {
+ cur = tracks.next;
+ } else if (cur) {
+ cur = fwd ? cur->next : cur->prev;
+ }
+
+ while (cur != &tracks && cur) {
+ track = UPCAST(cur, struct track);
+ if (wcsstr(track->name, text))
+ return wcsdup(track->name);
+ cur = fwd ? cur->next : cur->prev;
+ }
+
+ return NULL;
+}
+
+void
+select_current_tag(void)
+{
+ struct link *link, *iter;
+ struct track *track;
+ struct tag *tag;
+ struct ref *ref;
+
+ link = link_iter(tags.next, tag_nav.sel);
+ ASSERT(link != NULL);
+ tag = UPCAST(link, struct tag);
+ if (refs_incl(&tags_sel, tag)) {
+ refs_rm(&tags_sel, tag);
+ } else {
+ ref = ref_init(tag);
+ list_push_back(&tags_sel, LINK(ref));
+ }
+ refs_free(&playlist);
+ for (link = tags_sel.next; link; link = link->next) {
+ tag = UPCAST(link, struct ref)->data;
+ for (iter = tracks.next; iter; iter = iter->next) {
+ track = UPCAST(iter, struct track);
+ if (refs_incl(&track->tags, tag)
+ && !refs_incl(&playlist, track)) {
+ ref = ref_init(track);
+ list_push_back(&playlist, LINK(ref));
+ }
+ }
+ }
+}
+
+int
+tag_input(wint_t c)
+{
+ switch (c) {
+ case KEY_UP:
+ listnav_update_sel(&tag_nav, tag_nav.sel - 1);
+ return 1;
+ case KEY_DOWN:
+ listnav_update_sel(&tag_nav, tag_nav.sel + 1);
+ return 1;
+ case KEY_SPACE:
+ select_current_tag();
+ return 1;
+ case KEY_NPAGE:
+ listnav_update_sel(&track_nav,
+ track_nav.sel - track_nav.wlen / 2);
+ return 1;
+ case KEY_PPAGE:
+ listnav_update_sel(&track_nav,
+ track_nav.sel + track_nav.wlen / 2);
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+tag_vis(struct pane *pane, int sel)
+{
+ struct tag *tag, *tag2;
+ struct link *iter, *iter2;
+ int index, tsel;
+
+ werase(pane->win);
+ pane_title(pane, "Tags", sel);
+
+ listnav_update_bounds(&tag_nav, 0, list_len(&tags));
+ listnav_update_wlen(&tag_nav, pane->h - 1);
+
+ index = 0;
+ for (iter = tags.next; iter; iter = iter->next) {
+ tag = UPCAST(iter, struct tag);
+ tsel = refs_incl(&tags_sel, tag);
+
+ if (sel && index == tag_nav.sel && tsel)
+ style_on(pane->win, STYLE_ITEM_HOVER_SEL);
+ else if (sel && index == tag_nav.sel)
+ style_on(pane->win, STYLE_ITEM_HOVER);
+ else if (tsel)
+ style_on(pane->win, STYLE_ITEM_SEL);
+
+ wmove(pane->win, 1 + index, 0);
+ wprintw(pane->win, "%*.*s", pane->w, pane->w, tag->name);
+
+ if (index == tag_nav.sel && tsel)
+ style_off(pane->win, STYLE_ITEM_HOVER_SEL);
+ else if (index == tag_nav.sel)
+ style_off(pane->win, STYLE_ITEM_HOVER);
+ else if (tsel)
+ style_off(pane->win, STYLE_ITEM_SEL);
+ index++;
+ }
+}
+
+int
+track_input(wint_t c)
+{
+ struct link *link;
+ struct track *track;
+
+ switch (c) {
+ case KEY_UP:
+ listnav_update_sel(&track_nav, track_nav.sel - 1);
+ return 1;
+ case KEY_DOWN:
+ listnav_update_sel(&track_nav, track_nav.sel + 1);
+ return 1;
+ case KEY_ENTER:
+ link = link_iter(tracks.next, track_nav.sel);
+ ASSERT(link != NULL);
+ track = UPCAST(link, struct track);
+ player->track = track;
+ player_play_track(track);
+ return 1;
+ case KEY_PPAGE:
+ listnav_update_sel(&track_nav,
+ track_nav.sel - track_nav.wlen / 2);
+ return 1;
+ case KEY_NPAGE:
+ listnav_update_sel(&track_nav,
+ track_nav.sel + track_nav.wlen / 2);
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+track_vis(struct pane *pane, int sel)
+{
+ struct track *track;
+ struct link *iter;
+ int index;
+
+ werase(pane->win);
+ pane_title(pane, "Tracks", sel);
+
+ listnav_update_bounds(&track_nav, 0, list_len(&playlist));
+ listnav_update_wlen(&track_nav, pane->h - 1);
+
+ index = 0;
+ for (iter = playlist.next; iter; iter = iter->next, index++) {
+ track = UPCAST(iter, struct ref)->data;
+
+ if (index < track_nav.wmin) continue;
+ if (index >= track_nav.wmax) break;
+
+ if (sel && index == track_nav.sel && track == player->track)
+ style_on(pane->win, STYLE_ITEM_HOVER_SEL);
+ else if (sel && index == track_nav.sel)
+ style_on(pane->win, STYLE_ITEM_HOVER);
+ else if (track == player->track)
+ style_on(pane->win, STYLE_ITEM_SEL);
+
+ wmove(pane->win, 1 + index - track_nav.wmin, 0);
+ wprintw(pane->win, "%-*.*ls", pane->w, pane->w, track->name);
+
+ if (sel && index == track_nav.sel && track == player->track)
+ style_off(pane->win, STYLE_ITEM_HOVER_SEL);
+ else if (sel && index == track_nav.sel)
+ style_off(pane->win, STYLE_ITEM_HOVER);
+ else if (track == player->track)
+ style_off(pane->win, STYLE_ITEM_SEL);
+ }
+}
+
+void
+run_cmd(const wchar_t *query)
+{
+
+}
+
+void
+play_track(const wchar_t *query)
+{
+ struct track *track;
+ struct link *iter;
+
+ for (iter = tracks.next; iter; iter = iter->next) {
+ track = UPCAST(iter, struct track);
+ if (wcsstr(track->name, history->cmd->buf)) {
+ player_play_track(track);
+ break;
+ }
+ }
+}
+
+int
+cmd_input(wint_t c)
+{
+ wchar_t *res;
+
+ if (cmd_mode == IMODE_EXECUTE) {
+ history = &command_history;
+ completion = command_name_generator;
+ } else if (cmd_mode == IMODE_SEARCH) {
+ history = &search_history;
+ completion = track_name_generator;
+ }
+
+ switch (c) {
+ case KEY_ESC:
+ if (history->cmd == history->query) {
+ pane_sel = pane_top_sel;
+ } else {
+ history->cmd = history->query;
+ }
+ break;
+ case KEY_LEFT:
+ inputln_left(history->cmd);
+ break;
+ case KEY_RIGHT:
+ inputln_right(history->cmd);
+ break;
+ case KEY_CTRL('w'):
+ inputln_del(history->cmd, history->cmd->cur);
+ break;
+ case KEY_UP:
+ history_next(history);
+ break;
+ case KEY_DOWN:
+ history_prev(history);
+ break;
+ case KEY_ENTER:
+ if (!*history->cmd->buf) {
+ pane_sel = pane_top_sel;
+ break;
+ }
+
+ if (cmd_mode == IMODE_EXECUTE) {
+ run_cmd(history->cmd->buf);
+ } else if (cmd_mode == IMODE_SEARCH) {
+ play_track(history->cmd->buf);
+ }
+
+ history_submit(history);
+ pane_sel = pane_top_sel;
+ break;
+ case KEY_TAB:
+ case KEY_BTAB:
+ if (history->cmd != history->query) {
+ inputln_copy(history->query, history->cmd);
+ history->cmd = history->query;
+ }
+
+ if (completion_reset) {
+ inputln_copy(&completion_query, history->query);
+ }
+
+ res = completion(completion_query.buf,
+ c == KEY_TAB, completion_reset);
+ if (res) inputln_replace(history->query, res);
+ free(res);
+
+ completion_reset = 0;
+ break;
+ case KEY_BACKSPACE:
+ if (history->cmd->cur == 0) {
+ pane_sel = pane_top_sel;
+ break;
+ }
+ inputln_del(history->cmd, 1);
+ completion_reset = 1;
+ break;
+ default:
+ if (!iswprint(c)) return 0;
+ inputln_addch(history->cmd, c);
+ completion_reset = 1;
+ break;
+ }
+ return 1;
+}
+
+void
+cmd_vis(struct pane *pane, int sel)
+{
+ struct inputln *cmd;
+ struct link *iter;
+ int index, offset;
+ char *line;
+
+ werase(pane->win);
+
+ wmove(pane->win, 0, 0);
+ style_on(pane->win, STYLE_TITLE);
+ wprintw(pane->win, " %-*.*ls\n", pane->w - 1, pane->w - 1,
+ player->track ? player->track->name : L"");
+ style_off(pane->win, STYLE_TITLE);
+
+ if (player->loaded) {
+ line = appendstrf(NULL, "%c ", player_state_chars[player->state]);
+ line = appendstrf(line, "%s / ", timestr(player->time_pos));
+ line = appendstrf(line, "%s", timestr(player->time_end));
+
+ if (player->volume >= 0)
+ line = appendstrf(line, " - vol: %u%%", player->volume);
+
+ if (player->msg)
+ line = appendstrf(line, " | [PLAYER] %s", player->msg);
+
+ if (!list_empty(&player->queue))
+ line = appendstrf(line, " | [QUEUE] %i tracks",
+ list_len(&player->queue));
+
+ if (autoplay.enabled && !autoplay.shuffle)
+ line = appendstrf(line, " | AUTOPLAY");
+ else if (autoplay.enabled && autoplay.shuffle)
+ line = appendstrf(line, " | AUTOPLAY [S]");
+
+ wmove(pane->win, 1, 0);
+ ATTR_ON(pane->win, A_REVERSE);
+ wprintw(pane->win, "%-*.*s\n", pane->w, pane->w, line);
+ ATTR_OFF(pane->win, A_REVERSE);
+
+ free(line);
+ } else {
+ if (player->msg) {
+ wmove(pane->win, 1, 0);
+ line = aprintf("[PLAYER] %s", player->msg);
+ wprintw(pane->win, "%-*.*s\n", pane->w, pane->w, line);
+ free(line);
+ }
+ }
+
+ if (sel || cmd_show) {
+ cmd = history->cmd;
+ if (cmd != history->query) {
+ index = 0;
+ iter = history->list.next;
+ for (; iter; iter = iter->next, index++)
+ if (UPCAST(iter, struct inputln) == cmd)
+ break;
+ line = appendstrf(NULL, "[%i] ", iter ? index : -1);
+ } else {
+ line = appendstrf(NULL, "%c",
+ cmd_mode == IMODE_SEARCH ? '/' : ':');
+ }
+ offset = strlen(line);
+ line = appendstrf(line, "%ls", cmd->buf);
+ wprintw(pane->win, "%-*.*s", pane->w, pane->w, line);
+ free(line);
+ if (sel) { /* cursor */
+ ATTR_ON(pane->win, A_REVERSE);
+ wmove(pane->win, 2, offset + cmd->cur);
+ waddch(pane->win, cmd->cur < cmd->len
+ ? cmd->buf[cmd->cur] : ' ');
+ ATTR_OFF(pane->win, A_REVERSE);
+ }
+ }
+}
+
+void
+queue_hover(void)
+{
+ struct link *link;
+
+ link = link_iter(playlist.next, track_nav.sel);
+ ASSERT(link != NULL);
+ player_queue_append(UPCAST(link, struct ref)->data);
+}
+
+void
+main_input(wint_t c)
+{
+ switch (c) {
+ case KEY_TAB:
+ if (pane_sel == &pane_left)
+ pane_sel = &pane_right;
+ else
+ pane_sel = &pane_left;
+ pane_top_sel = pane_sel;
+ break;
+ case KEY_ESC:
+ pane_sel = pane_top_sel;
+ break;
+ case KEY_LEFT:
+ if (player->track)
+ player_seek(MAX(player->time_pos - 10, 0));
+ break;
+ case KEY_RIGHT:
+ if (player->track) {
+ if (player->time_end > player->time_pos + 10) {
+ player_seek(player->time_pos + 10);
+ } else {
+ player_next();
+ }
+ }
+ break;
+ case L'y':
+ queue_hover();
+ break;
+ case L'o':
+ player_queue_clear();
+ break;
+ case L'c':
+ player_toggle_pause();
+ break;
+ case L'n':
+ case L'>':
+ player_next();
+ break;
+ case L'p':
+ case L'<':
+ player_prev();
+ break;
+ case L'w':
+ autoplay.enabled ^= 1;
+ break;
+ case KEY_CTRL('s'):
+ autoplay.shuffle ^= 1;
+ break;
+ case L'b':
+ player_seek(0);
+ break;
+ case L's':
+ if (player->state == PLAYER_STATE_PLAYING) {
+ player_stop();
+ } else {
+ player_play();
+ }
+ break;
+ case L':':
+ cmd_mode = IMODE_EXECUTE;
+ pane_sel = &pane_bot;
+ completion_reset = 1;
+ break;
+ case L'/':
+ cmd_mode = IMODE_SEARCH;
+ pane_sel = &pane_bot;
+ completion_reset = 1;
+ break;
+ case L'+':
+ player_set_volume(MIN(100, player->volume + 5));
+ break;
+ case L'-':
+ player_set_volume(MAX(0, player->volume - 5));
+ break;
+ case L'q':
+ quit = 1;
+ break;
+ }
+}
+
+void
+main_vis(void)
+{
+ int i;
+
+ style_on(stdscr, STYLE_TITLE);
+ move(0, pane_left.ex);
+ addch(' ');
+ style_off(stdscr, STYLE_TITLE);
+
+ style_on(stdscr, STYLE_PANE_SEP);
+ for (i = pane_left.sy + 1; i < pane_left.ey; i++) {
+ move(i, pane_left.ex);
+ addch(ACS_VLINE);
+ }
+ style_off(stdscr, STYLE_PANE_SEP);
+}
+
+int
+usercmd_save(const char *args)
+{
+ data_save();
+ return 1;
+}
+
+void
+update_player(void)
+{
+ static struct track *last = NULL;
+ struct track *track;
+ struct link *iter;
+ int index;
+
+ player_update();
+
+ if (!player->loaded && autoplay.enabled && playlist.next) {
+ if (autoplay.shuffle) {
+ /* TODO better algorithm for random sequence */
+ index = rand() % list_len(&playlist);
+ iter = link_iter(&playlist, index);
+ ASSERT(iter != NULL);
+ } else {
+ if (last) {
+ iter = playlist.next;
+ for (; iter; iter = iter->next) {
+ track = UPCAST(iter, struct ref)->data;
+ if (track == last)
+ break;
+ }
+ iter = iter->next;
+ if (!iter) {
+ last = NULL;
+ return;
+ }
+ } else {
+ iter = playlist.next;
+ }
+ }
+ track = UPCAST(iter, struct ref)->data;
+ player_play_track(track);
+ } else if (player->track) {
+ last = player->track;
+ }
+}
+
+int
+main(int argc, const char **argv)
+{
+ int i, handled;
+ wint_t c;
+
+ init();
+
+ c = KEY_RESIZE;
+ do {
+ update_player();
+
+ if (c == KEY_RESIZE) {
+ resize();
+ } else if (c != ERR) {
+ handled = 0;
+ if (pane_sel && pane_sel->active)
+ handled = pane_sel->handle(c);
+
+ if (!handled) main_input(c);
+ }
+
+ refresh();
+ for (i = 0; i < ARRLEN(panes); i++) {
+ if (!panes[i]->active) continue;
+ panes[i]->update(panes[i], pane_sel == panes[i]);
+ wnoutrefresh(panes[i]->win);
+ }
+ main_vis();
+ doupdate();
+
+ get_wch(&c);
+ } while (!quit);
+}
+