New Makefile.dos16 builds with OpenWatcom wcc (16-bit, MEDIUM model) producing a standard MZ executable that runs on any DOS without DOS/4GW. All 24 source files compile clean; tested on FreeDOS 1.4 via QEMU. Changes for 16-bit compatibility: - hal_dos.c: INTX macro selects int86() vs int386() based on _M_I86 - sound.c: reduce stack buffer from 8192 to 512 samples on 16-bit - tui.c: gracefully disable TUI if screen buffer allocation fails (near heap exhaustion common on 16-bit), batch mode still works - .gitignore: add .obj/.exe/.err/.lib for OpenWatcom build artifacts Size comparison: - 32-bit DOS/4GW: 175KB + 265KB extender = 440KB total - 16-bit real-mode: 127KB standalone The 32-bit build (Makefile.dos) and Linux build are unaffected. 72/72 interpreter tests pass.
737 lines
20 KiB
C
737 lines
20 KiB
C
#include "tui.h"
|
|
#include "hal.h"
|
|
#include "gwbasic.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#ifndef __MSDOS__
|
|
#include <unistd.h>
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
|
|
tui_state_t tui;
|
|
|
|
/* Saved original HAL pointers for passthrough in non-TUI mode */
|
|
static void (*orig_putch)(int);
|
|
static void (*orig_puts)(const char *);
|
|
static void (*orig_cls)(void);
|
|
static void (*orig_locate)(int, int);
|
|
static int (*orig_get_cursor_row)(void);
|
|
static int (*orig_get_cursor_col)(void);
|
|
|
|
/* Default F-key definitions matching GW-BASIC */
|
|
static const char *default_fkeys[10] = {
|
|
"LIST ", /* F1 */
|
|
"RUN\r", /* F2 */
|
|
"LOAD\"", /* F3 */
|
|
"SAVE\"", /* F4 */
|
|
"CONT\r", /* F5 */
|
|
",\"LPT1:\"\r", /* F6 */
|
|
"TRON\r", /* F7 */
|
|
"TROFF\r", /* F8 */
|
|
"KEY ", /* F9 */
|
|
"SCREEN 0,0,0\r", /* F10 */
|
|
};
|
|
|
|
static void scroll_up(void)
|
|
{
|
|
int bottom = tui.view_bottom;
|
|
memmove(&TUI_CELL(0, 0), &TUI_CELL(1, 0),
|
|
bottom * tui.cols * sizeof(tui_cell_t));
|
|
for (int c = 0; c < tui.cols; c++) {
|
|
TUI_CELL(bottom, c).ch = ' ';
|
|
TUI_CELL(bottom, c).attr = tui.current_attr;
|
|
}
|
|
}
|
|
|
|
static void advance_cursor(void)
|
|
{
|
|
tui.cursor_col++;
|
|
if (tui.cursor_col >= tui.cols) {
|
|
tui.cursor_col = 0;
|
|
tui.cursor_row++;
|
|
if (tui.cursor_row > tui.view_bottom) {
|
|
scroll_up();
|
|
tui.cursor_row = tui.view_bottom;
|
|
}
|
|
}
|
|
}
|
|
|
|
void tui_putch(int ch)
|
|
{
|
|
if (ch == '\n') {
|
|
tui.cursor_col = 0;
|
|
tui.cursor_row++;
|
|
if (tui.cursor_row > tui.view_bottom) {
|
|
scroll_up();
|
|
tui.cursor_row = tui.view_bottom;
|
|
tui_refresh();
|
|
}
|
|
return;
|
|
}
|
|
if (ch == '\r') {
|
|
tui.cursor_col = 0;
|
|
return;
|
|
}
|
|
if (ch == '\b') {
|
|
if (tui.cursor_col > 0) {
|
|
tui.cursor_col--;
|
|
TUI_CELL(tui.cursor_row, tui.cursor_col).ch = ' ';
|
|
TUI_CELL(tui.cursor_row, tui.cursor_col).attr = tui.current_attr;
|
|
}
|
|
return;
|
|
}
|
|
if (ch == '\a') return;
|
|
if (ch == '\t') {
|
|
int target = (tui.cursor_col + 8) & ~7;
|
|
while (tui.cursor_col < target && tui.cursor_col < tui.cols)
|
|
tui_putch(' ');
|
|
return;
|
|
}
|
|
|
|
TUI_CELL(tui.cursor_row, tui.cursor_col).ch = (uint8_t)ch;
|
|
TUI_CELL(tui.cursor_row, tui.cursor_col).attr = tui.current_attr;
|
|
advance_cursor();
|
|
}
|
|
|
|
void tui_puts(const char *s)
|
|
{
|
|
while (*s)
|
|
tui_putch((unsigned char)*s++);
|
|
tui_refresh();
|
|
tui_update_cursor();
|
|
}
|
|
|
|
void tui_cls(void)
|
|
{
|
|
for (int r = 0; r < tui.rows; r++)
|
|
for (int c = 0; c < tui.cols; c++) {
|
|
TUI_CELL(r, c).ch = ' ';
|
|
TUI_CELL(r, c).attr = tui.current_attr;
|
|
}
|
|
tui.cursor_row = 0;
|
|
tui.cursor_col = 0;
|
|
tui_refresh();
|
|
tui_update_cursor();
|
|
}
|
|
|
|
void tui_locate(int row, int col)
|
|
{
|
|
tui.cursor_row = row - 1;
|
|
tui.cursor_col = col - 1;
|
|
if (tui.cursor_row < 0) tui.cursor_row = 0;
|
|
if (tui.cursor_col < 0) tui.cursor_col = 0;
|
|
if (tui.cursor_row >= tui.rows) tui.cursor_row = tui.rows - 1;
|
|
if (tui.cursor_col >= tui.cols) tui.cursor_col = tui.cols - 1;
|
|
tui_update_cursor();
|
|
}
|
|
|
|
int tui_get_cursor_row(void) { return tui.cursor_row; }
|
|
int tui_get_cursor_col(void) { return tui.cursor_col; }
|
|
|
|
/* CGA-to-ANSI color mapping */
|
|
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 emit_attr(uint8_t attr)
|
|
{
|
|
int fg = attr & 0x0F;
|
|
int bg = (attr >> 4) & 0x07;
|
|
printf("\033[%d;%dm", ansi_fg[fg], ansi_bg[bg]);
|
|
}
|
|
|
|
void tui_refresh(void)
|
|
{
|
|
printf("\033[H");
|
|
|
|
int bottom = tui.key_bar_visible ? tui.rows : tui.view_bottom + 1;
|
|
uint8_t prev_attr = 0xFF;
|
|
|
|
for (int r = 0; r < bottom; r++) {
|
|
printf("\033[%d;1H", r + 1);
|
|
for (int c = 0; c < tui.cols; c++) {
|
|
uint8_t attr = TUI_CELL(r, c).attr;
|
|
if (attr != prev_attr) {
|
|
emit_attr(attr);
|
|
prev_attr = attr;
|
|
}
|
|
uint8_t ch = TUI_CELL(r, c).ch;
|
|
putchar(ch ? ch : ' ');
|
|
}
|
|
}
|
|
|
|
printf("\033[0m");
|
|
|
|
if (tui.key_bar_visible)
|
|
tui_refresh_row(tui.rows - 1);
|
|
|
|
fflush(stdout);
|
|
}
|
|
|
|
void tui_refresh_row(int row)
|
|
{
|
|
if (row < 0 || row >= tui.rows) return;
|
|
printf("\033[%d;1H", row + 1);
|
|
uint8_t prev_attr = 0xFF;
|
|
for (int c = 0; c < tui.cols; c++) {
|
|
uint8_t attr = TUI_CELL(row, c).attr;
|
|
if (attr != prev_attr) {
|
|
emit_attr(attr);
|
|
prev_attr = attr;
|
|
}
|
|
uint8_t ch = TUI_CELL(row, c).ch;
|
|
putchar(ch ? ch : ' ');
|
|
}
|
|
printf("\033[0m");
|
|
fflush(stdout);
|
|
}
|
|
|
|
void tui_update_cursor(void)
|
|
{
|
|
printf("\033[%d;%dH", tui.cursor_row + 1, tui.cursor_col + 1);
|
|
fflush(stdout);
|
|
}
|
|
|
|
int tui_read_key(void)
|
|
{
|
|
/* Drain key buffer first (keys pushed back by event trapping) */
|
|
int buffered = tui_pop_key();
|
|
if (buffered >= 0)
|
|
return buffered;
|
|
|
|
gw_hal->enable_raw();
|
|
|
|
int ch = gw_hal->getch();
|
|
if (ch < 0) return -1;
|
|
|
|
if (ch == 3) return TK_CTRL_C;
|
|
|
|
if (ch != 27) return ch;
|
|
|
|
if (!gw_hal->kbhit())
|
|
return TK_ESCAPE;
|
|
|
|
int seq1 = gw_hal->getch();
|
|
if (seq1 < 0) return TK_ESCAPE;
|
|
|
|
if (seq1 == '[') {
|
|
int seq2 = gw_hal->getch();
|
|
if (seq2 < 0) return TK_ESCAPE;
|
|
|
|
switch (seq2) {
|
|
case 'A': return TK_UP;
|
|
case 'B': return TK_DOWN;
|
|
case 'C': return TK_RIGHT;
|
|
case 'D': return TK_LEFT;
|
|
case 'H': return TK_HOME;
|
|
case 'F': return TK_END;
|
|
default:
|
|
if (seq2 >= '0' && seq2 <= '9') {
|
|
int seq3 = gw_hal->getch();
|
|
if (seq3 == '~') {
|
|
switch (seq2) {
|
|
case '2': return TK_INSERT;
|
|
case '3': return TK_DELETE;
|
|
case '5': return TK_PGUP;
|
|
case '6': return TK_PGDN;
|
|
}
|
|
} else if (seq2 == '1' && seq3 >= '0' && seq3 <= '9') {
|
|
int seq4 = gw_hal->getch();
|
|
if (seq4 == '~') {
|
|
int code = (seq2 - '0') * 10 + (seq3 - '0');
|
|
switch (code) {
|
|
case 15: return TK_F5;
|
|
case 17: return TK_F6;
|
|
case 18: return TK_F7;
|
|
case 19: return TK_F8;
|
|
case 20: return TK_F9;
|
|
case 21: return TK_F10;
|
|
}
|
|
}
|
|
} else if (seq2 == '2' && seq3 >= '0' && seq3 <= '9') {
|
|
int seq4 = gw_hal->getch();
|
|
(void)seq4;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
} else if (seq1 == 'O') {
|
|
int seq2 = gw_hal->getch();
|
|
switch (seq2) {
|
|
case 'P': return TK_F1;
|
|
case 'Q': return TK_F2;
|
|
case 'R': return TK_F3;
|
|
case 'S': return TK_F4;
|
|
case 'H': return TK_HOME;
|
|
case 'F': return TK_END;
|
|
}
|
|
}
|
|
|
|
return TK_ESCAPE;
|
|
}
|
|
|
|
static int extract_screen_line(int row, char *buf, int bufsz)
|
|
{
|
|
int len = 0;
|
|
for (int c = 0; c < tui.cols && len < bufsz - 1; c++) {
|
|
buf[len++] = TUI_CELL(row, c).ch ? TUI_CELL(row, c).ch : ' ';
|
|
}
|
|
while (len > 0 && buf[len - 1] == ' ')
|
|
len--;
|
|
buf[len] = '\0';
|
|
return len;
|
|
}
|
|
|
|
char *tui_read_line(void)
|
|
{
|
|
static char line_buf[TUI_MAX_LINE + 1];
|
|
int enter_row = tui.cursor_row;
|
|
|
|
tui_update_cursor();
|
|
|
|
for (;;) {
|
|
int key = tui_read_key();
|
|
if (key < 0) return NULL;
|
|
|
|
if (key == TK_CTRL_C || tui.break_flag) {
|
|
tui.break_flag = false;
|
|
line_buf[0] = '\0';
|
|
return NULL;
|
|
}
|
|
|
|
if (key >= TK_F1 && key <= TK_F10) {
|
|
int fk = key - TK_F1;
|
|
const char *def = tui.fkey_defs[fk];
|
|
for (const char *p = def; *p; p++) {
|
|
if (*p == '\r') {
|
|
tui_putch('\n');
|
|
tui_refresh();
|
|
extract_screen_line(enter_row, line_buf, sizeof(line_buf));
|
|
tui_update_cursor();
|
|
return line_buf;
|
|
}
|
|
TUI_CELL(tui.cursor_row, tui.cursor_col).ch = (uint8_t)*p;
|
|
TUI_CELL(tui.cursor_row, tui.cursor_col).attr = tui.current_attr;
|
|
advance_cursor();
|
|
}
|
|
tui_refresh_row(tui.cursor_row);
|
|
tui_update_cursor();
|
|
continue;
|
|
}
|
|
|
|
switch (key) {
|
|
case TK_ENTER:
|
|
extract_screen_line(enter_row, line_buf, sizeof(line_buf));
|
|
tui.cursor_col = 0;
|
|
tui.cursor_row++;
|
|
if (tui.cursor_row > tui.view_bottom) {
|
|
scroll_up();
|
|
tui.cursor_row = tui.view_bottom;
|
|
}
|
|
tui_refresh();
|
|
tui_update_cursor();
|
|
return line_buf;
|
|
|
|
case TK_BACKSPACE:
|
|
case 127:
|
|
if (tui.cursor_col > 0) {
|
|
tui.cursor_col--;
|
|
for (int c = tui.cursor_col; c < tui.cols - 1; c++)
|
|
TUI_CELL(tui.cursor_row, c) = TUI_CELL(tui.cursor_row, c + 1);
|
|
TUI_CELL(tui.cursor_row, tui.cols - 1).ch = ' ';
|
|
TUI_CELL(tui.cursor_row, tui.cols - 1).attr = tui.current_attr;
|
|
tui_refresh_row(tui.cursor_row);
|
|
}
|
|
tui_update_cursor();
|
|
break;
|
|
|
|
case TK_DELETE:
|
|
for (int c = tui.cursor_col; c < tui.cols - 1; c++)
|
|
TUI_CELL(tui.cursor_row, c) = TUI_CELL(tui.cursor_row, c + 1);
|
|
TUI_CELL(tui.cursor_row, tui.cols - 1).ch = ' ';
|
|
TUI_CELL(tui.cursor_row, tui.cols - 1).attr = tui.current_attr;
|
|
tui_refresh_row(tui.cursor_row);
|
|
tui_update_cursor();
|
|
break;
|
|
|
|
case TK_LEFT:
|
|
if (tui.cursor_col > 0)
|
|
tui.cursor_col--;
|
|
else if (tui.cursor_row > 0) {
|
|
tui.cursor_row--;
|
|
tui.cursor_col = tui.cols - 1;
|
|
}
|
|
tui_update_cursor();
|
|
break;
|
|
|
|
case TK_RIGHT:
|
|
if (tui.cursor_col < tui.cols - 1)
|
|
tui.cursor_col++;
|
|
else if (tui.cursor_row < tui.view_bottom) {
|
|
tui.cursor_row++;
|
|
tui.cursor_col = 0;
|
|
}
|
|
tui_update_cursor();
|
|
break;
|
|
|
|
case TK_UP:
|
|
if (tui.cursor_row > 0)
|
|
tui.cursor_row--;
|
|
tui_update_cursor();
|
|
break;
|
|
|
|
case TK_DOWN:
|
|
if (tui.cursor_row < tui.view_bottom)
|
|
tui.cursor_row++;
|
|
tui_update_cursor();
|
|
break;
|
|
|
|
case TK_HOME:
|
|
tui.cursor_col = 0;
|
|
tui_update_cursor();
|
|
break;
|
|
|
|
case TK_END: {
|
|
int c = tui.cols - 1;
|
|
while (c > 0 && TUI_CELL(tui.cursor_row, c).ch == ' ')
|
|
c--;
|
|
if (TUI_CELL(tui.cursor_row, c).ch != ' ')
|
|
c++;
|
|
if (c >= tui.cols) c = tui.cols - 1;
|
|
tui.cursor_col = c;
|
|
tui_update_cursor();
|
|
break;
|
|
}
|
|
|
|
case TK_CTRL_HOME:
|
|
tui_cls();
|
|
enter_row = 0;
|
|
break;
|
|
|
|
case TK_INSERT:
|
|
tui.insert_mode = !tui.insert_mode;
|
|
if (tui.insert_mode)
|
|
tui_set_cursor_block();
|
|
else
|
|
tui_set_cursor_line();
|
|
break;
|
|
|
|
case TK_ESCAPE:
|
|
for (int c = tui.cursor_col; c < tui.cols; c++) {
|
|
TUI_CELL(tui.cursor_row, c).ch = ' ';
|
|
TUI_CELL(tui.cursor_row, c).attr = tui.current_attr;
|
|
}
|
|
tui_refresh_row(tui.cursor_row);
|
|
tui_update_cursor();
|
|
break;
|
|
|
|
default:
|
|
if (key >= 32 && key < 127) {
|
|
if (tui.insert_mode && tui.cursor_col < tui.cols - 1) {
|
|
for (int c = tui.cols - 1; c > tui.cursor_col; c--)
|
|
TUI_CELL(tui.cursor_row, c) = TUI_CELL(tui.cursor_row, c - 1);
|
|
}
|
|
TUI_CELL(tui.cursor_row, tui.cursor_col).ch = (uint8_t)key;
|
|
TUI_CELL(tui.cursor_row, tui.cursor_col).attr = tui.current_attr;
|
|
if (tui.cursor_row != enter_row && tui.cursor_col == 0)
|
|
enter_row = tui.cursor_row;
|
|
advance_cursor();
|
|
tui_refresh_row(tui.cursor_row);
|
|
tui_update_cursor();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void render_key_bar(void)
|
|
{
|
|
int row = tui.rows - 1;
|
|
for (int c = 0; c < tui.cols; c++) {
|
|
TUI_CELL(row, c).ch = ' ';
|
|
TUI_CELL(row, c).attr = 0x70;
|
|
}
|
|
|
|
int col = 0;
|
|
for (int i = 0; i < 10 && col < tui.cols; i++) {
|
|
char label[4];
|
|
snprintf(label, sizeof(label), "%d", i + 1);
|
|
for (const char *p = label; *p && col < tui.cols; p++) {
|
|
TUI_CELL(row, col).ch = *p;
|
|
TUI_CELL(row, col).attr = 0x07;
|
|
col++;
|
|
}
|
|
const char *def = tui.fkey_defs[i];
|
|
int maxw = 6;
|
|
for (int j = 0; j < maxw && def[j] && def[j] != '\r' && col < tui.cols; j++) {
|
|
TUI_CELL(row, col).ch = (uint8_t)def[j];
|
|
TUI_CELL(row, col).attr = 0x70;
|
|
col++;
|
|
}
|
|
if (col < tui.cols) {
|
|
TUI_CELL(row, col).ch = ' ';
|
|
TUI_CELL(row, col).attr = 0x07;
|
|
col++;
|
|
}
|
|
}
|
|
|
|
tui_refresh_row(row);
|
|
}
|
|
|
|
void tui_key_on(void)
|
|
{
|
|
tui.key_bar_visible = true;
|
|
tui.view_bottom = tui.rows - 2;
|
|
render_key_bar();
|
|
tui_update_cursor();
|
|
}
|
|
|
|
void tui_key_off(void)
|
|
{
|
|
tui.key_bar_visible = false;
|
|
tui.view_bottom = tui.rows - 1;
|
|
for (int c = 0; c < tui.cols; c++) {
|
|
TUI_CELL(tui.rows - 1, c).ch = ' ';
|
|
TUI_CELL(tui.rows - 1, c).attr = tui.current_attr;
|
|
}
|
|
tui_refresh_row(tui.rows - 1);
|
|
tui_update_cursor();
|
|
}
|
|
|
|
void tui_key_list(void)
|
|
{
|
|
for (int i = 0; i < 10; i++) {
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "F%d ", i + 1);
|
|
tui_puts(buf);
|
|
for (const char *p = tui.fkey_defs[i]; *p; p++) {
|
|
if (*p == '\r')
|
|
tui_putch(0x0D);
|
|
else
|
|
tui_putch(*p);
|
|
}
|
|
tui_putch('\n');
|
|
}
|
|
tui_refresh();
|
|
tui_update_cursor();
|
|
}
|
|
|
|
/* Key buffer ring buffer operations */
|
|
void tui_push_key(int key)
|
|
{
|
|
int next = (tui.keybuf_head + 1) % TUI_KEYBUF_SIZE;
|
|
if (next == tui.keybuf_tail)
|
|
return; /* buffer full, drop key */
|
|
tui.keybuf[tui.keybuf_head] = key;
|
|
tui.keybuf_head = next;
|
|
}
|
|
|
|
int tui_pop_key(void)
|
|
{
|
|
if (tui.keybuf_head == tui.keybuf_tail)
|
|
return -1;
|
|
int key = tui.keybuf[tui.keybuf_tail];
|
|
tui.keybuf_tail = (tui.keybuf_tail + 1) % TUI_KEYBUF_SIZE;
|
|
return key;
|
|
}
|
|
|
|
bool tui_keybuf_empty(void)
|
|
{
|
|
return tui.keybuf_head == tui.keybuf_tail;
|
|
}
|
|
|
|
int tui_key_to_scancode(int key)
|
|
{
|
|
/* Map internal TK_* key codes to IBM PC scan codes for INKEY$.
|
|
Returns -1 for regular ASCII keys (no scan code). */
|
|
switch (key) {
|
|
case TK_F1: return 59;
|
|
case TK_F2: return 60;
|
|
case TK_F3: return 61;
|
|
case TK_F4: return 62;
|
|
case TK_F5: return 63;
|
|
case TK_F6: return 64;
|
|
case TK_F7: return 65;
|
|
case TK_F8: return 66;
|
|
case TK_F9: return 67;
|
|
case TK_F10: return 68;
|
|
case TK_HOME: return 71;
|
|
case TK_UP: return 72;
|
|
case TK_PGUP: return 73;
|
|
case TK_LEFT: return 75;
|
|
case TK_RIGHT: return 77;
|
|
case TK_END: return 79;
|
|
case TK_DOWN: return 80;
|
|
case TK_PGDN: return 81;
|
|
case TK_INSERT: return 82;
|
|
case TK_DELETE: return 83;
|
|
default: return -1;
|
|
}
|
|
}
|
|
|
|
void tui_edit_line(const char *prefill)
|
|
{
|
|
/* Advance to new line */
|
|
tui.cursor_col = 0;
|
|
tui.cursor_row++;
|
|
if (tui.cursor_row > tui.view_bottom) {
|
|
scroll_up();
|
|
tui.cursor_row = tui.view_bottom;
|
|
}
|
|
|
|
/* Write prefill text into screen buffer */
|
|
for (int i = 0; prefill[i] && i < tui.cols; i++) {
|
|
TUI_CELL(tui.cursor_row, i).ch = (uint8_t)prefill[i];
|
|
TUI_CELL(tui.cursor_row, i).attr = tui.current_attr;
|
|
}
|
|
|
|
tui_refresh_row(tui.cursor_row);
|
|
tui_update_cursor();
|
|
|
|
char *result = tui_read_line();
|
|
if (result && result[0])
|
|
gw_exec_direct(result);
|
|
}
|
|
|
|
void tui_set_cursor_block(void)
|
|
{
|
|
printf("\033[1 q");
|
|
fflush(stdout);
|
|
}
|
|
|
|
void tui_set_cursor_line(void)
|
|
{
|
|
printf("\033[5 q");
|
|
fflush(stdout);
|
|
}
|
|
|
|
static void sigint_handler(int sig)
|
|
{
|
|
(void)sig;
|
|
tui.break_flag = true;
|
|
}
|
|
|
|
void tui_install_break_handler(void)
|
|
{
|
|
#ifdef __MSDOS__
|
|
signal(SIGINT, sigint_handler);
|
|
#else
|
|
struct sigaction sa;
|
|
sa.sa_handler = sigint_handler;
|
|
sa.sa_flags = 0;
|
|
sigemptyset(&sa.sa_mask);
|
|
sigaction(SIGINT, &sa, NULL);
|
|
#endif
|
|
}
|
|
|
|
void tui_check_break(void)
|
|
{
|
|
if (tui.break_flag) {
|
|
tui.break_flag = false;
|
|
tui_puts("\nBreak\n");
|
|
gw.running = false;
|
|
longjmp(gw_error_jmp, -1);
|
|
}
|
|
}
|
|
|
|
void tui_init(bool fullscreen)
|
|
{
|
|
memset(&tui, 0, sizeof(tui));
|
|
tui.current_attr = 0x07;
|
|
tui.insert_mode = false;
|
|
tui.active = true;
|
|
|
|
/* Determine screen dimensions */
|
|
tui.rows = TUI_DEFAULT_ROWS;
|
|
tui.cols = TUI_DEFAULT_COLS;
|
|
if (fullscreen) {
|
|
#ifndef __MSDOS__
|
|
struct winsize ws;
|
|
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_row > 0 && ws.ws_col > 0) {
|
|
tui.rows = ws.ws_row;
|
|
tui.cols = ws.ws_col;
|
|
if (tui.rows > TUI_MAX_ROWS) tui.rows = TUI_MAX_ROWS;
|
|
if (tui.cols > TUI_MAX_COLS) tui.cols = TUI_MAX_COLS;
|
|
}
|
|
#else
|
|
tui.rows = gw_hal ? gw_hal->screen_height : TUI_DEFAULT_ROWS;
|
|
tui.cols = gw_hal ? gw_hal->screen_width : TUI_DEFAULT_COLS;
|
|
#endif
|
|
}
|
|
|
|
tui.view_bottom = tui.rows - 1;
|
|
|
|
/* Allocate screen buffer */
|
|
tui.screen = calloc(tui.rows * tui.cols, sizeof(tui_cell_t));
|
|
if (!tui.screen) {
|
|
/* Not enough near heap (common on 16-bit DOS).
|
|
* Disable TUI — batch mode still works via HAL. */
|
|
tui.active = false;
|
|
return;
|
|
}
|
|
|
|
/* Set default F-key definitions */
|
|
for (int i = 0; i < 10; i++)
|
|
strncpy(tui.fkey_defs[i], default_fkeys[i], sizeof(tui.fkey_defs[i]) - 1);
|
|
|
|
/* Clear screen buffer */
|
|
for (int r = 0; r < tui.rows; r++)
|
|
for (int c = 0; c < tui.cols; c++) {
|
|
TUI_CELL(r, c).ch = ' ';
|
|
TUI_CELL(r, c).attr = tui.current_attr;
|
|
}
|
|
|
|
/* Save original HAL pointers */
|
|
orig_putch = gw_hal->putch;
|
|
orig_puts = gw_hal->puts;
|
|
orig_cls = gw_hal->cls;
|
|
orig_locate = gw_hal->locate;
|
|
orig_get_cursor_row = gw_hal->get_cursor_row;
|
|
orig_get_cursor_col = gw_hal->get_cursor_col;
|
|
|
|
/* Swap in TUI handlers */
|
|
gw_hal->putch = tui_putch;
|
|
gw_hal->puts = tui_puts;
|
|
gw_hal->cls = tui_cls;
|
|
gw_hal->locate = tui_locate;
|
|
gw_hal->get_cursor_row = tui_get_cursor_row;
|
|
gw_hal->get_cursor_col = tui_get_cursor_col;
|
|
|
|
/* Install break handler */
|
|
tui_install_break_handler();
|
|
|
|
/* Enter alternate screen buffer, clear */
|
|
printf("\033[?1049h");
|
|
printf("\033[2J\033[H");
|
|
tui_set_cursor_line();
|
|
fflush(stdout);
|
|
|
|
/* Show KEY bar by default (matches real GW-BASIC) */
|
|
tui_key_on();
|
|
}
|
|
|
|
void tui_shutdown(void)
|
|
{
|
|
if (!tui.active) return;
|
|
tui.active = false;
|
|
|
|
/* Restore original HAL pointers */
|
|
gw_hal->putch = orig_putch;
|
|
gw_hal->puts = orig_puts;
|
|
gw_hal->cls = orig_cls;
|
|
gw_hal->locate = orig_locate;
|
|
gw_hal->get_cursor_row = orig_get_cursor_row;
|
|
gw_hal->get_cursor_col = orig_get_cursor_col;
|
|
|
|
/* Leave alternate screen buffer, restore cursor */
|
|
printf("\033[?1049l");
|
|
printf("\033[0 q");
|
|
fflush(stdout);
|
|
|
|
free(tui.screen);
|
|
tui.screen = NULL;
|
|
|
|
signal(SIGINT, SIG_DFL);
|
|
}
|