tmus

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

commit 02141177f8055a2d0edace481d631adbcb4b4c47
Author: Louis Burda <quent.burda@gmail.com>
Date:   Tue, 30 Nov 2021 19:46:16 +0100

Added readline and ncurses basic setup

Diffstat:
A.gitignore | 2++
AMakefile | 12++++++++++++
Amain | 0
Amain.c | 519+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 533 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +vgcore* +main diff --git a/Makefile b/Makefile @@ -0,0 +1,12 @@ +CFLAGS = -I . +LDLIBS = -lcurses -lreadline + +.PHONY: all main + +all: main + +clean: + rm main + +main: main.c + $(CC) -o $@ $< $(CFLAGS) $(LDLIBS) diff --git a/main b/main Binary files differ. diff --git a/main.c b/main.c @@ -0,0 +1,519 @@ +#define _XOPEN_SOURCE 500 + +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <string.h> + +#include <unistd.h> +#include <time.h> +#include <wchar.h> +#include <wctype.h> + +#include "ncurses.h" +#include "readline/readline.h" +#include "readline/history.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 OFFSET(parent, attr) ((size_t) &((type *)0)->attr) +#define UPCAST(ptr, type) ({ \ + const typeof( ((type *)0)->link ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - OFFSET(type, link) ); }) + +#define ATTR_ON(win, attr) wattr_on((win), (attr), NULL) +#define ATTR_OFF(win, attr) wattr_off((win), (attr), NULL) +#define STYLE_ON(win, style) ATTR_ON((win), COLOR_PAIR(STYLE_ ## style)) +#define STYLE_OFF(win, style) ATTR_OFF((win), COLOR_PAIR(STYLE_ ## style)) + +#define KEY_ESC '\x1b' +#define KEY_TAB '\t' + +struct pane; + +typedef int (*pane_handler)(int c); +typedef void (*pane_updater)(struct pane *pane, int sel); + +struct link { + struct link *prev, *next; +}; + +struct pane { + WINDOW *win; + int sx, sy, ex, ey; + int w, h; + int active; + + pane_handler handle; + pane_updater update; +}; + +struct track { + char *title; + char *artist; + float duration; + struct link *tags; + + struct link link; +}; + +struct track_ref { + struct track *track; + + struct link link; +}; + +struct tag { + const char *name; + + struct link link; +}; + +enum { + STYLE_DEFAULT, + STYLE_TITLE, + STYLE_PANE_SEP +}; + +enum { + EXECUTE, + SEARCH +}; + +int scrw, scrh; +int quit; + +struct pane pane_left, pane_right, pane_bot; +struct pane *pane_sel; +float win_ratio; + +struct track *track_sel; +struct track *tracks; +struct track_ref *playlist; +int track_paused; + +int cmdshow, cmdcur; +int cmdmode; + +int rl_input, rl_input_avail; + +struct pane *const panes[] = { + &pane_left, + &pane_right, + &pane_bot +}; + +void init(void); +void cleanup(void); + +void cancel(int sig); + +void resize(void); +void pane_resize(struct pane *pane, + int sx, int sy, int ex, int ey); + +void pane_init(struct pane *p, pane_handler handle, pane_updater update); +void pane_title(struct pane *pane, const char *title, int highlight); + +char **track_name_completion(const char *text, int start, int end); +char *track_name_generator(const char *text, int state); + +int readline_getc(FILE *f); +int readline_input_avail(void); +void readline_consume(int c); +int exit_cmdmode(int a, int b); + +int strnwidth(const char *str, int len); + +void evalcmd(char *cmd); + +int track_input(int c); +void track_vis(struct pane *pane, int sel); + +int tag_input(int c); +void tag_vis(struct pane *pane, int sel); + +int cmd_input(int c); +void cmd_vis(struct pane *pane, int sel); + +void main_input(int c); +void main_vis(void); + +void +init(void) +{ + quit = 0; + win_ratio = 0.3f; + cmdshow = 0; + cmdcur = 0; + + /* readline */ + rl_initialize(); + rl_input = 0; + rl_input_avail = 0; + + rl_catch_signals = 0; + rl_catch_sigwinch = 0; + rl_deprep_term_function = NULL; + rl_prep_term_function = NULL; + rl_change_environment = 0; + + rl_getc_function = readline_getc; + rl_input_available_hook = readline_input_avail; + rl_callback_handler_install("", evalcmd); + rl_attempted_completion_function = track_name_completion; + rl_bind_key('\x07', exit_cmdmode); + + /* curses */ + initscr(); + cbreak(); + noecho(); + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + start_color(); + curs_set(0); + ESCDELAY = 0; + + signal(SIGINT, cancel); + + init_pair(STYLE_TITLE, COLOR_WHITE, COLOR_BLUE); + init_pair(STYLE_PANE_SEP, COLOR_BLUE, COLOR_BLACK); + + pane_init(&pane_left, tag_input, tag_vis); + pane_init(&pane_right, track_input, track_vis); + pane_init(&pane_bot, cmd_input, cmd_vis); + + pane_sel = &pane_left; + + atexit(cleanup); +} + +void +cleanup(void) +{ + rl_callback_handler_remove(); + delwin(pane_left.win); + delwin(pane_right.win); + delwin(pane_bot.win); + endwin(); +} + +void +cancel(int sig) +{ + if (pane_sel == &pane_bot) { + pane_sel = NULL; + } else { + quit = 1; + } +} + +void +resize(void) +{ + int i; + + getmaxyx(stdscr, scrh, scrw); + + if (scrw < 10 || scrh < 4) { + clear(); + printw("Term too small.."); + refresh(); + usleep(10000); + } + + pane_resize(&pane_left, 0, 0, win_ratio * scrw, 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); + 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, TITLE); + ATTR_ON(pane->win, A_BOLD); + 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); + ATTR_OFF(pane->win, A_BOLD); + STYLE_OFF(pane->win, TITLE); +} + +char ** +track_name_completion(const char *text, int start, int end) +{ + rl_attempted_completion_over = 1; + return rl_completion_matches(text, track_name_generator); +} + +char * +track_name_generator(const char *text, int state) +{ + static int list_index, len; + char *name; + + if (cmdmode != SEARCH) return NULL; + + if (!state) { + list_index = 0; + len = strlen(text); + } + + if (list_index++ == 0 && !strncmp("hello", text, len)) + return strdup("hello"); + // TODO: iter over playlist + // while ((name = track_names[list_index++])) { + // if (strncmp(name, text, len) == 0) { + // return strdup(name); + // } + // } + + return NULL; +} + +int +readline_input_avail(void) +{ + return rl_input_avail; +} + +int +readline_getc(FILE *dummy) +{ + rl_input_avail = false; + return rl_input; +} + +void +readline_consume(int c) +{ + rl_input = c; + rl_input_avail = true; + rl_callback_read_char(); +} + +int +exit_cmdmode(int a, int b) +{ + pane_sel = NULL; + move(5, 5); + wprintw(stdscr, "HIT %i %i", a, b); + return 0; +} + +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 +evalcmd(char *line) +{ + if (!line || !*line) { + pane_sel = NULL; + return; + } + + if (*line) add_history(line); +} + +int +tag_input(int c) +{ + return 0; +} + +void +tag_vis(struct pane *pane, int sel) +{ + werase(pane->win); + pane_title(pane, "Tags", sel); +} + +int +track_input(int c) +{ + return 0; +} + +void +track_vis(struct pane *pane, int sel) +{ + werase(pane->win); + pane_title(pane, "Tracks", sel); +} + +int +cmd_input(int c) +{ + readline_consume(c); + return 1; +} + +void +cmd_vis(struct pane *pane, int sel) +{ + int in_range; + + werase(pane->win); + + if (track_sel) + pane_title(pane, track_sel->title, track_paused); + else + pane_title(pane, "", 0); + + if (sel || cmdshow) { + wmove(pane->win, 2, 0); + waddch(pane->win, cmdmode == SEARCH ? '/' : ':'); + wprintw(pane->win, "%-*.*s", pane->w - 1, pane->w - 1, rl_line_buffer); + if (sel) { + cmdcur = strnwidth(rl_line_buffer, rl_point); + ATTR_ON(pane->win, A_REVERSE); + wmove(pane->win, 2, 1 + cmdcur); + in_range = cmdcur < strlen(rl_line_buffer); + waddch(pane->win, in_range ? rl_line_buffer[cmdcur] : ' '); + ATTR_OFF(pane->win, A_REVERSE); + } + } +} + +void +main_input(int c) +{ + switch (c) { + case KEY_TAB: + if (pane_sel == &pane_left) + pane_sel = &pane_right; + else + pane_sel = &pane_left; + break; + case KEY_ESC: + pane_sel = NULL; + break; + case KEY_LEFT: + pane_sel = &pane_left; + break; + case KEY_RIGHT: + pane_sel = &pane_right; + break; + case CTRL('c'): + quit = 1; + break; + case ':': + cmdmode = EXECUTE; + pane_sel = &pane_bot; + break; + case '?': + case '/': + cmdmode = SEARCH; + pane_sel = &pane_bot; + break; + } +} + +void +main_vis(void) +{ + int i; + + STYLE_ON(stdscr, TITLE); + move(0, pane_left.ex); + addch(' '); + STYLE_OFF(stdscr, TITLE); + + STYLE_ON(stdscr, PANE_SEP); + for (i = pane_left.sy + 1; i < pane_left.ey; i++) { + move(i, pane_left.ex); + addch(ACS_VLINE); + } + STYLE_OFF(stdscr, PANE_SEP); +} + +int +main(int argc, const char **argv) +{ + int i, c, handled; + + init(); + + c = KEY_RESIZE; + do { + if (c == KEY_RESIZE) { + resize(); + } else { + 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(); + + keypad(stdscr, pane_sel != &pane_bot); + c = getch(); + } while (!quit); + + cleanup(); +}