feat: add TUI client with FIFO communication and status display

This commit is contained in:
Loic Coenen
2026-05-14 17:22:02 +00:00
committed by Loic Coenen (aider)
parent 5341cb676a
commit 971372eac9
19 changed files with 382 additions and 111 deletions

View File

Binary file not shown.

View File

@@ -1,15 +1,18 @@
CC = gcc
CFLAGS = -Wall -Wextra -Wpedantic -std=c11
all: test_status_parse
all: looper-client test_status_parse
looper-client: src/main.c src/tui.c
$(CC) $(CFLAGS) -Isrc -o $@ $^ -lncurses
test_status_parse: tests/test_status_parse.c
$(CC) $(CFLAGS) -Isrc -o test_status_parse tests/test_status_parse.c src/tui.c -lncurses
test: test_status_parse
test: looper-client test_status_parse
./test_status_parse
.PHONY: all test clean
clean:
rm -f test_status_parse
rm -f looper-client test_status_parse

8
client/src/main.c Normal file
View File

@@ -0,0 +1,8 @@
#include "tui.h"
int main(void) {
tui_init();
tui_run();
tui_cleanup();
return 0;
}

View File

@@ -36,6 +36,14 @@ static const char *clip_state_string(ClipState s) { (void)s; return "?"; }
#define CELL_WIDTH 6
#define CELL_HEIGHT 3
/* status FIFO path */
#define STATUS_FIFO "/tmp/looper_status"
#define CMD_FIFO "/tmp/looper_cmd"
/* Percell state array (indexed by row*GRID_COLS+col) */
typedef enum { STATE_IDLE, STATE_RECORD, STATE_LOOPING, STATE_PAUSED } ChannelState;
static ChannelState cell_state[GRID_ROWS * GRID_COLS];
/* Color pairs */
enum {
COLOR_EMPTY=1, COLOR_RECORDING, COLOR_LOOPING, COLOR_STOPPED,
@@ -64,8 +72,6 @@ typedef struct {
static FuzzySearch fuzzy_search = {0};
/* ---------- Parse status line from engine status FIFO ---------- */
typedef enum { STATE_IDLE, STATE_RECORD, STATE_LOOPING, STATE_PAUSED } ChannelState;
bool parse_status_line(const char *line, int *ch, int *scene, ChannelState *state) {
int sta;
if (sscanf(line, "CH=%d SC=%d STATE=%d", ch, scene, &sta) == 3) {
@@ -85,14 +91,24 @@ bool parse_status_line(const char *line, int *ch, int *scene, ChannelState *stat
return false;
}
/* ---------- State to color (dummy: all white) ---------- */
static int state_to_color(ClipState s) { (void)s; return COLOR_EMPTY; }
/* ---------- State to color (uses cell_state array) ---------- */
static int state_to_color(ChannelState s) {
switch (s) {
case STATE_IDLE: return COLOR_EMPTY;
case STATE_RECORD: return COLOR_RECORDING;
case STATE_LOOPING: return COLOR_LOOPING;
case STATE_PAUSED: return COLOR_STOPPED;
default: return COLOR_EMPTY;
}
}
/* ---------- Draw cell (no AppState) ---------- */
static void draw_cell(int grid, int row, int col, bool selected) {
int y = row * CELL_HEIGHT + 3;
int x = col * CELL_WIDTH + 1;
int color = selected ? COLOR_SELECTED : COLOR_EMPTY;
int idx = row * GRID_COLS + col;
ChannelState s = cell_state[idx];
int color = selected ? COLOR_SELECTED : state_to_color(s);
attron(COLOR_PAIR(color));
for (int dy=0; dy<CELL_HEIGHT; dy++)
for (int dx=0; dx<CELL_WIDTH; dx++)
@@ -113,7 +129,7 @@ static void draw_grid(void) {
selected_grid, selected_row, selected_col);
if (show_help) {
attron(COLOR_PAIR(COLOR_HELP));
mvprintw(GRID_ROWS*CELL_HEIGHT+4, 0, "Help: h/j/k/l navigate, t record, d stop, s scene, ? help, q/Esc quit");
mvprintw(GRID_ROWS*CELL_HEIGHT+4, 0, "Help: h/j/k/l navigate, t record, d/D stop, s/S scene, a add, A add_midi, r remove, b bind, u unbind, ? help, Esc/Q quit");
attroff(COLOR_PAIR(COLOR_HELP));
}
refresh();
@@ -132,14 +148,42 @@ void tui_init(void) {
init_pair(COLOR_SELECTED, COLOR_BLACK, COLOR_CYAN);
init_pair(COLOR_HELP, COLOR_CYAN, COLOR_BLACK);
for (int i=0;i<26;i++) marks[i] = -1;
/* initialise cell states to idle */
for (int i = 0; i < GRID_ROWS * GRID_COLS; i++)
cell_state[i] = STATE_IDLE;
}
/* ---------- TUI run ---------- */
void tui_run(void) {
draw_grid();
while (1) {
int ch = getch();
switch (ch) {
/* read any available status lines */
int fd = open(STATUS_FIFO, O_RDONLY | O_NONBLOCK);
if (fd >= 0) {
char buf[256];
int n = read(fd, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\0';
char *line = buf;
while (*line) {
char *nl = strchr(line, '\n');
if (nl) *nl = '\0';
int ch, sc;
ChannelState st;
if (parse_status_line(line, &ch, &sc, &st)) {
if (ch >= 0 && ch < GRID_ROWS * GRID_COLS)
cell_state[ch] = st;
}
if (nl) {
*nl = '\n';
line = nl + 1;
} else break;
}
}
close(fd);
}
int chc = getch();
switch (chc) {
case 'h': case KEY_LEFT: selected_col = (selected_col-1+GRID_COLS)%GRID_COLS; break;
case 'j': case KEY_DOWN: selected_row = (selected_row+1)%GRID_ROWS; break;
case 'k': case KEY_UP: selected_row = (selected_row-1+GRID_ROWS)%GRID_ROWS; break;
@@ -150,13 +194,33 @@ void tui_run(void) {
send_command(cmd);
break;
}
case 's': {
case 's':
send_command("scene_next\n");
break;
}
case 'd': case 'S':
case 'S':
send_command("scene_prev\n");
break;
case 'd': case 'D':
send_command("stop\n");
break;
case 'a':
send_command("add\n");
break;
case 'A':
send_command("add_midi\n");
break;
case 'r':
send_command("remove\n");
break;
case 'b': {
char cmd[16];
snprintf(cmd, sizeof(cmd), "bind %d\n", selected_col);
send_command(cmd);
break;
}
case 'u':
send_command("unbind\n");
break;
case '?': show_help = !show_help; break;
case 27: case 'Q': return;
default: break;
@@ -167,5 +231,8 @@ void tui_run(void) {
void tui_cleanup(void) {
if (yank_buffer.clip_indices) free(yank_buffer.clip_indices);
/* delete FIFOs */
unlink(STATUS_FIFO);
unlink(CMD_FIFO);
curs_set(1); endwin();
}

BIN
client/test_status_parse Executable file
View File

Binary file not shown.