diff options
| author | info@mode42.com <info@mode42.com> | 2026-08-07 18:23:28 +0000 |
|---|---|---|
| committer | info@mode42.com <info@mode42.com> | 2026-08-07 18:23:28 +0000 |
| commit | fa05a5f8238e9e1417235711656e5062ccc843a7 (patch) | |
| tree | 46fbbd18de54492b59307c16efcc7f3152723238 /stacks/terminal/max25_ui.c | |
Initial push
Diffstat (limited to 'stacks/terminal/max25_ui.c')
| -rw-r--r-- | stacks/terminal/max25_ui.c | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/stacks/terminal/max25_ui.c b/stacks/terminal/max25_ui.c new file mode 100644 index 0000000..91cc977 --- /dev/null +++ b/stacks/terminal/max25_ui.c @@ -0,0 +1,407 @@ +#include "max25_ui.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef MAX25_HAVE_NCURSES +#include <ncurses.h> +#endif + +struct max25_ui { + int menu_open; + char rx_lines[MAX25_UI_RX_LINES][MAX25_LINE_MAX]; + int rx_count; + int rx_head; +#ifdef MAX25_HAVE_NCURSES + int ncurses_on; +#endif +}; + +#ifdef MAX25_HAVE_NCURSES +static int term_ok_for_ncurses(void) +{ + const char *term = getenv("TERM"); + + if (term == NULL || term[0] == '\0') { + return 0; + } + if (strcmp(term, "dumb") == 0) { + return 0; + } + if (strncmp(term, "unknown", 7) == 0) { + return 0; + } + return 1; +} +#endif + +int max25_ui_init(max25_ui_t **ui) +{ + max25_ui_t *u; + + if (ui == NULL) { + return -1; + } + + u = calloc(1, sizeof(*u)); + if (u == NULL) { + return -1; + } + +#ifdef MAX25_HAVE_NCURSES + if (isatty(STDOUT_FILENO) && isatty(STDIN_FILENO) && term_ok_for_ncurses() + && initscr() != NULL) { + cbreak(); + noecho(); + keypad(stdscr, TRUE); + nodelay(stdscr, TRUE); + if (has_colors()) { + start_color(); + init_pair(1, COLOR_WHITE, COLOR_BLACK); + } + bkgd(COLOR_PAIR(1)); + curs_set(0); + clear(); + refresh(); + u->ncurses_on = 1; + } +#endif + + *ui = u; + return 0; +} + +void max25_ui_shutdown(max25_ui_t *ui) +{ + if (ui == NULL) { + return; + } +#ifdef MAX25_HAVE_NCURSES + if (ui->ncurses_on) { + endwin(); + } +#endif + free(ui); +} + +void max25_ui_apply_palette(void) +{ + fputs(MAX25_TERM_SGR, stdout); + fflush(stdout); +} + +void max25_ui_clear_screen(void) +{ + fputs(MAX25_TERM_CLEAR, stdout); + fputs(MAX25_TERM_SGR, stdout); + fflush(stdout); +} + +int max25_ui_has_ncurses(const max25_ui_t *ui) +{ +#ifdef MAX25_HAVE_NCURSES + return ui != NULL && ui->ncurses_on; +#else + (void)ui; + return 0; +#endif +} + +void max25_ui_reset_rx(max25_ui_t *ui) +{ + if (ui == NULL) { + return; + } + ui->rx_count = 0; + ui->rx_head = 0; + memset(ui->rx_lines, 0, sizeof(ui->rx_lines)); +} + +void max25_ui_append_rx(max25_ui_t *ui, const char *text) +{ + int slot; + + if (ui == NULL || text == NULL) { + return; + } + + slot = ui->rx_head; + strncpy(ui->rx_lines[slot], text, MAX25_LINE_MAX - 1); + ui->rx_lines[slot][MAX25_LINE_MAX - 1] = '\0'; + ui->rx_head = (ui->rx_head + 1) % MAX25_UI_RX_LINES; + if (ui->rx_count < MAX25_UI_RX_LINES) { + ui->rx_count++; + } +} + +static void draw_header_plain(const max25_status_t *status) +{ + printf("%sMAX25 Terminal DEVICE: %-12s CALLERID: %-9s CALLID: %-9s ax25-ui: %s connected: %s F10=Menu\n", + MAX25_TERM_SGR, + status->device[0] != '\0' ? status->device : "-", + status->callerid, status->callid, + status->ax25_ui ? "on" : "off", + status->connected ? "yes" : "no"); + fflush(stdout); +} + +static void draw_rx_plain(const max25_ui_t *ui) +{ + int i; + int start; + int idx; + + if (ui == NULL) { + return; + } + + start = ui->rx_count < MAX25_UI_RX_LINES ? 0 : ui->rx_head; + for (i = 0; i < ui->rx_count; i++) { + idx = (start + i) % MAX25_UI_RX_LINES; + printf("%s%s\n", MAX25_TERM_SGR, ui->rx_lines[idx]); + } + fflush(stdout); +} + +void max25_ui_draw_screen(max25_ui_t *ui, const max25_status_t *status, + const char *input_line) +{ + int i; + int start; + int idx; + int rows; + int y; + + if (ui == NULL || status == NULL) { + return; + } + +#ifdef MAX25_HAVE_NCURSES + if (ui->ncurses_on && !ui->menu_open) { + getmaxyx(stdscr, rows, y); + (void)y; + if (rows < 4) { + rows = 24; + } + + attron(COLOR_PAIR(1)); + mvprintw(0, 0, + "MAX25 Terminal DEVICE: %-12s CALLERID: %-9s CALLID: %-9s ax25-ui: %s connected: %s F10=Menu", + status->device[0] != '\0' ? status->device : "-", + status->callerid, status->callid, + status->ax25_ui ? "on" : "off", + status->connected ? "yes" : "no"); + clrtoeol(); + + start = ui->rx_count < MAX25_UI_RX_LINES ? 0 : ui->rx_head; + for (i = 0; i < ui->rx_count && i + 2 < rows - 2; i++) { + idx = (start + i) % MAX25_UI_RX_LINES; + mvprintw(i + 2, 0, "%.*s", (int)(rows > 0 ? 200 : 80), ui->rx_lines[idx]); + clrtoeol(); + } + + mvprintw(rows - 2, 0, "%s", "--------------------------------------------------------"); + clrtoeol(); + mvprintw(rows - 1, 0, "> %s", input_line != NULL ? input_line : ""); + clrtoeol(); + attroff(COLOR_PAIR(1)); + refresh(); + return; + } +#endif + + if (!ui->menu_open) { + fputs(MAX25_TERM_CLEAR, stdout); + fputs(MAX25_TERM_SGR, stdout); + draw_header_plain(status); + draw_rx_plain(ui); + printf("%s> %s\n", MAX25_TERM_SGR, input_line != NULL ? input_line : ""); + fflush(stdout); + } +} + +static void draw_menu_plain(const max25_status_t *status) +{ + puts(""); + puts("+- MAX25 Terminal ---------------------+"); + printf("| DEVICE: %-12s |\n", + status->device[0] != '\0' ? status->device : "-"); + printf("| CALLERID: %-9s CALLID: %-9s |\n", + status->callerid, status->callid); + puts("+---------------------------------------+"); + puts("| 1 Change CALLERID (live) |"); + puts("| 2 Change CALLID (live) |"); + puts("| 3 Status |"); + puts("| 4 Send line |"); + puts("| 5 RX only (Monitor) |"); + puts("| 6 Connection on/off |"); + puts("| 7 Change DEVICE (TX target) |"); + puts("| 8 List DEVICES (max25d) |"); + puts("| 0 Quit client |"); + puts("+---------------------------------------+"); + puts("Pick a number - F10 closes"); + fflush(stdout); +} + +#ifdef MAX25_HAVE_NCURSES +static void draw_menu_ncurses(const max25_status_t *status, int y0) +{ + attron(COLOR_PAIR(1)); + mvprintw(y0, 0, "+- MAX25 Terminal ---------------------+"); + mvprintw(y0 + 1, 0, "| DEVICE: %-12s |", + status->device[0] != '\0' ? status->device : "-"); + mvprintw(y0 + 2, 0, "| CALLERID: %-9s CALLID: %-9s |", + status->callerid, status->callid); + mvprintw(y0 + 3, 0, "+---------------------------------------+"); + mvprintw(y0 + 4, 0, "| 1 Change CALLERID (live) |"); + mvprintw(y0 + 5, 0, "| 2 Change CALLID (live) |"); + mvprintw(y0 + 6, 0, "| 3 Status |"); + mvprintw(y0 + 7, 0, "| 4 Send line |"); + mvprintw(y0 + 8, 0, "| 5 RX only (Monitor) |"); + mvprintw(y0 + 9, 0, "| 6 Connection on/off |"); + mvprintw(y0 + 10, 0, "| 7 Change DEVICE (TX target) |"); + mvprintw(y0 + 11, 0, "| 8 List DEVICES (max25d) |"); + mvprintw(y0 + 12, 0, "| 0 Quit client |"); + mvprintw(y0 + 13, 0, "+---------------------------------------+"); + mvprintw(y0 + 14, 0, "Pick a number - F10 closes"); + attroff(COLOR_PAIR(1)); +} +#endif + +void max25_ui_show_menu(max25_ui_t *ui, const max25_status_t *status) +{ + if (ui == NULL || status == NULL) { + return; + } + ui->menu_open = 1; + +#ifdef MAX25_HAVE_NCURSES + if (ui->ncurses_on) { + clear(); + bkgd(COLOR_PAIR(1)); + draw_menu_ncurses(status, 2); + refresh(); + return; + } +#endif + fputs(MAX25_TERM_CLEAR, stdout); + fputs(MAX25_TERM_SGR, stdout); + draw_menu_plain(status); +} + +void max25_ui_hide_menu(max25_ui_t *ui) +{ + if (ui == NULL) { + return; + } + ui->menu_open = 0; +#ifdef MAX25_HAVE_NCURSES + if (ui->ncurses_on) { + clear(); + bkgd(COLOR_PAIR(1)); + refresh(); + } +#endif +} + +int max25_ui_menu_visible(const max25_ui_t *ui) +{ + return ui != NULL && ui->menu_open; +} + +max25_menu_action_t max25_ui_menu_pick(max25_ui_t *ui, int ch) +{ + (void)ui; + switch (ch) { + case '1': + return MAX25_MENU_CALLERID; + case '2': + return MAX25_MENU_CALLID; + case '3': + return MAX25_MENU_STATUS; + case '4': + return MAX25_MENU_SEND; + case '5': + return MAX25_MENU_MONITOR; + case '6': + return MAX25_MENU_CONNECT; + case '7': + return MAX25_MENU_DEVICE; + case '8': + return MAX25_MENU_DEVICES; + case '0': + return MAX25_MENU_QUIT; + default: + return MAX25_MENU_NONE; + } +} + +int max25_ui_prompt(max25_ui_t *ui, const char *label, char *buf, size_t buf_sz) +{ + if (label == NULL || buf == NULL || buf_sz == 0) { + return -1; + } + +#ifdef MAX25_HAVE_NCURSES + if (ui != NULL && ui->ncurses_on) { + int ch; + size_t len = 0; + int rows; + + getmaxyx(stdscr, rows, ch); + (void)rows; + echo(); + curs_set(1); + attron(COLOR_PAIR(1)); + mvprintw(14, 0, "%s: ", label); + clrtoeol(); + refresh(); + buf[0] = '\0'; + + nodelay(stdscr, FALSE); + while (len + 1 < buf_sz) { + ch = getch(); + if (ch == '\n' || ch == KEY_ENTER || ch == '\r') { + break; + } + if (ch == 27) { + nodelay(stdscr, TRUE); + noecho(); + curs_set(0); + return -1; + } + if (ch == KEY_BACKSPACE || ch == 127 || ch == '\b') { + if (len > 0) { + len--; + buf[len] = '\0'; + mvprintw(14, (int)strlen(label) + 2, "%s ", buf); + clrtoeol(); + refresh(); + } + continue; + } + if (ch >= 32 && ch < 127) { + buf[len++] = (char)ch; + buf[len] = '\0'; + mvprintw(14, (int)strlen(label) + 2, "%s", buf); + refresh(); + } + } + nodelay(stdscr, TRUE); + noecho(); + curs_set(0); + attroff(COLOR_PAIR(1)); + return (int)len; + } +#endif + + printf("%s%s: ", MAX25_TERM_SGR, label); + fflush(stdout); + if (fgets(buf, (int)buf_sz, stdin) == NULL) { + return -1; + } + buf[strcspn(buf, "\r\n")] = '\0'; + return (int)strlen(buf); +} |
