xdebug

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit e6233ef4f875bd1420469a0fdfab36f3ba12336b
parent ca75f09d87624b5a01a667491ad81e386df7b189
Author: Brian Swetland <swetland@frotz.net>
Date:   Wed,  1 Mar 2023 07:37:06 -0800

tui: initial checkin of a simple Text UI

- provide a log area and commandline, divided by an infobar
- provide mechanisms to append log lines
- provide a callback for commands

Diffstat:
Atui/test.c | 19+++++++++++++++++++
Atui/tui.c | 273+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atui/tui.h | 34++++++++++++++++++++++++++++++++++
3 files changed, 326 insertions(+), 0 deletions(-)

diff --git a/tui/test.c b/tui/test.c @@ -0,0 +1,19 @@ +// Copyright 2023, Brian Swetland <swetland@frotz.net> +// Licensed under the Apache License, Version 2.0. + +#include <tui.h> +#include <string.h> + +void handle_line(char* line, unsigned len) { + if (len) { + tui_printf("? %s\n", line); + } + if (!strcmp(line, "exit")) { + tui_exit(); + } +} + +int main(int argc, char** argv) { + tui_loop(handle_line); + return 0; +} diff --git a/tui/tui.c b/tui/tui.c @@ -0,0 +1,273 @@ +// Copyright 2023, Brian Swetland <swetland@frotz.net> +// Licensed under the Apache License, Version 2.0. + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include <tui.h> +#include <termbox.h> + +#define MAXWIDTH 128 + +typedef struct line LINE; +typedef struct ux UX; + +struct line { + LINE* prev; + LINE* next; + uint16_t len; + uint8_t fg; + uint8_t bg; + uint8_t text[MAXWIDTH]; +}; + +struct ux { + int w; + int h; + int invalid; + + uint8_t cmd[MAXWIDTH + 1]; + int len; + + LINE list; +}; + +static void paint(int x, int y, const char* str) { + while (*str) { + tb_change_cell(x++, y, *str++, TB_DEFAULT, TB_DEFAULT); + } +} + +static void paint_titlebar(UX *ux) { +} + +static void paint_infobar(UX *ux) { + for (int x = 0; x < ux->w; x++) { + tb_change_cell(x, ux->h - 2, '-', TB_DEFAULT | TB_REVERSE, TB_DEFAULT); + } +} + +static void paint_cmdline(UX *ux) { + uint8_t* ch = ux->cmd; + int y = ux->h - 1; + int w = ux->w; + for (int x = 0; x < w; x++) { + if (x < ux->len) { + tb_change_cell(x, y, *ch++, TB_DEFAULT, TB_DEFAULT); + } else { + tb_change_cell(x, y, ' ', TB_DEFAULT, TB_DEFAULT); + } + } + tb_set_cursor(ux->len >= w ? w - 1 : ux->len, y); +} + +static void paint_log(UX *ux) { + LINE* list = &ux->list; + int y = ux->h - 3; + int w = ux->w; + uint8_t c; + + for (LINE* line = list->prev; (line != list) && (y >= 0); line = line->prev) { + for (int x = 0; x < w; x++) { + c = (x < line->len) ? line->text[x] : ' '; + tb_change_cell(x, y, c, line->fg, line->bg); + } + y--; + } +} + +static int repaint(UX* ux) { + // clear entire display and adjust to any resize events + tb_clear(); + ux->w = tb_width(); + ux->h = tb_height(); + + if ((ux->w < 40) || (ux->h < 8)) { + paint(0, 0, "WINDOW TOO SMALL"); + return 1; + } + + paint_titlebar(ux); + paint_infobar(ux); + paint_cmdline(ux); + paint_log(ux); + return 0; +} + +static int handle_event(UX* ux, void (*cb)(char*, unsigned) ) { + struct tb_event ev; + tb_present(); + + if ((tb_poll_event(&ev) < 0) || + (ev.key == TB_KEY_CTRL_C)) { + return -1; + } + + // always process full repaints due to resize or user request + if ((ev.type == TB_EVENT_RESIZE) || + (ev.key == TB_KEY_CTRL_L)) { + ux->invalid = repaint(ux); + return 0; + } + + // ignore other input of the window is too small + if (ux->invalid) { + return 0; + } + + switch (ev.key) { + case 0: // printable character + // ignore fancy unicode characters + if (ev.ch > 255) { + break; + } + if (ux->len >= MAXWIDTH) { + break; + } + ux->cmd[ux->len++] = ev.ch; + paint_cmdline(ux); + break; + case TB_KEY_BACKSPACE: + case TB_KEY_BACKSPACE2: + if (ux->len > 0 ) { + ux->len--; + paint_cmdline(ux); + } + break; + case TB_KEY_ENTER: { + unsigned len = ux->len; + // clear command line + ux->len = 0; + paint_cmdline(ux); + tb_present(); + // process command + ux->cmd[len] = 0; + cb((void*) ux->cmd, len); + break; + } + default: +#if 0 // debug unexpected keys + if (ux->len < (MAXWIDTH - 6)) { + char tmp[5]; + sprintf(tmp, "%04x", ev.key); + ux->cmd[ux->len++] = '<'; + ux->cmd[ux->len++] = tmp[0]; + ux->cmd[ux->len++] = tmp[1]; + ux->cmd[ux->len++] = tmp[2]; + ux->cmd[ux->len++] = tmp[3]; + ux->cmd[ux->len++] = '>'; + paint_cmdline(ux); + } +#endif + break; + } + return 0; +} + +static int running = 1; + +// TODO: handle calls from outside of tui_handle(); +void tui_exit(void) { + running = 0; +} + +static UX ux = { + .list = { + .prev = &ux.list, + .next = &ux.list, + }, +}; + +void tui_loop(void (*cb)(char*, unsigned)) { + if (tb_init()) { + fprintf(stderr, "termbox init failed\n"); + return; + } + + tb_select_input_mode(TB_INPUT_ESC | TB_INPUT_SPACE); + + repaint(&ux); + + while (running && (handle_event(&ux, cb) == 0)) ; + + tb_shutdown(); +} + +static void tui_logline(uint8_t* text, unsigned len) { + LINE* line = malloc(sizeof(LINE)); + if (line == NULL) return; + memcpy(line->text, text, len); + line->len = len; + line->fg = TB_DEFAULT; + line->bg = TB_DEFAULT; + + // add line to the log + line->prev = ux.list.prev; + line->next = &ux.list; + line->prev->next = line; + ux.list.prev = line; + + // refresh the log + paint_log(&ux); + tb_present(); +} + +struct tui_ch { + unsigned len; + uint8_t buffer[MAXWIDTH]; +}; + +int tui_ch_create(tui_ch_t** out, unsigned flags) { + tui_ch_t* ch = calloc(1, sizeof(tui_ch_t)); + if (ch == NULL) { + return -1; + } else { + *out = ch; + return 0; + } +} + +void tui_ch_destroy(tui_ch_t* ch) { + free(ch); +} + +void tui_ch_vaprintf(tui_ch_t* ch, const char* fmt, va_list ap) { + char tmp[1024]; + int n = vsnprintf(tmp, sizeof(tmp), fmt, ap); + char *x = tmp; + while (n > 0) { + uint8_t c = *x++; + n--; + if ((c == '\n') && (ch->len > 0)) { + tui_logline(ch->buffer, ch->len); + ch->len = 0; + continue; + } + if ((c < ' ') || (c > 0x7e)) { + continue; + } + if (ch->len < MAXWIDTH) { + ch->buffer[ch->len++] = c; + } + } +} + +void tui_ch_printf(tui_ch_t* ch, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + tui_ch_vaprintf(ch, fmt, ap); + va_end(ap); +} + +void tui_printf(const char* fmt, ...) { + va_list ap; + tui_ch_t ch = { 0 }; + va_start(ap, fmt); + tui_ch_vaprintf(&ch, fmt, ap); + va_end(ap); + if (ch.len > 0) { + tui_logline(ch.buffer, ch.len); + } +} diff --git a/tui/tui.h b/tui/tui.h @@ -0,0 +1,34 @@ +// Copyright 2023, Brian Swetland <swetland@frotz.net> +// Licensed under the Apache License, Version 2.0. + +#pragma once + +#include <stdarg.h> + +void tui_loop(void (*callback)(char* line, unsigned len)); + +void tui_exit(void); + +// Write a line (or multiple lines separated by '\n') to the TUI log +// Non-printing and non-ascii characters are ignored. +// Lines larger than the TUI log max width (128) are truncated. +void tui_printf(const char* fmt, ...); + + +// TUI Channels provide a way for different entities to use a +// printf() interface to send log lines to the TUI without +// interleaving partial log lines. + +// They contain a line assembly buffer and only send lines to +// the TUI log when a newline character is encountered. + +// It is safe for different threads to use different channels +// to simultaneously send log lines, but it is NOT safe for +// different threads to simultaneously use the same channel. +typedef struct tui_ch tui_ch_t; + +int tui_ch_create(tui_ch_t** ch, unsigned flags); +void tui_ch_destroy(tui_ch_t* ch); +void tui_ch_printf(tui_ch_t* ch, const char* fmt, ...); +void tui_ch_vaprintf(tui_ch_t* ch, const char* fmt, va_list ap); +