tmus

TUI Music Player
git clone https://git.sinitax.com/sinitax/tmus
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

commit fb0a1cabe8d61d3305d9d14acc4754a1b6151b8c
parent fee76b1e3d8da5152e44d67293165ae0c5651d25
Author: Louis Burda <quent.burda@gmail.com>
Date:   Tue, 28 Dec 2021 22:56:43 +0100

Refactor for readability, use static line buffer for statusline etc

Diffstat:
Msrc/main.c | 446+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Asrc/pane.c | 35+++++++++++++++++++++++++++++++++++
Asrc/pane.h | 25+++++++++++++++++++++++++
Msrc/player.h | 5++++-
4 files changed, 307 insertions(+), 204 deletions(-)

diff --git a/src/main.c b/src/main.c @@ -6,6 +6,7 @@ #include "history.h" #include "tag.h" #include "track.h" +#include "pane.h" #include "player.h" #include "listnav.h" #include "ref.h" @@ -51,23 +52,8 @@ enum { 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; -}; +typedef wchar_t *(*completion_gen)(const wchar_t *text, int fwd, int state); +typedef int (*cmd_handler)(const wchar_t *args); struct cmd { const wchar_t *name; @@ -94,57 +80,59 @@ static struct pane *const panes[] = { &pane_bot }; -static struct inputln completion_query = { 0 }; -static int completion_reset = 1; -static completion_generator completion; - +/* bottom 'cmd' pane for search / exec */ static struct pane *cmd_pane; static struct history search_history, command_history; static struct history *history; static int cmd_show, cmd_mode; +static struct inputln completion_query = { 0 }; +static int completion_reset = 1; +static completion_gen completion; -static int autoplay; -static int shuffle; - +/* left pane for tags */ static struct pane *tag_pane; static struct listnav tag_nav; static struct link tags; static struct link tags_sel; +/* right pane for tracks */ static struct pane *track_pane; static struct listnav track_nav; static struct link playlist; static struct link tracks; +static int autoplay; +static int shuffle; static void init(void); -static void cleanup(void *arg, int code); +static void cleanup(int code, void *arg); -static void data_load(void); -static void tracks_load(struct tag *tag); +static void tui_init(void); +static void tui_resize(void); +static void tui_end(void); + +static void ncurses_init(void); +static void data_load(void); 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 tracks_load(struct tag *tag); +static void tracks_save(struct tag *tag); -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 wchar_t *command_name_gen(const wchar_t *text, int fwd, int state); +static wchar_t *track_name_gen(const wchar_t *text, int fwd, int state); static void toggle_current_tag(void); -static int tag_input(wint_t c); -static void tag_vis(struct pane *pane, int sel); +static int tag_pane_input(wint_t c); +static void tag_pane_vis(struct pane *pane, int sel); -static int track_input(wint_t c); -static void track_vis(struct pane *pane, int sel); +static int track_pane_input(wint_t c); +static void track_pane_vis(struct pane *pane, int sel); static int cmd_input(wint_t c); static void cmd_vis(struct pane *pane, int sel); @@ -153,7 +141,7 @@ 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 int usercmd_save(const wchar_t *args); static void update_player(void); @@ -164,32 +152,44 @@ const struct cmd cmds[] = { void init(void) { - quit = 0; - setlocale(LC_ALL, ""); + quit = 0; - /* TODO handle as character intead of signal */ signal(SIGINT, exit); - atexit((void*) cleanup); /* this works */ + on_exit(cleanup, NULL); - history = &command_history; history_init(&search_history); history_init(&command_history); + history = &command_history; data_load(); player_init(); - /* ncurses init */ - initscr(); - raw(); - noecho(); - halfdelay(1); - intrflush(stdscr, FALSE); - keypad(stdscr, TRUE); - start_color(); - curs_set(0); - ESCDELAY = 0; + tui_init(); + + listnav_init(&tag_nav); + listnav_init(&track_nav); +} + +void +cleanup(int code, void* arg) +{ + tui_end(); + + if (code == EXIT_SUCCESS) + data_save(); + + player_free(); + + history_free(&search_history); + history_free(&command_history); +} + +void +tui_init(void) +{ + ncurses_init(); memset(style_attrs, 0, sizeof(style_attrs)); style_init(STYLE_DEFAULT, COLOR_WHITE, COLOR_BLACK, 0); @@ -199,39 +199,75 @@ init(void) 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((tag_pane = &pane_left), tag_pane_input, tag_pane_vis); + pane_init((track_pane = &pane_right), track_pane_input, track_pane_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; +void +tui_resize(void) +{ + struct link *iter; + int i, leftw; - autoplay = 0; - shuffle = 0; + getmaxyx(stdscr, scrh, scrw); - listnav_init(&tag_nav); - listnav_init(&track_nav); + /* guarantee a minimum terminal size */ + if (scrw < 10 || scrh < 4) { + clear(); + printw("..."); + refresh(); + usleep(10000); + } + + /* adjust tag pane width to name lengths */ + leftw = 0; + for (iter = tags.next; iter; iter = iter->next) + leftw = MAX(leftw, strlen(UPCAST(iter, struct tag)->name)); + leftw = MAX(leftw + 1, 0.2f * 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 -cleanup(void* arg, int code) +tui_end(void) { - if (code) return; + pane_free(&pane_left); + pane_free(&pane_right); + pane_free(&pane_bot); - delwin(pane_left.win); - delwin(pane_right.win); - delwin(pane_bot.win); - endwin(); + if (!isendwin()) endwin(); +} - data_save(); +void +ncurses_init(void) +{ + initscr(); - player_free(); + /* do most of the handling ourselves, + * enable special keys */ + raw(); + noecho(); + keypad(stdscr, TRUE); - history_free(&search_history); - history_free(&command_history); + /* update screen occasionally for things like + * time even when no input was received */ + halfdelay(1); + + /* inits COLOR and COLOR_PAIRS used by styles */ + start_color(); + + /* dont show cursor */ + curs_set(0); + + /* we use ESC deselecting the current pane + * and not for escape sequences, so dont wait */ + ESCDELAY = 0; } void @@ -241,8 +277,12 @@ data_load(void) struct tag *tag; DIR *dir; + tracks = LIST_HEAD; tags = LIST_HEAD; + playlist = LIST_HEAD; + tags_sel = LIST_HEAD; + datadir = getenv("TMUS_DATA"); ASSERT(datadir != NULL); @@ -270,6 +310,15 @@ data_load(void) tracks_load(tag); } closedir(dir); + + autoplay = 0; + shuffle = 0; +} + +void +data_save(void) +{ + } void @@ -299,65 +348,12 @@ tracks_load(struct tag *tag) } 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); @@ -391,7 +387,7 @@ style_off(WINDOW *win, int style) } wchar_t * -command_name_generator(const wchar_t *text, int fwd, int reset) +command_name_gen(const wchar_t *text, int fwd, int reset) { static int index, len; int dir; @@ -415,7 +411,7 @@ command_name_generator(const wchar_t *text, int fwd, int reset) } wchar_t * -track_name_generator(const wchar_t *text, int fwd, int reset) +track_name_gen(const wchar_t *text, int fwd, int reset) { static struct link *cur; struct track *track; @@ -447,12 +443,16 @@ toggle_current_tag(void) link = link_iter(tags.next, tag_nav.sel); ASSERT(link != NULL); tag = UPCAST(link, struct tag); + + /* toggle tag in tags_sel */ if (refs_incl(&tags_sel, tag)) { refs_rm(&tags_sel, tag); } else { ref = ref_init(tag); list_push_back(&tags_sel, LINK(ref)); } + + /* rebuild the full playlist */ refs_free(&playlist); for (link = tags_sel.next; link; link = link->next) { tag = UPCAST(link, struct ref)->data; @@ -468,7 +468,7 @@ toggle_current_tag(void) } int -tag_input(wint_t c) +tag_pane_input(wint_t c) { switch (c) { case KEY_UP: @@ -485,13 +485,12 @@ tag_input(wint_t c) player->next = NULL; refs_free(&tags_sel); toggle_current_tag(); - 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); + listnav_update_sel(&tag_nav, tag_nav.sel - tag_nav.wlen / 2); + return 1; + case KEY_NPAGE: + listnav_update_sel(&tag_nav, tag_nav.sel + tag_nav.wlen / 2); return 1; } @@ -499,10 +498,10 @@ tag_input(wint_t c) } void -tag_vis(struct pane *pane, int sel) +tag_pane_vis(struct pane *pane, int sel) { - struct tag *tag, *tag2; - struct link *iter, *iter2; + struct tag *tag; + struct link *iter; int index, tsel; werase(pane->win); @@ -512,10 +511,13 @@ tag_vis(struct pane *pane, int sel) listnav_update_wlen(&tag_nav, pane->h - 1); index = 0; - for (iter = tags.next; iter; iter = iter->next) { + for (iter = tags.next; iter; iter = iter->next, index++) { tag = UPCAST(iter, struct tag); tsel = refs_incl(&tags_sel, tag); + if (index < tag_nav.wmin) continue; + if (index >= tag_nav.wmax) break; + if (sel && index == tag_nav.sel && tsel) style_on(pane->win, STYLE_ITEM_HOVER_SEL); else if (sel && index == tag_nav.sel) @@ -523,7 +525,7 @@ tag_vis(struct pane *pane, int sel) else if (tsel) style_on(pane->win, STYLE_ITEM_SEL); - wmove(pane->win, 1 + index, 0); + wmove(pane->win, 1 + index - tag_nav.wmin, 0); wprintw(pane->win, "%*.*s", pane->w, pane->w, tag->name); if (index == tag_nav.sel && tsel) @@ -532,12 +534,11 @@ tag_vis(struct pane *pane, int 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) +track_pane_input(wint_t c) { struct link *link; struct track *track; @@ -570,7 +571,7 @@ track_input(wint_t c) } void -track_vis(struct pane *pane, int sel) +track_pane_vis(struct pane *pane, int sel) { struct track *track; struct link *iter; @@ -608,13 +609,25 @@ track_vis(struct pane *pane, int sel) } } -void +int run_cmd(const wchar_t *query) { + const wchar_t *sep; + int i, cmdlen; + + sep = wcschr(query, L' '); + cmdlen = sep ? sep - query : wcslen(query); + for (i = 0; i < ARRLEN(cmds); i++) { + if (!wcsncmp(cmds[i].name, query, cmdlen)) { + cmds[i].func(sep ? sep + 1 : NULL); + return 1; + } + } + return 0; } -void +int play_track(const wchar_t *query) { struct track *track; @@ -622,11 +635,13 @@ play_track(const wchar_t *query) for (iter = tracks.next; iter; iter = iter->next) { track = UPCAST(iter, struct track); - if (wcsstr(track->name, history->cmd->buf)) { + if (wcsstr(track->name, query)) { player_play_track(track); - break; + return 1; } } + + return 0; } int @@ -636,19 +651,18 @@ cmd_input(wint_t c) if (cmd_mode == IMODE_EXECUTE) { history = &command_history; - completion = command_name_generator; + completion = command_name_gen; } else if (cmd_mode == IMODE_SEARCH) { history = &search_history; - completion = track_name_generator; + completion = track_name_gen; } switch (c) { case KEY_ESC: - if (history->cmd == history->query) { + if (history->cmd == history->query) pane_sel = pane_top_sel; - } else { + else history->cmd = history->query; - } break; case KEY_LEFT: inputln_left(history->cmd); @@ -687,12 +701,11 @@ cmd_input(wint_t c) history->cmd = history->query; } - if (completion_reset) { + if (completion_reset) inputln_copy(&completion_query, history->query); - } res = completion(completion_query.buf, - c == KEY_TAB, completion_reset); + c == KEY_TAB, completion_reset); if (res) inputln_replace(history->query, res); free(res); @@ -712,19 +725,29 @@ cmd_input(wint_t c) completion_reset = 1; break; } - return 1; + + return 1; /* grab everything */ } void cmd_vis(struct pane *pane, int sel) { + static wchar_t *linebuf = NULL; + static int linecap = 0; struct inputln *cmd; struct link *iter; + wchar_t *line, *end; int index, offset; - char *line; werase(pane->win); + /* static line buffer for perf */ + if (pane->w + 1 > linecap) { + linecap = MAX(linecap, pane->w + 1); + linebuf = realloc(linebuf, linecap * sizeof(wchar_t)); + } + end = linebuf + linecap; + wmove(pane->win, 0, 0); style_on(pane->win, STYLE_TITLE); wprintw(pane->win, " %-*.*ls\n", pane->w - 1, pane->w - 1, @@ -732,42 +755,53 @@ cmd_vis(struct pane *pane, int sel) 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)); + line = linebuf; + line += swprintf(line, end - line, L"%c ", + player_state_chars[player->state]); + line += swprintf(line, end - line, L"%s / ", + timestr(player->time_pos)); + line += swprintf(line, end - line, L"%s", + timestr(player->time_end)); + + if (player->volume >= 0) { + line += swprintf(line, end - line, L" - vol: %u%%", + player->volume); + } - if (player->volume >= 0) - line = appendstrf(line, " - vol: %u%%", player->volume); + if (player->msg) { + line += swprintf(line, end - line, L" | [PLAYER] %s", + player->msg); + } - if (player->msg) - line = appendstrf(line, " | [PLAYER] %s", player->msg); + if (!list_empty(&player->queue)) { + line += swprintf(line, end - line, + L" | [QUEUE] %i tracks", + list_len(&player->queue)); + } - if (!list_empty(&player->queue)) - line = appendstrf(line, " | [QUEUE] %i tracks", - list_len(&player->queue)); + if (autoplay) { + line += swprintf(line, end - line, L" | AUTOPLAY"); + } - if (autoplay) - line = appendstrf(line, " | AUTOPLAY"); + if (shuffle) { + line += swprintf(line, end - line, L" | SHUFFLE"); + } - if (shuffle) - line = appendstrf(line, " | SHUFFLE"); + line += swprintf(line, end - line, L"%*s", pane->w, L" "); - wmove(pane->win, 1, 0); ATTR_ON(pane->win, A_REVERSE); - wprintw(pane->win, "%-*.*s\n", pane->w, pane->w, line); + mvwaddwstr(pane->win, 1, 0, linebuf); 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); - } + } else if (player->msg) { + line = linebuf; + line += swprintf(line, linecap, L"[PLAYER] %s", player->msg); + line += swprintf(line, end - line, L"%*s", pane->w, L" "); + mvwaddwstr(pane->win, 1, 0, linebuf); } if (sel || cmd_show) { + line = linebuf; + cmd = history->cmd; if (cmd != history->query) { index = 0; @@ -775,20 +809,24 @@ cmd_vis(struct pane *pane, int sel) for (; iter; iter = iter->next, index++) if (UPCAST(iter, struct inputln) == cmd) break; - line = appendstrf(NULL, "[%i] ", iter ? index : -1); + line += swprintf(line, end - line, L"[%i] ", + iter ? index : -1); } else { - line = appendstrf(NULL, "%c", + line += swprintf(line, end - line, L"%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 */ + offset = wcslen(linebuf); + + line += swprintf(line, end - line, L"%ls", cmd->buf); + line += swprintf(line, end - line, L"%*s", pane->w, L" "); + + mvwaddwstr(pane->win, 2, 0, linebuf); + + if (sel) { /* show cursor in text */ ATTR_ON(pane->win, A_REVERSE); wmove(pane->win, 2, offset + cmd->cur); waddch(pane->win, cmd->cur < cmd->len - ? cmd->buf[cmd->cur] : ' '); + ? cmd->buf[cmd->cur] : L' '); ATTR_OFF(pane->win, A_REVERSE); } } @@ -809,26 +847,23 @@ 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_sel = pane_sel == &pane_left + ? &pane_right : &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)); + if (!player->loaded) break; + 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(); - } + if (!player->loaded) break; + if (player->time_end > player->time_pos + 10) { + player_seek(player->time_pos + 10); + } else { + player_next(); } break; case L'y': @@ -892,11 +927,13 @@ main_vis(void) { int i; + /* add missing title tile at the top */ style_on(stdscr, STYLE_TITLE); move(0, pane_left.ex); addch(' '); style_off(stdscr, STYLE_TITLE); + /* draw left-right separator line */ style_on(stdscr, STYLE_PANE_SEP); for (i = pane_left.sy + 1; i < pane_left.ey; i++) { move(i, pane_left.ex); @@ -906,10 +943,10 @@ main_vis(void) } int -usercmd_save(const char *args) +usercmd_save(const wchar_t *args) { data_save(); - return 1; + return 0; } void @@ -962,18 +999,21 @@ main(int argc, const char **argv) update_player(); if (c == KEY_RESIZE) { - resize(); + tui_resize(); } else if (c != ERR) { handled = 0; if (pane_sel && pane_sel->active) handled = pane_sel->handle(c); + /* fallback if char not handled by pane */ if (!handled) main_input(c); } refresh(); for (i = 0; i < ARRLEN(panes); i++) { + /* only update ui for panes that are visible */ if (!panes[i]->active) continue; + panes[i]->update(panes[i], pane_sel == panes[i]); wnoutrefresh(panes[i]->win); } diff --git a/src/pane.c b/src/pane.c @@ -0,0 +1,35 @@ +#include "pane.h" +#include "util.h" + +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_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_free(struct pane *pane) +{ + delwin(pane->win); +} diff --git a/src/pane.h b/src/pane.h @@ -0,0 +1,25 @@ +#pragma once + +#include "ncurses.h" + +#include <wchar.h> + +struct pane; + +typedef int (*pane_handler)(wint_t c); +typedef void (*pane_updater)(struct pane *pane, int sel); + +struct pane { + WINDOW *win; + int sx, sy, ex, ey; + int w, h; + int active; + + pane_handler handle; + pane_updater update; +}; + +void pane_init(struct pane *pane, pane_handler handle, pane_updater update); +void pane_resize(struct pane *pane, int sx, int sy, int ex, int ey); +void pane_free(struct pane *p); + diff --git a/src/player.h b/src/player.h @@ -39,8 +39,11 @@ struct player { struct link queue; struct link history; - /* current loaded track */ + /* last player track */ struct track *track; + + /* player has a track loaded, + * not necessarily player->track */ int loaded; /* stopped, paused or playing */