diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/history.c | 216 | ||||
| -rw-r--r-- | src/history.h | 43 | ||||
| -rw-r--r-- | src/list.c | 108 | ||||
| -rw-r--r-- | src/list.h | 35 | ||||
| -rw-r--r-- | src/listnav.c | 43 | ||||
| -rw-r--r-- | src/listnav.h | 18 | ||||
| -rw-r--r-- | src/main.c | 985 | ||||
| -rw-r--r-- | src/player.c | 300 | ||||
| -rw-r--r-- | src/player.h | 69 | ||||
| -rw-r--r-- | src/ref.c | 68 | ||||
| -rw-r--r-- | src/ref.h | 16 | ||||
| -rw-r--r-- | src/tag.c | 4 | ||||
| -rw-r--r-- | src/tag.h | 12 | ||||
| -rw-r--r-- | src/track.c | 34 | ||||
| -rw-r--r-- | src/track.h | 16 | ||||
| -rw-r--r-- | src/util.c | 135 | ||||
| -rw-r--r-- | src/util.h | 19 |
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); |
