QA findings from a multi-round review of the FreeDOS submission prep work:
- TUI rendering refactor: src/tui.c emitted ANSI escape sequences via
printf, which displays as raw text on bare FreeDOS (no ANSI.SYS).
Add four HAL ops (tui_enter, tui_leave, render_run, set_cursor_shape)
and route per-cell rendering through them. POSIX backend keeps the
ANSI path; DOS backend drives BIOS INT 10h via the existing
bios_set_cursor / bios_write_char helpers. The TUI's logical cursor
goes through the saved orig_locate to avoid recursing through the
swapped-in gw_hal->locate.
- DOS extended-key mapping: dos_getch returns 0x100 | scancode for
arrows / F-keys; tui_read_key wasn't translating those to its TK_*
constants, so the editor never saw arrow keys or F1-F10 on DOS.
Add a __MSDOS__-conditional translation table in tui_read_key.
- Version banner: GW_VERSION was still 0.16.0 even though the v0.17.0
release prep was already in CHANGES.TXT. Bump.
- Compiler PulseAudio link: gwbasic-compile -c hardcoded
'-lgwrt -lm -lpthread' on the gcc command line. When libgwrt was
built against libpulse-simple (the default on any host with the
PulseAudio dev headers installed), the compile workflow failed with
'undefined reference to pa_simple_drain'. CMake now passes
GWRT_HAS_PULSEAUDIO to gwbasic-compile when libpulse is present, and
the compiler appends -lpulse-simple to the link line.
- FRE("") garbage collection: the interpreter skipped strpool_gc with a
comment 'unsafe during expression eval', but that's exactly what real
GW-BASIC's FRE("") does (and the AOT compiler path already did). Add
the GC call; strpool_pin/unpin is the existing escape hatch if a
caller has live pool pointers on the C stack. Fixes the string_gc
compat test.
- Test harness normalization: run_tests.sh stripped trailing whitespace
on the actual output but not the expected file, causing spurious
mismatches against golden files captured from real GWBASIC.EXE.
Normalize both sides identically. Fixes the peek_gfx mismatch.
- Print_using: snprintf into mantissa[32] with %.*f and an unbounded
dec triggered a -Wformat-truncation warning. Clamp dec to 20 (IEEE
double has at most ~17 significant decimal digits).
- Doc/version consistency: 16-bit binary size reported as 127KB in one
place and 128KB in three; standardize on 128KB. HAL backend count
said '1 file' but is now 2. CI test count said 'all 66 test
programs' but is 72. Add a v0.17.0 row to the development.md table.
Update getting-started.md DOS section to match the BIOS-rendering
reality and add a manual TUI verification checklist.
- dos_init now writes back BIOS-reported cols/rows to dos_hal struct
fields (forward-declared so dos_init can reference it).
After these changes: 72/72 interpreter tests pass, compat 68/68
matched, no warnings on the Linux build.
218 lines
4.8 KiB
C
218 lines
4.8 KiB
C
#include "hal.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <termios.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
static struct termios orig_termios;
|
|
static int raw_mode = 0;
|
|
static int termios_saved = 0;
|
|
static int cursor_row = 0;
|
|
static int cursor_col = 0;
|
|
static int pending_char = -1;
|
|
|
|
static void posix_putch(int ch)
|
|
{
|
|
putchar(ch);
|
|
if (ch == '\n') {
|
|
cursor_row++;
|
|
cursor_col = 0;
|
|
} else if (ch == '\r') {
|
|
cursor_col = 0;
|
|
} else {
|
|
cursor_col++;
|
|
}
|
|
}
|
|
|
|
static void posix_puts(const char *s)
|
|
{
|
|
while (*s)
|
|
posix_putch(*s++);
|
|
}
|
|
|
|
static int posix_getch(void)
|
|
{
|
|
fflush(stdout);
|
|
if (pending_char >= 0) {
|
|
int ch = pending_char;
|
|
pending_char = -1;
|
|
return ch;
|
|
}
|
|
if (raw_mode) {
|
|
/* Set VMIN=1 to block until a byte arrives */
|
|
struct termios t;
|
|
tcgetattr(STDIN_FILENO, &t);
|
|
t.c_cc[VMIN] = 1;
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &t);
|
|
unsigned char ch;
|
|
read(STDIN_FILENO, &ch, 1);
|
|
t.c_cc[VMIN] = 0;
|
|
tcsetattr(STDIN_FILENO, TCSANOW, &t);
|
|
return ch;
|
|
}
|
|
return getchar();
|
|
}
|
|
|
|
static bool posix_kbhit(void)
|
|
{
|
|
if (pending_char >= 0)
|
|
return true;
|
|
if (!raw_mode)
|
|
return false;
|
|
unsigned char ch;
|
|
ssize_t n = read(STDIN_FILENO, &ch, 1);
|
|
if (n == 1) {
|
|
pending_char = ch;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void posix_locate(int row, int col)
|
|
{
|
|
printf("\033[%d;%dH", row, col);
|
|
fflush(stdout);
|
|
cursor_row = row - 1;
|
|
cursor_col = col - 1;
|
|
}
|
|
|
|
static int posix_get_cursor_row(void) { return cursor_row; }
|
|
static int posix_get_cursor_col(void) { return cursor_col; }
|
|
|
|
static void posix_cls(void)
|
|
{
|
|
printf("\033[2J\033[H");
|
|
fflush(stdout);
|
|
cursor_row = 0;
|
|
cursor_col = 0;
|
|
}
|
|
|
|
static void posix_set_width(int cols)
|
|
{
|
|
(void)cols;
|
|
}
|
|
|
|
static void posix_enable_raw(void)
|
|
{
|
|
if (raw_mode || !isatty(STDIN_FILENO))
|
|
return;
|
|
if (!termios_saved) {
|
|
tcgetattr(STDIN_FILENO, &orig_termios);
|
|
termios_saved = 1;
|
|
}
|
|
struct termios raw = orig_termios;
|
|
cfmakeraw(&raw);
|
|
raw.c_oflag |= OPOST; /* keep output processing (newline translation) */
|
|
raw.c_cc[VMIN] = 0;
|
|
raw.c_cc[VTIME] = 0;
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
|
raw_mode = 1;
|
|
}
|
|
|
|
static void posix_disable_raw(void)
|
|
{
|
|
if (!raw_mode)
|
|
return;
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
|
|
raw_mode = 0;
|
|
}
|
|
|
|
static void posix_write_raw(const char *data, int len)
|
|
{
|
|
fflush(stdout);
|
|
write(STDOUT_FILENO, data, len);
|
|
}
|
|
|
|
static const int ansi_fg[16] = {30,34,32,36,31,35,33,37,90,94,92,96,91,95,93,97};
|
|
static const int ansi_bg[8] = {40,44,42,46,41,45,43,47};
|
|
|
|
static void posix_tui_enter(void)
|
|
{
|
|
printf("\033[?1049h\033[2J\033[H");
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void posix_tui_leave(void)
|
|
{
|
|
printf("\033[?1049l\033[0 q\033[?25h");
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void posix_render_run(int row, int col,
|
|
const uint8_t *chars, const uint8_t *attrs, int len)
|
|
{
|
|
printf("\033[%d;%dH", row + 1, col + 1);
|
|
int prev_attr = -1;
|
|
for (int i = 0; i < len; i++) {
|
|
int a = attrs[i];
|
|
if (a != prev_attr) {
|
|
printf("\033[%d;%dm", ansi_fg[a & 0x0F], ansi_bg[(a >> 4) & 0x07]);
|
|
prev_attr = a;
|
|
}
|
|
unsigned char ch = chars[i];
|
|
putchar(ch ? ch : ' ');
|
|
}
|
|
printf("\033[0m");
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void posix_set_cursor_shape(int shape)
|
|
{
|
|
switch (shape) {
|
|
case 0: printf("\033[?25l"); break;
|
|
case 1: printf("\033[?25h\033[1 q"); break;
|
|
case 2: printf("\033[?25h\033[5 q"); break;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void posix_init(void)
|
|
{
|
|
setbuf(stdout, NULL);
|
|
}
|
|
|
|
static void posix_shutdown(void)
|
|
{
|
|
if (raw_mode) {
|
|
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
|
|
raw_mode = 0;
|
|
}
|
|
}
|
|
|
|
static hal_ops_t posix_hal = {
|
|
.putch = posix_putch,
|
|
.puts = posix_puts,
|
|
.getch = posix_getch,
|
|
.kbhit = posix_kbhit,
|
|
.locate = posix_locate,
|
|
.get_cursor_row = posix_get_cursor_row,
|
|
.get_cursor_col = posix_get_cursor_col,
|
|
.cls = posix_cls,
|
|
.set_width = posix_set_width,
|
|
.enable_raw = posix_enable_raw,
|
|
.disable_raw = posix_disable_raw,
|
|
.write_raw = posix_write_raw,
|
|
.tui_enter = posix_tui_enter,
|
|
.tui_leave = posix_tui_leave,
|
|
.render_run = posix_render_run,
|
|
.set_cursor_shape = posix_set_cursor_shape,
|
|
.screen_width = 80,
|
|
.screen_height = 25,
|
|
.is_tty = false,
|
|
.init = posix_init,
|
|
.shutdown = posix_shutdown,
|
|
};
|
|
|
|
hal_ops_t *hal_posix_create(void)
|
|
{
|
|
struct winsize ws;
|
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
|
|
if (ws.ws_col > 0) posix_hal.screen_width = ws.ws_col;
|
|
if (ws.ws_row > 0) posix_hal.screen_height = ws.ws_row;
|
|
}
|
|
posix_hal.is_tty = isatty(STDIN_FILENO);
|
|
return &posix_hal;
|
|
}
|