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:
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);