tmus

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

commit bba6cce573d46b02dbb207b005721b370cab1698
parent 7146f1c08cf06c5865181f6e45b70505ca89d7a2
Author: Louis Burda <quent.burda@gmail.com>
Date:   Wed, 13 Mar 2024 19:33:30 +0100

Add initial rudimentary grapheme support

Diffstat:
M.gitmodules | 3+++
MMakefile | 9+++++++--
Alib/libgrapheme | 1+
Msrc/history.c | 64+++++++++++++++++++++++++++++++++++++---------------------------
Msrc/history.h | 1+
Msrc/player_mplay.c | 16+++++++++++++---
Msrc/tui.c | 39++++++++++++++++++++++++++++++---------
Msrc/util.c | 64+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/util.h | 5+++++
9 files changed, 160 insertions(+), 42 deletions(-)

diff --git a/.gitmodules b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/mplay"] path = lib/mplay url = git@sinitax.com:sinitax/mplay +[submodule "lib/libgrapheme"] + path = lib/libgrapheme + url = https://git.suckless.org/libgrapheme diff --git a/Makefile b/Makefile @@ -1,5 +1,6 @@ CFLAGS = -I src -g $(shell pkg-config --cflags glib-2.0 dbus-1) -CFLAGS += -I lib/liblist/include -Wunused-variable -Wmissing-prototypes +CFLAGS += -I lib/liblist/include -I lib/libgrapheme/ +CFLAGS += -Wunused-variable -Wmissing-prototypes LDLIBS = -lcurses $(shell pkg-config --libs glib-2.0 dbus-1) DEPFLAGS = -MT $@ -MMD -MP -MF build/$*.d @@ -22,6 +23,7 @@ OBJS = $(SRCS:src/%.c=build/%.o) build/player_$(BACKEND).o DEPS = $(OBJS:%.o=%.d) LIBLIST_A = lib/liblist/build/liblist.a +LIBGRAPHEME_A = lib/libgrapheme/libgrapheme.a PREFIX ?= /usr/local BINDIR ?= /bin @@ -47,7 +49,10 @@ include $(DEPS) $(LIBLIST_A): make -C lib/liblist DEBUG=1 build/liblist.a -tmus: $(OBJS) $(LIBLIST_A) +$(LIBGRAPHEME_A): + make -C lib/libgrapheme DEBUG=1 libgrapheme.a + +tmus: $(OBJS) $(LIBLIST_A) $(LIBGRAPHEME_A) $(CC) -o tmus $^ $(CFLAGS) $(LDLIBS) install: tmus diff --git a/lib/libgrapheme b/lib/libgrapheme @@ -0,0 +1 @@ +Subproject commit c8b34aa04ac8702e55ba4b8946d6794c9c6056f5 diff --git a/src/history.c b/src/history.c @@ -4,6 +4,8 @@ #include "history.h" #include "util.h" +#include "grapheme.h" + #include <string.h> #include <stdlib.h> @@ -124,6 +126,7 @@ inputln_init(struct inputln *ln) ln->len = 0; ln->cap = 0; ln->cur = 0; + ln->curpos = 0; ln->link = LIST_LINK_INIT; inputln_resize(ln, 128); @@ -169,51 +172,56 @@ inputln_resize(struct inputln *ln, size_t size) void inputln_left(struct inputln *ln) { - ln->cur = MAX(0, ln->cur-1); + ln->cur = utf8_next_break_left(ln->buf, ln->cur); + ln->curpos = text_width(ln->buf, ln->cur); } void inputln_right(struct inputln *ln) { - ln->cur = MIN(ln->len, ln->cur+1); + ln->cur = utf8_next_break_right(ln->buf, ln->cur); + ln->curpos = text_width(ln->buf, ln->cur); } void -inputln_addch(struct inputln *line, char c) +inputln_addch(struct inputln *ln, char c) { int i; - if (line->len + 1 >= line->cap) { - line->cap *= 2; - line->buf = realloc(line->buf, line->cap); + if (ln->len + 1 >= ln->cap) { + ln->cap *= 2; + ln->buf = realloc(ln->buf, ln->cap); } - for (i = line->len; i > line->cur; i--) - line->buf[i] = line->buf[i-1]; - line->buf[line->cur] = c; + for (i = ln->len; i > ln->cur; i--) + ln->buf[i] = ln->buf[i-1]; + ln->buf[ln->cur] = c; - line->len++; - line->cur++; + ln->len++; + ln->cur++; + ln->buf[ln->len] = '\0'; - line->buf[line->len] = '\0'; + ln->curpos = text_width(ln->buf, ln->cur); } void -inputln_del(struct inputln *line, int n) +inputln_del(struct inputln *ln, int n) { + size_t next; int i; - if (!line->cur) return; + if (!ln->cur) return; - n = MIN(n, line->cur); - for (i = line->cur; i <= line->len; i++) - line->buf[i-n] = line->buf[i]; + next = utf8_next_break_left(ln->buf, ln->cur); + n = ln->cur - next; + for (i = ln->cur; i <= ln->len; i++) + ln->buf[i-n] = ln->buf[i]; - for (i = line->len - n; i <= line->len; i++) - line->buf[i] = 0; + ln->len -= n; + ln->cur -= n; + ln->buf[ln->len] = '\0'; - line->len -= n; - line->cur -= n; + ln->curpos = text_width(ln->buf, ln->cur); } void @@ -227,15 +235,17 @@ inputln_copy(struct inputln *dst, struct inputln *src) dst->buf = astrdup(src->buf); dst->cap = src->len + 1; dst->cur = dst->len; + dst->curpos = text_width(dst->buf, dst->len); } void -inputln_replace(struct inputln *line, const char *str) +inputln_replace(struct inputln *ln, const char *str) { - line->len = strlen(str); - if (line->cap <= line->len) - inputln_resize(line, line->len + 1); - strncpy(line->buf, str, line->len + 1); - line->cur = line->len; + ln->len = strlen(str); + if (ln->cap <= ln->len) + inputln_resize(ln, ln->len + 1); + strncpy(ln->buf, str, ln->len + 1); + ln->cur = ln->len; + ln->curpos = text_width(ln->buf, ln->len); } diff --git a/src/history.h b/src/history.h @@ -8,6 +8,7 @@ struct inputln { char *buf; int len, cap; int cur; + int curpos; struct list_link link; }; diff --git a/src/player_mplay.c b/src/player_mplay.c @@ -106,9 +106,7 @@ mplay_run(struct track *track) void mplay_kill(void) { - if (!player.loaded) - return; - + if (!player.loaded) return; kill(mplay.pid, SIGKILL); waitpid(mplay.pid, NULL, 0); player.loaded = false; @@ -271,6 +269,8 @@ player_toggle_pause(void) { char *line, *arg; + if (!player.loaded) return PLAYER_ERR; + fputc(MPLAY_ACTION_KEY_PAUSE, mplay.stdin); line = mplay_readline(); if (!line || !(arg = mplay_info_arg(line, MPLAY_INFO_STR_PAUSE))) { @@ -290,6 +290,8 @@ player_toggle_pause(void) int player_pause(void) { + if (!player.loaded) return PLAYER_ERR; + if (player.state != PLAYER_STATE_PAUSED) player_toggle_pause(); @@ -299,6 +301,8 @@ player_pause(void) int player_resume(void) { + if (!player.loaded) return PLAYER_ERR; + if (player.state != PLAYER_STATE_PLAYING) player_toggle_pause(); @@ -314,6 +318,8 @@ player_play(void) int player_stop(void) { + if (!player.loaded) return PLAYER_ERR; + player_clear_track(); return PLAYER_OK; @@ -324,6 +330,8 @@ player_seek(int sec) { char *line, *arg; + if (!player.loaded) return PLAYER_ERR; + putc(MPLAY_ACTION_KEY_SEEK, mplay.stdin); fprintf(mplay.stdin, "%i\n", sec); line = mplay_readline(); @@ -344,6 +352,8 @@ player_set_volume(unsigned int vol) { char *line, *arg; + if (!player.loaded) return PLAYER_ERR; + if (player.volume == -1) { PLAYER_STATUS("volume control not supported"); return PLAYER_ERR; diff --git a/src/tui.c b/src/tui.c @@ -1,4 +1,3 @@ -#include <stdbool.h> #define NCURSES_WIDECHAR 1 #define _GNU_SOURCE @@ -16,6 +15,7 @@ #include "strbuf.h" #include "util.h" +#include "grapheme.h" #include <ncurses.h> #include <unistd.h> @@ -56,6 +56,7 @@ static void seek_next_selected_tag(void); static void delete_current_tag(void); static bool tag_name_cmp(const void *p1, const void *p2, void *user); static void sort_visible_tags(void); +static void select_all_tags(void); static bool tag_pane_input(wint_t c); static void tag_pane_vis(struct pane *pane, int sel); @@ -435,6 +436,20 @@ sort_visible_tags(void) LIST_OFFSET(struct tag, link), NULL); } +void +select_all_tags(void) +{ + struct list_link *link; + struct tag *tag; + + list_clear(&tags_sel); + for (LIST_ITER(&tags, link)) { + tag = LIST_UPCAST(link, struct tag, link); + list_insert_back(&tags_sel, &tag->link_sel); + } + playlist_outdated = true; +} + bool tag_pane_input(wint_t c) { @@ -475,6 +490,9 @@ tag_pane_input(wint_t c) case KEY_CTRL(L's'): sort_visible_tags(); break; + case KEY_CTRL(L'a'): + select_all_tags(); + break; default: return false; } @@ -950,7 +968,7 @@ cmd_pane_input(wint_t c) case KEY_RIGHT: inputln_right(history->sel); break; - case KEY_CTRL('w'): + case KEY_CTRL(L'w'): inputln_del(history->sel, history->sel->cur); break; case KEY_UP: @@ -1012,7 +1030,7 @@ cmd_pane_input(wint_t c) break; default: /* TODO: wide char input support */ - if (!isprint(c)) return 0; + if (c <= 0) break; inputln_addch(history->sel, c); completion_reset = 1; break; @@ -1117,9 +1135,14 @@ cmd_pane_vis(struct pane *pane, int sel) 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] : L' '); + wmove(pane->win, 2, offset + cmd->curpos); + if (cmd->cur >= cmd->len) { + waddch(pane->win, ' '); + } else { + size_t n = grapheme_next_character_break_utf8( + cmd->buf + cmd->cur, cmd->len - cmd->cur); + waddnstr(pane->win, cmd->buf + cmd->cur, n); + } ATTR_OFF(pane->win, A_REVERSE); } } else if (user_status && user_status_uptime) { @@ -1204,11 +1227,9 @@ main_input(wint_t c) pane_sel = pane_after_cmd; break; case KEY_LEFT: - if (!player.loaded) break; player_seek(player.time_pos - 10); break; case KEY_RIGHT: - if (!player.loaded) break; player_seek(player.time_pos + 10); break; case L'o': @@ -1356,7 +1377,7 @@ tui_resize(void) leftw = 0; for (LIST_ITER(&tags, link)) { tag = LIST_UPCAST(link, struct tag, link); - leftw = MAX(leftw, strlen(tag->name)); + leftw = MAX(leftw, text_width(tag->name, strlen(tag->name))); } leftw = MAX(leftw + 1, 0.2f * scrw); diff --git a/src/util.c b/src/util.c @@ -1,4 +1,4 @@ -#include <bits/time.h> +#include <stddef.h> #define _XOPEN_SOURCE 600 #define _GNU_SOURCE @@ -6,6 +6,9 @@ #include "util.h" #include "log.h" +#include "grapheme.h" + +#include <wchar.h> #include <time.h> #include <execinfo.h> #include <errno.h> @@ -200,3 +203,62 @@ current_ms(void) return ms; } + +size_t +text_width(const char *utf8, size_t len) +{ + size_t left; + size_t n, width; + uint32_t out; + + width = 0; + left = len; + while (left) { + n = MAX(1, grapheme_next_character_break_utf8(utf8, left)); + grapheme_decode_utf8(utf8, n, &out); + if (out == GRAPHEME_INVALID_CODEPOINT) + width += 1; + else + width += wcwidth(out); + utf8 += n; + left -= n; + } + + return width; +} + +size_t +utf8_next_break_left(const char *utf8, size_t prev) +{ + size_t pos, n; + size_t len, left; + + len = strlen(utf8); + + left = len; + pos = 0; + while (left) { + n = MAX(1, grapheme_next_character_break_utf8(utf8, left)); + if (pos + n >= prev) { + return pos; + } + utf8 += n; + left -= n; + pos += n; + } + + return len; +} + +size_t +utf8_next_break_right(const char *utf8, size_t pos) +{ + size_t len; + + len = strlen(utf8); + if (len > pos) { + pos += MAX(1, grapheme_next_character_break_utf8(utf8 + pos, len - pos)); + } + + return pos; +} diff --git a/src/util.h b/src/util.h @@ -37,3 +37,8 @@ char *sanitized(const char *instr); const char *timestr(unsigned int seconds); uint64_t current_ms(void); + +size_t text_width(const char *utf8, size_t len); + +size_t utf8_next_break_left(const char *utf8, size_t pos); +size_t utf8_next_break_right(const char *utf8, size_t pos);