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

BIN
engine/integration_test Executable file
View File

Binary file not shown.

BIN
engine/looper Executable file
View File

Binary file not shown.

View File

@@ -13,13 +13,17 @@ src/%.o: src/%.c
integration: looper tests/integration.c
$(CC) $(CFLAGS) -o integration_test tests/integration.c -ljack -lm
test_status_fifo: looper tests/test_status_fifo.c
$(CC) $(CFLAGS) -o test_status_fifo tests/test_status_fifo.c -ljack -lm
test: integration test_status_fifo
./test_status_fifo
./integration_test
test: integration
.PHONY: clean integration test
.PHONY: clean integration test_status_fifo test
clean:
rm -f looper integration_test src/*.o
rm -f looper integration_test test_status_fifo src/*.o
check:
cppcheck --enable=all --error-exitcode=1 --suppress=missingIncludeSystem --suppress=normalCheckLevelMaxBranches src/*.c --library=posix

BIN
engine/src/channel.o Normal file
View File

Binary file not shown.

BIN
engine/src/looper.o Normal file
View File

Binary file not shown.

BIN
engine/src/main.o Normal file
View File

Binary file not shown.

BIN
engine/src/midi.o Normal file
View File

Binary file not shown.

BIN
engine/src/pipe.o Normal file
View File

Binary file not shown.

BIN
engine/src/queue.o Normal file
View File

Binary file not shown.

BIN
engine/test_status_fifo Executable file
View File

Binary file not shown.

16
engine/tests/makefile Normal file
View File

@@ -0,0 +1,16 @@
CC = gcc
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -I../src -I$(JACK_CFLAGS)
LDFLAGS = -ljack -lpthread -lm
all: test_status_fifo
test_status_fifo: test_status_fifo.c ../src/looper.c ../src/channel.c ../src/midi.c ../src/queue.c ../src/pipe.c
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test: test_status_fifo
./test_status_fifo
.PHONY: all test clean
clean:
rm -f test_status_fifo

View File

@@ -6,33 +6,10 @@
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/select.h>
#define STATUS_FIFO "/tmp/looper_status"
#define CMD_FIFO "/tmp/looper_cmd"
static int write_cmd(const char *cmd) {
int fd = open(CMD_FIFO, O_WRONLY | O_NONBLOCK);
if (fd < 0) return -1;
size_t len = strlen(cmd);
int n = write(fd, cmd, len);
if (n == (int)len && cmd[len-1] != '\n')
write(fd, "\n", 1);
close(fd);
return (n >= 0) ? 0 : -1;
}
static int read_status_line(char *buf, size_t bufsize) {
int fd = open(STATUS_FIFO, O_RDONLY | O_NONBLOCK);
if (fd < 0) return -1;
FILE *f = fdopen(fd, "r");
if (!f) { close(fd); return -1; }
if (fgets(buf, bufsize, f) == NULL) {
fclose(f);
return -1;
}
fclose(f);
return 0;
}
#define CMD_FIFO "/tmp/looper_cmd"
static pid_t start_looper(void) {
pid_t pid = fork();
@@ -47,33 +24,83 @@ static pid_t start_looper(void) {
return pid;
}
/* Drain any stale data from the status FIFO */
static void drain_fifo(void) {
int fd = open(STATUS_FIFO, O_RDONLY | O_NONBLOCK);
if (fd < 0) return;
char buf[4096];
while (read(fd, buf, sizeof(buf)) > 0);
close(fd);
}
/* Read the first status line with a timeout (milliseconds).
* Returns 0 on success, -1 on timeout/error. */
static int read_status_line_timeout(char *buf, size_t bufsize, int timeout_ms) {
int fd = open(STATUS_FIFO, O_RDONLY | O_NONBLOCK);
if (fd < 0) return -1;
fd_set set;
struct timeval tv;
FD_ZERO(&set);
FD_SET(fd, &set);
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
int ret = select(fd + 1, &set, NULL, NULL, &tv);
if (ret <= 0) {
close(fd);
return -1;
}
int n = read(fd, buf, bufsize - 1);
close(fd);
if (n <= 0) return -1;
buf[n] = '\0';
/* keep only the first line */
char *nl = strchr(buf, '\n');
if (nl) *nl = '\0';
return 0;
}
static int test_status_after_record(void) {
printf("Test: status FIFO reports recording state\n");
printf("Test: status FIFO reports RECORD state after record command\n");
pid_t pid = start_looper();
if (pid < 0) return 1;
usleep(200000);
if (write_cmd("record 0") != 0) {
fprintf(stderr, " FAIL: cannot write record command\n");
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
usleep(500000);
char line[256];
if (read_status_line(line, sizeof(line)) != 0) {
fprintf(stderr, " FAIL: cannot read status line\n");
/* Give looper time to start main loop and begin writing status */
usleep(1500000);
drain_fifo();
/* Send record 0 command via FIFO */
int fd_cmd = open(CMD_FIFO, O_WRONLY);
if (fd_cmd < 0) {
fprintf(stderr, " FAIL: cannot open command FIFO\n");
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
write(fd_cmd, "record 0\n", 9);
close(fd_cmd);
/* Keep reading status lines until we see RECORD or timeout (5 seconds) */
int found = 0;
int ch, sc;
char state[32];
if (sscanf(line, "CH=%d SC=%d STATE=%31s", &ch, &sc, state) != 3) {
fprintf(stderr, " FAIL: malformed status line: %s\n", line);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
char line[256];
for (int tries = 0; tries < 50; tries++) {
if (read_status_line_timeout(line, sizeof(line), 100) != 0) {
usleep(100000);
continue;
}
if (sscanf(line, "CH=%d SC=%d STATE=%31s", &ch, &sc, state) != 3)
continue;
if (ch == 0 && sc == 0 && strcmp(state, "RECORD") == 0) {
found = 1;
break;
}
}
if (ch != 0 || sc != 0 || strcmp(state, "RECORD") != 0) {
fprintf(stderr, " FAIL: expected CH=0 SC=0 STATE=RECORD, got: CH=%d SC=%d STATE=%s\n",
ch, sc, state);
if (!found) {
fprintf(stderr, " FAIL: did not see STATE=RECORD for CH=0 SC=0 within 5 seconds\n");
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}