summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/history.c216
-rw-r--r--src/history.h43
-rw-r--r--src/list.c108
-rw-r--r--src/list.h35
-rw-r--r--src/listnav.c43
-rw-r--r--src/listnav.h18
-rw-r--r--src/main.c985
-rw-r--r--src/player.c300
-rw-r--r--src/player.h69
-rw-r--r--src/ref.c68
-rw-r--r--src/ref.h16
-rw-r--r--src/tag.c4
-rw-r--r--src/tag.h12
-rw-r--r--src/track.c34
-rw-r--r--src/track.h16
-rw-r--r--src/util.c135
-rw-r--r--src/util.h19
17 files changed, 2121 insertions, 0 deletions
diff --git a/src/history.c b/src/history.c
new file mode 100644
index 0000000..6fc615e
--- /dev/null
+++ b/src/history.c
@@ -0,0 +1,216 @@
+#include "history.h"
+#include "util.h"
+
+#include <string.h>
+#include <wchar.h>
+
+
+void
+history_init(struct history *history)
+{
+ history->list = LIST_HEAD;
+ history->query = inputln_init();
+ history->cmd = history->query;
+}
+
+void
+history_submit(struct history *history)
+{
+ /* if chose from history free query */
+ if (history->cmd != history->query) {
+ history_pop(history->query);
+ inputln_free(history->query);
+ }
+
+ /* pop first in case already in history */
+ history_pop(history->cmd);
+ history_add(history, history->cmd);
+
+ /* create new input buf and add to hist */
+ history->query = inputln_init();
+ history->cmd = history->query;
+ history_add(history, history->cmd);
+}
+
+void
+history_free(struct history *history)
+{
+ struct link *iter, *next;
+ struct inputln *ln;
+
+ for (iter = history->list.next; iter; iter = next) {
+ next = iter->next;
+ ln = UPCAST(iter, struct inputln);
+ free(ln);
+ }
+ history->list = LIST_HEAD;
+ history->query = NULL;
+ history->cmd = NULL;
+}
+
+struct inputln *
+history_list_prev(struct inputln *cur, const wchar_t *search)
+{
+ struct link *iter;
+ struct inputln *ln;
+
+ for (iter = cur->link.prev; iter && iter->prev; iter = iter->prev) {
+ ln = UPCAST(iter, struct inputln);
+ if (!search || !*search || wcsstr(ln->buf, search))
+ return ln;
+ }
+
+ return NULL;
+}
+
+struct inputln *
+history_list_next(struct inputln *cur, const wchar_t *search)
+{
+ struct link *iter;
+ struct inputln *ln;
+
+ for (iter = cur->link.next; iter; iter = iter->next) {
+ ln = UPCAST(iter, struct inputln);
+ if (!search || !*search || wcsstr(ln->buf, search))
+ return ln;
+ }
+
+ return cur;
+}
+
+void
+history_prev(struct history *history)
+{
+ history->cmd = history_list_prev(history->cmd, history->query->buf);
+ if (!history->cmd) history->cmd = history->query;
+}
+
+void
+history_next(struct history *history)
+{
+ history->cmd = history_list_next(history->cmd, history->query->buf);
+}
+
+void
+history_pop(struct inputln *line)
+{
+ link_pop(&line->link);
+}
+
+void
+history_add(struct history *history, struct inputln *line)
+{
+ struct inputln *ln;
+ struct link *back;
+
+ if (list_len(&history->list) == HISTORY_MAX) {
+ /* pop last item to make space */
+ back = link_back(&history->list);
+ back->prev->next = NULL;
+ ln = UPCAST(back, struct inputln);
+ inputln_free(ln);
+ }
+
+ link_append(&history->list, &line->link);
+}
+
+struct inputln *
+inputln_init(void)
+{
+ struct inputln *ln;
+
+ ln = malloc(sizeof(struct inputln));
+ ASSERT(ln != NULL);
+ ln->cap = 128;
+ ln->buf = malloc(ln->cap * sizeof(wchar_t));
+ ASSERT(ln->buf != NULL);
+ ln->len = 0;
+ ln->buf[ln->len] = '\0';
+ ln->cur = 0;
+ ln->link.next = NULL;
+ ln->link.prev = NULL;
+
+ return ln;
+}
+
+void
+inputln_free(struct inputln *ln)
+{
+ free(ln->buf);
+ free(ln);
+}
+
+void
+inputln_left(struct inputln *cmd)
+{
+ cmd->cur = MAX(0, cmd->cur-1);
+}
+
+void
+inputln_right(struct inputln *cmd)
+{
+ cmd->cur = MIN(cmd->len, cmd->cur+1);
+}
+
+void
+inputln_addch(struct inputln *line, wchar_t c)
+{
+ int i;
+
+ if (line->len + 1 >= line->cap) {
+ line->cap *= 2;
+ line->buf = realloc(line->buf,
+ line->cap * sizeof(wchar_t));
+ }
+
+ for (i = line->len; i > line->cur; i--)
+ line->buf[i] = line->buf[i-1];
+ line->buf[line->cur] = c;
+
+ line->len++;
+ line->cur++;
+
+ line->buf[line->len] = '\0';
+}
+
+void
+inputln_del(struct inputln *line, int n)
+{
+ int i;
+
+ if (!line->cur) return;
+
+ n = MIN(n, line->cur);
+ for (i = line->cur; i <= line->len; i++)
+ line->buf[i-n] = line->buf[i];
+
+ for (i = line->len - n; i <= line->len; i++)
+ line->buf[i] = 0;
+
+ line->len -= n;
+ line->cur -= n;
+}
+
+void
+inputln_copy(struct inputln *dst, struct inputln *src)
+{
+ if (dst->buf) {
+ free(dst->buf);
+ dst->buf = NULL;
+ }
+ dst->len = src->len;
+ dst->buf = wcsdup(src->buf);
+ ASSERT(dst->buf != NULL);
+ dst->cap = src->len + 1;
+ dst->cur = dst->len;
+}
+
+void
+inputln_replace(struct inputln *line, const wchar_t *str)
+{
+ line->buf = wcsdup(str);
+ ASSERT(line->buf != NULL);
+ line->len = wcslen(str);
+ line->cap = line->len + 1;
+ line->cur = line->len;
+}
diff --git a/src/history.h b/src/history.h
new file mode 100644
index 0000000..bbf4faa
--- /dev/null
+++ b/src/history.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "list.h"
+
+#include "wchar.h"
+
+#define HISTORY_MAX 100
+
+struct inputln {
+ wchar_t *buf;
+ int len, cap;
+ int cur;
+
+ struct link link;
+};
+
+struct history {
+ struct link list;
+ struct inputln *cmd, *query;
+};
+
+void history_init(struct history *history);
+void history_free(struct history *history);
+
+void history_submit(struct history *history);
+
+void history_prev(struct history *history);
+void history_next(struct history *history);
+
+void history_add(struct history *history, struct inputln *line);
+void history_pop(struct inputln *line);
+
+struct inputln *inputln_init(void);
+void inputln_free(struct inputln *ln);
+
+void inputln_left(struct inputln *line);
+void inputln_right(struct inputln *line);
+
+void inputln_addch(struct inputln *line, wchar_t c);
+void inputln_del(struct inputln *line, int n);
+
+void inputln_copy(struct inputln *dst, struct inputln *src);
+void inputln_replace(struct inputln *line, const wchar_t *str);
diff --git a/src/list.c b/src/list.c
new file mode 100644
index 0000000..29ddcf1
--- /dev/null
+++ b/src/list.c
@@ -0,0 +1,108 @@
+#include "list.h"
+#include "util.h"
+
+int
+list_empty(struct link *head)
+{
+ return head->next == NULL;
+}
+
+int
+list_len(struct link *head)
+{
+ struct link *iter;
+ int len;
+
+ ASSERT(head != NULL);
+
+ len = 0;
+ for (iter = head->next; iter; iter = iter->next)
+ len += 1;
+
+ return len;
+}
+
+int
+list_ffind(struct link *head, struct link *link)
+{
+ struct link *iter;
+
+ ASSERT(head != NULL);
+
+ for (iter = head->next; iter && iter != link; iter = iter->next);
+
+ return (iter == link);
+}
+
+struct link *
+link_back(struct link *link)
+{
+ ASSERT(link != NULL);
+
+ for (; link->next; link = link->next);
+
+ return link;
+}
+
+void
+link_prepend(struct link *cur, struct link *link)
+{
+ ASSERT(cur != NULL && link != NULL);
+
+ link->prev = cur->prev;
+ link->next = cur;
+
+ if (link->prev)
+ link->prev->next = link;
+ if (link->next)
+ link->next->prev = link;
+}
+
+void
+link_append(struct link *cur, struct link *link)
+{
+ ASSERT(cur != NULL && link != NULL);
+
+ link->prev = cur;
+ link->next = cur->next;
+
+ if (link->prev)
+ link->prev->next = link;
+ if (link->next)
+ link->next->prev = link;
+}
+
+struct link *
+link_pop(struct link *link)
+{
+ ASSERT(link != NULL);
+
+ if (link->prev)
+ link->prev->next = link->next;
+ if (link->next)
+ link->next->prev = link->prev;
+
+ return link;
+}
+
+struct link *
+link_iter(struct link *link, int n)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ if (!link) return NULL;
+ link = link->next;
+ }
+
+ return link;
+}
+
+void
+list_push_back(struct link *cur, struct link *link)
+{
+ struct link *back;
+
+ back = link_back(cur);
+ link_append(back, link);
+}
diff --git a/src/list.h b/src/list.h
new file mode 100644
index 0000000..d74ac9f
--- /dev/null
+++ b/src/list.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <stdlib.h>
+
+#define OFFSET(type, attr) ((size_t) &((type *)0)->attr)
+#define UPCAST(ptr, type) ({ \
+ const typeof( ((type *)0)->link ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - OFFSET(type, link) ); })
+
+#define LIST_HEAD ((struct link) { .prev = NULL, .next = NULL })
+#define LINK_EMPTY ((struct link) { 0 })
+
+#define LINK(p) (&(p)->link)
+
+struct link {
+ struct link *prev;
+ struct link *next;
+};
+
+/* list_XXX functions operate on the list head */
+
+int list_empty(struct link *head);
+int list_len(struct link *head);
+int list_ffind(struct link *head, struct link *link);
+
+struct link *link_back(struct link *list);
+void link_prepend(struct link *list, struct link *link);
+void link_append(struct link *list, struct link *link);
+struct link *link_pop(struct link *link);
+
+struct link *link_iter(struct link *link, int n);
+
+void list_push_back(struct link *list, struct link *link);
+
+//rstrrrstrssiimmrsrsssts
diff --git a/src/listnav.c b/src/listnav.c
new file mode 100644
index 0000000..707a973
--- /dev/null
+++ b/src/listnav.c
@@ -0,0 +1,43 @@
+#include "listnav.h"
+#include "util.h"
+
+#include <string.h>
+
+void
+listnav_init(struct listnav *nav)
+{
+ memset(nav, 0, sizeof(struct listnav));
+}
+
+void
+listnav_update_bounds(struct listnav *nav, int min, int max)
+{
+ nav->min = min;
+ nav->max = max;
+ nav->wmin = MAX(nav->wmin, nav->min);
+ nav->wmax = MIN(nav->wmin + nav->wlen, nav->max);
+ nav->sel = MIN(MAX(nav->sel, nav->wmin), nav->wmax - 1);
+}
+
+void
+listnav_update_wlen(struct listnav *nav, int wlen)
+{
+ nav->wlen = wlen;
+ nav->wmax = MIN(nav->wmin + nav->wlen, nav->max);
+ nav->sel = MIN(MAX(nav->sel, nav->wmin), nav->wmax - 1);
+}
+
+void
+listnav_update_sel(struct listnav *nav, int sel)
+{
+ nav->sel = MAX(MIN(sel, nav->max - 1), nav->min);
+
+ if (nav->sel >= nav->wmax) {
+ nav->wmax = nav->sel + 1;
+ nav->wmin = MAX(nav->min, nav->wmax - nav->wlen);
+ } else if (nav->sel < nav->wmin) {
+ nav->wmin = nav->sel;
+ nav->wmax = MIN(nav->wmin + nav->wlen, nav->max);
+ }
+}
+
diff --git a/src/listnav.h b/src/listnav.h
new file mode 100644
index 0000000..688172c
--- /dev/null
+++ b/src/listnav.h
@@ -0,0 +1,18 @@
+#pragma once
+
+struct listnav {
+ /* current window */
+ int wmin, wmax, wlen;
+
+ /* selected item moving inside of window */
+ int sel;
+
+ /* bounds of actual list */
+ int min, max;
+};
+
+void listnav_init(struct listnav *nav);
+void listnav_update_bounds(struct listnav *nav, int min, int max);
+void listnav_update_wlen(struct listnav *nav, int wlen);
+void listnav_update_sel(struct listnav *nav, int sel);
+
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);
+}
+
diff --git a/src/player.c b/src/player.c
new file mode 100644
index 0000000..ead9281
--- /dev/null
+++ b/src/player.c
@@ -0,0 +1,300 @@
+#include "player.h"
+#include "ref.h"
+
+#include "portaudio.h"
+#include "sndfile.h"
+#include "util.h"
+
+#include <mpd/song.h>
+#include <mpd/status.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#define PLAYER_STATUS(lvl, ...) do { \
+ player->msglvl = lvl; \
+ if (player->msg) free(player->msg); \
+ player->msg = aprintf(__VA_ARGS__); \
+ } while (0)
+
+static struct player player_static;
+struct player *player;
+
+void
+player_init(void)
+{
+ player = malloc(sizeof(struct player));
+ ASSERT(player != NULL);
+
+ player->conn = mpd_connection_new(NULL, 0, 0);
+ ASSERT(player->conn != NULL);
+
+ player->queue = LIST_HEAD;
+ player->track = NULL;
+ player->state = PLAYER_STATE_PAUSED;
+
+ player->seek_delay = 0;
+
+ player->volume = 0;
+ player->time_pos = 0;
+ player->time_end = 0;
+
+ player->msg = NULL;
+ player->msglvl = PLAYER_MSG_INFO;
+
+ // mpd_run_stop(player->conn);
+ // mpd_run_clear(player->conn);
+}
+
+void
+player_free(void)
+{
+ if (!player->conn) return;
+ mpd_run_stop(player->conn);
+ // mpd_run_clear(player->conn);
+ mpd_connection_free(player->conn);
+}
+
+void
+player_update(void)
+{
+ struct mpd_status *status;
+ struct mpd_song *song;
+ struct ref *track;
+ const char *tmp;
+
+ status = mpd_run_status(player->conn);
+ ASSERT(status != NULL);
+
+ switch (mpd_status_get_state(status)) {
+ case MPD_STATE_PAUSE:
+ player->state = PLAYER_STATE_PAUSED;
+ break;
+ case MPD_STATE_PLAY:
+ player->state = PLAYER_STATE_PLAYING;
+ break;
+ case MPD_STATE_STOP:
+ player->state = PLAYER_STATE_STOPPED;
+ break;
+ default:
+ ASSERT(0);
+ }
+ player->volume = mpd_status_get_volume(status);
+
+ if (player->seek_delay) {
+ player->seek_delay -= 1;
+ if (!player->seek_delay)
+ player_play();
+ }
+
+ if (!mpd_run_current_song(player->conn)
+ && !list_empty(&player->queue)) {
+ track = UPCAST(link_pop(player->queue.next),
+ struct ref);
+ player_play_track(track->data);
+ ref_free(track);
+ }
+
+ song = mpd_run_current_song(player->conn);
+ if (song) {
+ player->loaded = true;
+ player->time_pos = mpd_status_get_elapsed_time(status);
+ player->time_end = mpd_song_get_duration(song);
+ mpd_song_free(song);
+ } else {
+ player->track = NULL;
+ player->loaded = false;
+ player->time_pos = 0;
+ player->time_end = 0;
+ }
+
+ mpd_status_free(status);
+}
+
+void
+player_queue_clear(void)
+{
+ struct ref *ref;
+
+ while (player->queue.next) {
+ ref = UPCAST(link_pop(player->queue.next), struct ref);
+ ref_free(ref);
+ }
+}
+
+void
+player_queue_append(struct track *track)
+{
+ struct ref *ref;
+ struct link *link;
+
+ ref = ref_init(track);
+ link = link_back(&player->queue);
+ link_append(link, LINK(ref));
+}
+
+void
+player_queue_insert(struct track *track, size_t pos)
+{
+ struct ref *ref;
+ struct link *link;
+
+ ref = ref_init(track);
+ link = link_iter(player->queue.next, pos);
+ link_prepend(link, LINK(ref));
+}
+
+int
+player_play_track(struct track *track)
+{
+ player_clear_msg();
+ player->track = track;
+ mpd_run_stop(player->conn);
+ mpd_run_clear(player->conn);
+
+ if (!mpd_run_add(player->conn, player->track->fpath)
+ || !mpd_run_play(player->conn)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Playback failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ return PLAYER_OK;
+}
+
+int
+player_toggle_pause(void)
+{
+ if (!mpd_run_toggle_pause(player->conn)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Pause toggle failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ return PLAYER_OK;
+}
+
+int
+player_pause(void)
+{
+ if (!mpd_run_pause(player->conn, true)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Pausing track failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ return PLAYER_OK;
+}
+
+int
+player_resume(void)
+{
+ if (!mpd_run_pause(player->conn, false)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Resuming track failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ return PLAYER_OK;
+}
+
+int
+player_next(void)
+{
+ if (!player->loaded) return PLAYER_ERR;
+
+ if (!mpd_run_next(player->conn)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Playing next track failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ return PLAYER_OK;
+}
+
+int
+player_prev(void)
+{
+ /* TODO prevent mpd from dying on error, how to use properly */
+ if (!player->loaded) return PLAYER_ERR;
+
+ if (!mpd_run_previous(player->conn)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Playing prev track failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ return PLAYER_OK;
+}
+
+int
+player_play(void)
+{
+ if (!mpd_run_play(player->conn)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Playing track failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ return PLAYER_OK;
+}
+
+int
+player_stop(void)
+{
+ if (!mpd_run_stop(player->conn)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Stopping track failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ return PLAYER_OK;
+}
+
+int
+player_seek(int sec)
+{
+ if (!player->loaded || player->state == PLAYER_STATE_STOPPED) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "No track loaded");
+ return PLAYER_ERR;
+ }
+
+ if (!mpd_run_seek_current(player->conn, sec, false)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Track seek failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ player->seek_delay = 8;
+ player_pause();
+
+ return PLAYER_OK;
+}
+
+int
+player_set_volume(unsigned int vol)
+{
+ if (player->volume == -1) {
+ PLAYER_STATUS(PLAYER_MSG_INFO, "Setting volume not supported");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ if (!mpd_run_set_volume(player->conn, vol)) {
+ PLAYER_STATUS(PLAYER_MSG_ERR, "Setting volume failed");
+ mpd_run_clearerror(player->conn);
+ return PLAYER_ERR;
+ }
+
+ return PLAYER_OK;
+}
+
+void
+player_clear_msg(void)
+{
+ free(player->msg);
+ player->msg = NULL;
+ player->msglvl = PLAYER_MSG_NONE;
+}
diff --git a/src/player.h b/src/player.h
new file mode 100644
index 0000000..9e28903
--- /dev/null
+++ b/src/player.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "track.h"
+#include "list.h"
+#include "util.h"
+
+#include "mpd/client.h"
+
+#include <signal.h>
+
+enum {
+ PLAYER_OK,
+ PLAYER_ERR
+};
+
+enum {
+ PLAYER_MSG_NONE,
+ PLAYER_MSG_INFO,
+ PLAYER_MSG_ERR
+};
+
+enum {
+ PLAYER_STATE_PAUSED,
+ PLAYER_STATE_PLAYING,
+ PLAYER_STATE_STOPPED
+};
+
+struct player {
+ struct mpd_connection *conn;
+
+ struct link queue;
+ struct track *track;
+ int state;
+
+ int seek_delay;
+
+ int loaded;
+ int volume;
+ unsigned int time_pos, time_end;
+
+ char *msg;
+ int msglvl;
+};
+
+void player_init(void);
+void player_free(void);
+void player_update(void);
+
+void player_queue_clear(void);
+void player_queue_append(struct track *track);
+void player_queue_insert(struct track *track, size_t pos);
+
+int player_play_track(struct track *track);
+
+int player_toggle_pause(void);
+int player_pause(void);
+int player_resume(void);
+int player_prev(void);
+int player_next(void);
+int player_seek(int sec);
+int player_play(void);
+int player_stop(void);
+
+int player_set_volume(unsigned int vol);
+
+void player_clear_msg(void);
+
+extern struct player *player;
+
diff --git a/src/ref.c b/src/ref.c
new file mode 100644
index 0000000..c961dd7
--- /dev/null
+++ b/src/ref.c
@@ -0,0 +1,68 @@
+#include "ref.h"
+#include "util.h"
+
+struct ref *
+ref_init(void *data)
+{
+ struct ref *ref;
+
+ ref = malloc(sizeof(struct ref));
+ ASSERT(ref != NULL);
+ ref->link = LINK_EMPTY;
+ ref->data = data;
+ return ref;
+}
+
+void
+ref_free(struct ref *ref)
+{
+ free(ref);
+}
+
+void
+refs_free(struct link *head)
+{
+ struct link *cur;
+
+ while (head->next) {
+ cur = link_pop(head->next);
+ ref_free(UPCAST(cur, struct ref));
+ }
+}
+
+static struct link *
+refs_ffind(struct link *head, void *data)
+{
+ struct link *iter;
+
+ for (iter = head->next; iter; iter = iter->next) {
+ if (UPCAST(iter, struct ref)->data == data)
+ return iter;
+ }
+
+ return NULL;
+}
+
+int
+refs_incl(struct link *head, void *data)
+{
+ struct link *ref;
+
+ ref = refs_ffind(head, data);
+ return ref != NULL;
+}
+
+void
+refs_rm(struct link *head, void *data)
+{
+ struct link *ref;
+ struct ref *dataref;
+
+ ref = refs_ffind(head, data);
+ if (!ref) return;
+
+ dataref = UPCAST(ref, struct ref);
+ link_pop(ref);
+ free(dataref);
+}
+
diff --git a/src/ref.h b/src/ref.h
new file mode 100644
index 0000000..3a3936d
--- /dev/null
+++ b/src/ref.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "list.h"
+
+struct ref {
+ void *data;
+
+ struct link link;
+};
+
+struct ref *ref_init(void *data);
+void ref_free(struct ref *ref);
+
+void refs_free(struct link *head);
+int refs_incl(struct link *head, void *data);
+void refs_rm(struct link *head, void *data);
diff --git a/src/tag.c b/src/tag.c
new file mode 100644
index 0000000..452d099
--- /dev/null
+++ b/src/tag.c
@@ -0,0 +1,4 @@
+#include "tag.h"
+#include "link.h"
+
+
diff --git a/src/tag.h b/src/tag.h
new file mode 100644
index 0000000..cf2c757
--- /dev/null
+++ b/src/tag.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "list.h"
+#include "util.h"
+
+struct tag {
+ char *name;
+ char *fname, *fpath;
+
+ struct link link;
+};
+
diff --git a/src/track.c b/src/track.c
new file mode 100644
index 0000000..f7aa2a9
--- /dev/null
+++ b/src/track.c
@@ -0,0 +1,34 @@
+#include "track.h"
+
+#include <wchar.h>
+#include <string.h>
+
+
+struct track *
+track_init(const char *dir, const char *file)
+{
+ struct track *track;
+
+ track = malloc(sizeof(struct track));
+
+ ASSERT(track != NULL);
+ track->fname = strdup(file);
+ ASSERT(track->fname != NULL);
+ track->fpath = aprintf("%s/%s", dir, file);
+ ASSERT(track->fpath != NULL);
+ track->name = calloc(strlen(track->fname) + 1, sizeof(wchar_t));
+ mbstowcs(track->name, track->fname, strlen(track->fname) + 1);
+
+ track->link = LINK_EMPTY;
+ track->tags = LIST_HEAD;
+
+ return track;
+}
+
+void
+track_free(struct track *t)
+{
+ free(t->fname);
+ free(t->fpath);
+ free(t->name);
+}
diff --git a/src/track.h b/src/track.h
new file mode 100644
index 0000000..7a2a142
--- /dev/null
+++ b/src/track.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "list.h"
+#include "util.h"
+
+
+struct track {
+ wchar_t *name;
+ struct link tags;
+ char *fname, *fpath;
+
+ struct link link;
+};
+
+struct track *track_init(const char *dir, const char *file);
+void track_free(struct track *t);
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..e8c0935
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,135 @@
+#define _XOPEN_SOURCE 600
+
+#include "util.h"
+
+#include "ncurses.h"
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+
+static const char *allowed = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:,;-_(){}[]";
+
+int
+strnwidth(const char *s, int n)
+{
+ mbstate_t shift_state;
+ wchar_t wc;
+ size_t wc_len;
+ size_t width = 0;
+
+ memset(&shift_state, '\0', sizeof shift_state);
+
+ for (size_t i = 0; i < n; i += wc_len) {
+ wc_len = mbrtowc(&wc, s + i, MB_CUR_MAX, &shift_state);
+ if (!wc_len) {
+ break;
+ } else if (wc_len >= (size_t)-2) {
+ width += MIN(n - 1, strlen(s + i));
+ break;
+ } else {
+ width += iswcntrl(wc) ? 2 : MAX(0, wcwidth(wc));
+ }
+ }
+
+done:
+ return width;
+}
+
+void
+assert(int cond, const char *file, int line, const char *condstr)
+{
+ if (cond) return;
+
+ endwin();
+ fprintf(stderr, "Assertion failed %s:%i (%s)\n", file, line, condstr);
+ exit(1);
+}
+
+char *
+aprintf(const char *fmtstr, ...)
+{
+ va_list ap, cpy;
+ size_t size;
+ char *str;
+
+ va_copy(cpy, ap);
+
+ va_start(ap, fmtstr);
+ size = vsnprintf(NULL, 0, fmtstr, ap);
+ va_end(ap);
+
+ str = malloc(size + 1);
+ ASSERT(str != NULL);
+
+ va_start(cpy, fmtstr);
+ vsnprintf(str, size + 1, fmtstr, cpy);
+ va_end(cpy);
+
+ return str;
+}
+
+char *
+appendstrf(char *alloc, const char *fmtstr, ...)
+{
+ va_list ap, cpy;
+ size_t size, prevlen;
+
+ va_copy(cpy, ap);
+
+ va_start(ap, fmtstr);
+ size = vsnprintf(NULL, 0, fmtstr, ap);
+ va_end(ap);
+
+ prevlen = alloc ? strlen(alloc) : 0;
+ alloc = realloc(alloc, prevlen + size + 1);
+ ASSERT(alloc != NULL);
+
+ va_start(cpy, fmtstr);
+ vsnprintf(alloc + prevlen, size + 1, fmtstr, cpy);
+ va_end(cpy);
+
+ return alloc;
+}
+
+char *
+sanitized(const char *instr)
+{
+ const char *p;
+ char *clean;
+ int i;
+
+ clean = strdup(instr);
+ ASSERT(clean != NULL);
+ for (i = 0, p = instr; *p; p++) {
+ if (strchr(allowed, *p))
+ clean[i++] = *p;
+ }
+ ASSERT(i != 0);
+ clean[i] = '\0';
+
+ return clean;
+}
+
+const char *
+timestr(unsigned int secs)
+{
+ static char buf[16];
+ unsigned int mins, hours;
+
+ hours = secs / 3600;
+ mins = secs / 60 % 60;
+ secs = secs % 60;
+
+ if (hours) {
+ snprintf(buf, sizeof(buf), "%02u:%02u:%02u", hours, mins, secs);
+ } else {
+ snprintf(buf, sizeof(buf), "%02u:%02u", mins, secs);
+ }
+
+ return buf;
+}
+
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..b9f45f4
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <stdio.h>
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+#define ARRLEN(x) (sizeof(x)/sizeof((x)[0]))
+
+#define ASSERT(x) assert((x), __FILE__, __LINE__, #x)
+
+int strnwidth(const char *s, int n);
+void assert(int cond, const char *file, int line, const char *condstr);
+
+char *aprintf(const char *fmtstr, ...);
+char *appendstrf(char *alloc, const char *fmtstr, ...);
+
+char *sanitized(const char *instr);
+
+const char *timestr(unsigned int seconds);