Added additional ANSIBBS sequences and made dvxUpate handle terminal widget polling.
This commit is contained in:
parent
eeb6541af3
commit
324382d758
5 changed files with 314 additions and 109 deletions
38
dvx/dvxApp.c
38
dvx/dvxApp.c
|
|
@ -24,6 +24,8 @@ static void drawCursorAt(AppContextT *ctx, int32_t x, int32_t y);
|
|||
static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t buttons);
|
||||
static void initColorScheme(AppContextT *ctx);
|
||||
static void initMouse(AppContextT *ctx);
|
||||
static void pollAnsiTermWidgets(AppContextT *ctx);
|
||||
static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win);
|
||||
static void pollKeyboard(AppContextT *ctx);
|
||||
static void pollMouse(AppContextT *ctx);
|
||||
static void refreshMinimizedIcons(AppContextT *ctx);
|
||||
|
|
@ -536,6 +538,7 @@ bool dvxUpdate(AppContextT *ctx) {
|
|||
pollMouse(ctx);
|
||||
pollKeyboard(ctx);
|
||||
dispatchEvents(ctx);
|
||||
pollAnsiTermWidgets(ctx);
|
||||
|
||||
// Periodically refresh one minimized window thumbnail (staggered)
|
||||
ctx->frameCount++;
|
||||
|
|
@ -819,6 +822,41 @@ static void initMouse(AppContextT *ctx) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// pollAnsiTermWidgets — poll and repaint all ANSI term widgets
|
||||
// ============================================================
|
||||
|
||||
static void pollAnsiTermWidgets(AppContextT *ctx) {
|
||||
for (int32_t i = 0; i < ctx->stack.count; i++) {
|
||||
WindowT *win = ctx->stack.windows[i];
|
||||
|
||||
if (win->widgetRoot) {
|
||||
pollAnsiTermWidgetsWalk(ctx, win->widgetRoot, win);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// pollAnsiTermWidgetsWalk — recursive helper
|
||||
// ============================================================
|
||||
|
||||
static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win) {
|
||||
if (w->type == WidgetAnsiTermE) {
|
||||
wgtAnsiTermPoll(w);
|
||||
|
||||
if (wgtAnsiTermRepaint(w) > 0) {
|
||||
win->contentDirty = true;
|
||||
dvxInvalidateWindow(ctx, win);
|
||||
}
|
||||
}
|
||||
|
||||
for (WidgetT *child = w->firstChild; child; child = child->nextSibling) {
|
||||
pollAnsiTermWidgetsWalk(ctx, child, win);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// pollKeyboard
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -296,6 +296,7 @@ typedef struct WidgetT {
|
|||
bool cursorVisible;
|
||||
bool wrapMode; // auto-wrap at right margin
|
||||
bool bold; // SGR bold flag (brightens foreground)
|
||||
bool originMode; // cursor positioning relative to scroll region
|
||||
bool csiPrivate; // '?' prefix in CSI sequence
|
||||
uint8_t curAttr; // current text attribute (fg | bg<<4)
|
||||
uint8_t parseState; // 0=normal, 1=ESC, 2=CSI
|
||||
|
|
@ -303,6 +304,9 @@ typedef struct WidgetT {
|
|||
int32_t paramCount; // number of CSI params collected
|
||||
int32_t savedRow; // saved cursor position (SCP)
|
||||
int32_t savedCol;
|
||||
// Scrolling region (0-based, inclusive)
|
||||
int32_t scrollTop; // top row of scroll region
|
||||
int32_t scrollBot; // bottom row of scroll region
|
||||
// Scrollback
|
||||
uint8_t *scrollback; // circular buffer of scrollback lines
|
||||
int32_t scrollbackMax; // max lines in scrollback buffer
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ static const int32_t sAnsiToCga[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
|
|||
|
||||
static void ansiTermAddToScrollback(WidgetT *w, int32_t screenRow);
|
||||
static void ansiTermDeleteLines(WidgetT *w, int32_t count);
|
||||
static void ansiTermDirtyAll(WidgetT *w);
|
||||
static void ansiTermDirtyRange(WidgetT *w, int32_t startCell, int32_t count);
|
||||
static void ansiTermDirtyRow(WidgetT *w, int32_t row);
|
||||
static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd);
|
||||
|
|
@ -105,15 +104,6 @@ static void ansiTermAddToScrollback(WidgetT *w, int32_t screenRow) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// ansiTermDirtyAll
|
||||
// ============================================================
|
||||
|
||||
static void ansiTermDirtyAll(WidgetT *w) {
|
||||
w->as.ansiTerm.dirtyRows = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// ansiTermDirtyRange
|
||||
// ============================================================
|
||||
|
|
@ -148,11 +138,11 @@ static void ansiTermDirtyRow(WidgetT *w, int32_t row) {
|
|||
|
||||
static void ansiTermDeleteLines(WidgetT *w, int32_t count) {
|
||||
int32_t cols = w->as.ansiTerm.cols;
|
||||
int32_t rows = w->as.ansiTerm.rows;
|
||||
int32_t bot = w->as.ansiTerm.scrollBot;
|
||||
int32_t row = w->as.ansiTerm.cursorRow;
|
||||
|
||||
if (count > rows - row) {
|
||||
count = rows - row;
|
||||
if (count > bot - row + 1) {
|
||||
count = bot - row + 1;
|
||||
}
|
||||
|
||||
if (count <= 0) {
|
||||
|
|
@ -160,20 +150,22 @@ static void ansiTermDeleteLines(WidgetT *w, int32_t count) {
|
|||
}
|
||||
|
||||
uint8_t *cells = w->as.ansiTerm.cells;
|
||||
|
||||
// Shift lines up
|
||||
int32_t bytesPerRow = cols * 2;
|
||||
|
||||
// Shift lines up within region
|
||||
if (row + count <= bot) {
|
||||
memmove(cells + row * bytesPerRow,
|
||||
cells + (row + count) * bytesPerRow,
|
||||
(rows - row - count) * bytesPerRow);
|
||||
(bot - row - count + 1) * bytesPerRow);
|
||||
}
|
||||
|
||||
// Clear the bottom lines
|
||||
for (int32_t r = rows - count; r < rows; r++) {
|
||||
// Clear the bottom lines of the region
|
||||
for (int32_t r = bot - count + 1; r <= bot; r++) {
|
||||
ansiTermFillCells(w, r * cols, cols);
|
||||
}
|
||||
|
||||
// All rows from cursorRow down are affected
|
||||
for (int32_t r = row; r < rows; r++) {
|
||||
// Dirty affected rows
|
||||
for (int32_t r = row; r <= bot; r++) {
|
||||
ansiTermDirtyRow(w, r);
|
||||
}
|
||||
}
|
||||
|
|
@ -192,6 +184,12 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
|||
int32_t mode = (n >= 1) ? p[0] : 0;
|
||||
|
||||
if (cmd == 'h') {
|
||||
if (mode == 6) {
|
||||
w->as.ansiTerm.originMode = true;
|
||||
w->as.ansiTerm.cursorRow = w->as.ansiTerm.scrollTop;
|
||||
w->as.ansiTerm.cursorCol = 0;
|
||||
}
|
||||
|
||||
if (mode == 7) {
|
||||
w->as.ansiTerm.wrapMode = true;
|
||||
}
|
||||
|
|
@ -200,6 +198,12 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
|||
w->as.ansiTerm.cursorVisible = true;
|
||||
}
|
||||
} else if (cmd == 'l') {
|
||||
if (mode == 6) {
|
||||
w->as.ansiTerm.originMode = false;
|
||||
w->as.ansiTerm.cursorRow = 0;
|
||||
w->as.ansiTerm.cursorCol = 0;
|
||||
}
|
||||
|
||||
if (mode == 7) {
|
||||
w->as.ansiTerm.wrapMode = false;
|
||||
}
|
||||
|
|
@ -213,13 +217,40 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
|||
}
|
||||
|
||||
switch (cmd) {
|
||||
case '@': // ICH - insert character
|
||||
{
|
||||
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
||||
int32_t cols = w->as.ansiTerm.cols;
|
||||
int32_t row = w->as.ansiTerm.cursorRow;
|
||||
int32_t col = w->as.ansiTerm.cursorCol;
|
||||
|
||||
if (count > cols - col) {
|
||||
count = cols - col;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
uint8_t *base = w->as.ansiTerm.cells + row * cols * 2;
|
||||
memmove(base + (col + count) * 2, base + col * 2, (cols - col - count) * 2);
|
||||
|
||||
for (int32_t i = col; i < col + count; i++) {
|
||||
base[i * 2] = ' ';
|
||||
base[i * 2 + 1] = w->as.ansiTerm.curAttr;
|
||||
}
|
||||
|
||||
ansiTermDirtyRow(w, row);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'A': // CUU - cursor up
|
||||
{
|
||||
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
||||
int32_t minRow = w->as.ansiTerm.originMode ? w->as.ansiTerm.scrollTop : 0;
|
||||
w->as.ansiTerm.cursorRow -= count;
|
||||
|
||||
if (w->as.ansiTerm.cursorRow < 0) {
|
||||
w->as.ansiTerm.cursorRow = 0;
|
||||
if (w->as.ansiTerm.cursorRow < minRow) {
|
||||
w->as.ansiTerm.cursorRow = minRow;
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -228,10 +259,11 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
|||
case 'B': // CUD - cursor down
|
||||
{
|
||||
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
||||
int32_t maxRow = w->as.ansiTerm.originMode ? w->as.ansiTerm.scrollBot : w->as.ansiTerm.rows - 1;
|
||||
w->as.ansiTerm.cursorRow += count;
|
||||
|
||||
if (w->as.ansiTerm.cursorRow >= w->as.ansiTerm.rows) {
|
||||
w->as.ansiTerm.cursorRow = w->as.ansiTerm.rows - 1;
|
||||
if (w->as.ansiTerm.cursorRow > maxRow) {
|
||||
w->as.ansiTerm.cursorRow = maxRow;
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -261,12 +293,44 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
|||
break;
|
||||
}
|
||||
|
||||
case 'c': // DA - device attributes
|
||||
{
|
||||
if (w->as.ansiTerm.commWrite) {
|
||||
// Respond as VT100 with advanced video option
|
||||
const uint8_t reply[] = "\033[?1;2c";
|
||||
w->as.ansiTerm.commWrite(w->as.ansiTerm.commCtx, reply, 7);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'E': // CNL - cursor next line
|
||||
{
|
||||
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
ansiTermNewline(w);
|
||||
}
|
||||
|
||||
w->as.ansiTerm.cursorCol = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'H': // CUP - cursor position
|
||||
case 'f': // HVP - same
|
||||
{
|
||||
int32_t row = (n >= 1 && p[0]) ? p[0] - 1 : 0;
|
||||
int32_t col = (n >= 2 && p[1]) ? p[1] - 1 : 0;
|
||||
|
||||
// Origin mode: row is relative to scroll region
|
||||
if (w->as.ansiTerm.originMode) {
|
||||
row += w->as.ansiTerm.scrollTop;
|
||||
|
||||
if (row > w->as.ansiTerm.scrollBot) {
|
||||
row = w->as.ansiTerm.scrollBot;
|
||||
}
|
||||
}
|
||||
|
||||
if (row < 0) {
|
||||
row = 0;
|
||||
}
|
||||
|
|
@ -316,6 +380,32 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
|||
break;
|
||||
}
|
||||
|
||||
case 'P': // DCH - delete character
|
||||
{
|
||||
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
||||
int32_t cols = w->as.ansiTerm.cols;
|
||||
int32_t row = w->as.ansiTerm.cursorRow;
|
||||
int32_t col = w->as.ansiTerm.cursorCol;
|
||||
|
||||
if (count > cols - col) {
|
||||
count = cols - col;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
uint8_t *base = w->as.ansiTerm.cells + row * cols * 2;
|
||||
memmove(base + col * 2, base + (col + count) * 2, (cols - col - count) * 2);
|
||||
|
||||
for (int32_t i = cols - count; i < cols; i++) {
|
||||
base[i * 2] = ' ';
|
||||
base[i * 2 + 1] = w->as.ansiTerm.curAttr;
|
||||
}
|
||||
|
||||
ansiTermDirtyRow(w, row);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'S': // SU - scroll up
|
||||
{
|
||||
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
||||
|
|
@ -338,10 +428,72 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
|||
break;
|
||||
}
|
||||
|
||||
case 'Z': // CBT - back tab
|
||||
{
|
||||
int32_t col = w->as.ansiTerm.cursorCol;
|
||||
|
||||
if (col > 0) {
|
||||
col = ((col - 1) / 8) * 8;
|
||||
}
|
||||
|
||||
w->as.ansiTerm.cursorCol = col;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'm': // SGR - select graphic rendition
|
||||
ansiTermProcessSgr(w);
|
||||
break;
|
||||
|
||||
case 'n': // DSR - device status report
|
||||
{
|
||||
int32_t mode = (n >= 1) ? p[0] : 0;
|
||||
|
||||
if (w->as.ansiTerm.commWrite) {
|
||||
if (mode == 6) {
|
||||
// CPR — cursor position report: ESC[row;colR (1-based)
|
||||
char reply[16];
|
||||
int32_t len = snprintf(reply, sizeof(reply), "\033[%ld;%ldR", (long)(w->as.ansiTerm.cursorRow + 1), (long)(w->as.ansiTerm.cursorCol + 1));
|
||||
w->as.ansiTerm.commWrite(w->as.ansiTerm.commCtx, (const uint8_t *)reply, len);
|
||||
} else if (mode == 255) {
|
||||
// Screen size report
|
||||
char reply[16];
|
||||
int32_t len = snprintf(reply, sizeof(reply), "\033[%ld;%ldR", (long)w->as.ansiTerm.rows, (long)w->as.ansiTerm.cols);
|
||||
w->as.ansiTerm.commWrite(w->as.ansiTerm.commCtx, (const uint8_t *)reply, len);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'r': // DECSTBM - set scrolling region
|
||||
{
|
||||
int32_t rows = w->as.ansiTerm.rows;
|
||||
int32_t top = (n >= 1 && p[0]) ? p[0] - 1 : 0;
|
||||
int32_t bot = (n >= 2 && p[1]) ? p[1] - 1 : rows - 1;
|
||||
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
}
|
||||
|
||||
if (bot >= rows) {
|
||||
bot = rows - 1;
|
||||
}
|
||||
|
||||
if (top < bot) {
|
||||
w->as.ansiTerm.scrollTop = top;
|
||||
w->as.ansiTerm.scrollBot = bot;
|
||||
} else {
|
||||
// Invalid or reset — restore full screen
|
||||
w->as.ansiTerm.scrollTop = 0;
|
||||
w->as.ansiTerm.scrollBot = rows - 1;
|
||||
}
|
||||
|
||||
// Home cursor (relative to region if origin mode)
|
||||
w->as.ansiTerm.cursorRow = w->as.ansiTerm.originMode ? w->as.ansiTerm.scrollTop : 0;
|
||||
w->as.ansiTerm.cursorCol = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case 's': // SCP - save cursor position
|
||||
w->as.ansiTerm.savedRow = w->as.ansiTerm.cursorRow;
|
||||
w->as.ansiTerm.savedCol = w->as.ansiTerm.cursorCol;
|
||||
|
|
@ -475,11 +627,11 @@ static const uint8_t *ansiTermGetLine(WidgetT *w, int32_t lineIndex) {
|
|||
|
||||
static void ansiTermInsertLines(WidgetT *w, int32_t count) {
|
||||
int32_t cols = w->as.ansiTerm.cols;
|
||||
int32_t rows = w->as.ansiTerm.rows;
|
||||
int32_t bot = w->as.ansiTerm.scrollBot;
|
||||
int32_t row = w->as.ansiTerm.cursorRow;
|
||||
|
||||
if (count > rows - row) {
|
||||
count = rows - row;
|
||||
if (count > bot - row + 1) {
|
||||
count = bot - row + 1;
|
||||
}
|
||||
|
||||
if (count <= 0) {
|
||||
|
|
@ -487,20 +639,22 @@ static void ansiTermInsertLines(WidgetT *w, int32_t count) {
|
|||
}
|
||||
|
||||
uint8_t *cells = w->as.ansiTerm.cells;
|
||||
|
||||
// Shift lines down
|
||||
int32_t bytesPerRow = cols * 2;
|
||||
|
||||
// Shift lines down within region
|
||||
if (row + count <= bot) {
|
||||
memmove(cells + (row + count) * bytesPerRow,
|
||||
cells + row * bytesPerRow,
|
||||
(rows - row - count) * bytesPerRow);
|
||||
(bot - row - count + 1) * bytesPerRow);
|
||||
}
|
||||
|
||||
// Clear the inserted lines
|
||||
for (int32_t r = row; r < row + count; r++) {
|
||||
ansiTermFillCells(w, r * cols, cols);
|
||||
}
|
||||
|
||||
// All rows from cursorRow down are affected
|
||||
for (int32_t r = row; r < rows; r++) {
|
||||
// Dirty affected rows
|
||||
for (int32_t r = row; r <= bot; r++) {
|
||||
ansiTermDirtyRow(w, r);
|
||||
}
|
||||
}
|
||||
|
|
@ -513,11 +667,12 @@ static void ansiTermInsertLines(WidgetT *w, int32_t count) {
|
|||
// Move cursor to next line, scrolling if at the bottom.
|
||||
|
||||
static void ansiTermNewline(WidgetT *w) {
|
||||
w->as.ansiTerm.cursorRow++;
|
||||
int32_t bot = w->as.ansiTerm.scrollBot;
|
||||
|
||||
if (w->as.ansiTerm.cursorRow >= w->as.ansiTerm.rows) {
|
||||
w->as.ansiTerm.cursorRow = w->as.ansiTerm.rows - 1;
|
||||
if (w->as.ansiTerm.cursorRow == bot) {
|
||||
ansiTermScrollUp(w);
|
||||
} else if (w->as.ansiTerm.cursorRow < w->as.ansiTerm.rows - 1) {
|
||||
w->as.ansiTerm.cursorRow++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -547,6 +702,11 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
|
|||
if (w->as.ansiTerm.cursorCol >= w->as.ansiTerm.cols) {
|
||||
w->as.ansiTerm.cursorCol = w->as.ansiTerm.cols - 1;
|
||||
}
|
||||
} else if (ch == '\f') {
|
||||
// Form feed — clear screen and home cursor
|
||||
ansiTermEraseDisplay(w, 2);
|
||||
w->as.ansiTerm.cursorRow = 0;
|
||||
w->as.ansiTerm.cursorCol = 0;
|
||||
} else if (ch == '\a') {
|
||||
// Bell — ignored
|
||||
} else {
|
||||
|
|
@ -562,6 +722,27 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
|
|||
w->as.ansiTerm.paramCount = 0;
|
||||
w->as.ansiTerm.csiPrivate = false;
|
||||
memset(w->as.ansiTerm.params, 0, sizeof(w->as.ansiTerm.params));
|
||||
} else if (ch == 'D') {
|
||||
// IND — scroll up one line
|
||||
ansiTermScrollUp(w);
|
||||
w->as.ansiTerm.parseState = PARSE_NORMAL;
|
||||
} else if (ch == 'M') {
|
||||
// RI — scroll down one line
|
||||
ansiTermScrollDown(w);
|
||||
w->as.ansiTerm.parseState = PARSE_NORMAL;
|
||||
} else if (ch == 'c') {
|
||||
// RIS — terminal reset
|
||||
w->as.ansiTerm.cursorRow = 0;
|
||||
w->as.ansiTerm.cursorCol = 0;
|
||||
w->as.ansiTerm.curAttr = ANSI_DEFAULT_ATTR;
|
||||
w->as.ansiTerm.bold = false;
|
||||
w->as.ansiTerm.wrapMode = true;
|
||||
w->as.ansiTerm.originMode = false;
|
||||
w->as.ansiTerm.cursorVisible = true;
|
||||
w->as.ansiTerm.scrollTop = 0;
|
||||
w->as.ansiTerm.scrollBot = w->as.ansiTerm.rows - 1;
|
||||
ansiTermEraseDisplay(w, 2);
|
||||
w->as.ansiTerm.parseState = PARSE_NORMAL;
|
||||
} else {
|
||||
// Unknown escape — return to normal
|
||||
w->as.ansiTerm.parseState = PARSE_NORMAL;
|
||||
|
|
@ -635,6 +816,9 @@ static void ansiTermProcessSgr(WidgetT *w) {
|
|||
uint8_t tmp = fg;
|
||||
fg = bg;
|
||||
bg = tmp;
|
||||
} else if (code == 8) {
|
||||
// Invisible — foreground same as background
|
||||
fg = bg & 0x07;
|
||||
} else if (code == 22) {
|
||||
// Normal intensity
|
||||
w->as.ansiTerm.bold = false;
|
||||
|
|
@ -698,17 +882,25 @@ static void ansiTermPutChar(WidgetT *w, uint8_t ch) {
|
|||
|
||||
static void ansiTermScrollDown(WidgetT *w) {
|
||||
int32_t cols = w->as.ansiTerm.cols;
|
||||
int32_t rows = w->as.ansiTerm.rows;
|
||||
int32_t top = w->as.ansiTerm.scrollTop;
|
||||
int32_t bot = w->as.ansiTerm.scrollBot;
|
||||
uint8_t *cells = w->as.ansiTerm.cells;
|
||||
int32_t bytesPerRow = cols * 2;
|
||||
|
||||
// Shift all lines down by one
|
||||
memmove(cells + bytesPerRow, cells, (rows - 1) * bytesPerRow);
|
||||
// Shift lines within region down by one
|
||||
if (bot > top) {
|
||||
memmove(cells + (top + 1) * bytesPerRow,
|
||||
cells + top * bytesPerRow,
|
||||
(bot - top) * bytesPerRow);
|
||||
}
|
||||
|
||||
// Clear the top line
|
||||
ansiTermFillCells(w, 0, cols);
|
||||
// Clear the top line of the region
|
||||
ansiTermFillCells(w, top * cols, cols);
|
||||
|
||||
ansiTermDirtyAll(w);
|
||||
// Dirty affected rows
|
||||
for (int32_t r = top; r <= bot && r < 32; r++) {
|
||||
w->as.ansiTerm.dirtyRows |= (1U << r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -721,28 +913,35 @@ static void ansiTermScrollDown(WidgetT *w) {
|
|||
|
||||
static void ansiTermScrollUp(WidgetT *w) {
|
||||
int32_t cols = w->as.ansiTerm.cols;
|
||||
int32_t rows = w->as.ansiTerm.rows;
|
||||
int32_t top = w->as.ansiTerm.scrollTop;
|
||||
int32_t bot = w->as.ansiTerm.scrollBot;
|
||||
uint8_t *cells = w->as.ansiTerm.cells;
|
||||
int32_t bytesPerRow = cols * 2;
|
||||
|
||||
// Track whether the view was following live output
|
||||
// Only push to scrollback when scrolling the full screen
|
||||
if (top == 0 && bot == w->as.ansiTerm.rows - 1) {
|
||||
bool wasAtBottom = (w->as.ansiTerm.scrollPos == w->as.ansiTerm.scrollbackCount);
|
||||
|
||||
// Push top line to scrollback
|
||||
ansiTermAddToScrollback(w, 0);
|
||||
|
||||
// Shift all lines up by one
|
||||
memmove(cells, cells + bytesPerRow, (rows - 1) * bytesPerRow);
|
||||
|
||||
// Clear the bottom line
|
||||
ansiTermFillCells(w, (rows - 1) * cols, cols);
|
||||
|
||||
// Keep view at bottom if it was following live output
|
||||
if (wasAtBottom) {
|
||||
w->as.ansiTerm.scrollPos = w->as.ansiTerm.scrollbackCount;
|
||||
}
|
||||
}
|
||||
|
||||
ansiTermDirtyAll(w);
|
||||
// Shift lines within region up by one
|
||||
if (bot > top) {
|
||||
memmove(cells + top * bytesPerRow,
|
||||
cells + (top + 1) * bytesPerRow,
|
||||
(bot - top) * bytesPerRow);
|
||||
}
|
||||
|
||||
// Clear the bottom line of the region
|
||||
ansiTermFillCells(w, bot * cols, cols);
|
||||
|
||||
// Dirty affected rows
|
||||
for (int32_t r = top; r <= bot && r < 32; r++) {
|
||||
w->as.ansiTerm.dirtyRows |= (1U << r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -794,12 +993,15 @@ WidgetT *wgtAnsiTerm(WidgetT *parent, int32_t cols, int32_t rows) {
|
|||
w->as.ansiTerm.cursorVisible = true;
|
||||
w->as.ansiTerm.wrapMode = true;
|
||||
w->as.ansiTerm.bold = false;
|
||||
w->as.ansiTerm.originMode = false;
|
||||
w->as.ansiTerm.csiPrivate = false;
|
||||
w->as.ansiTerm.curAttr = ANSI_DEFAULT_ATTR;
|
||||
w->as.ansiTerm.parseState = PARSE_NORMAL;
|
||||
w->as.ansiTerm.paramCount = 0;
|
||||
w->as.ansiTerm.savedRow = 0;
|
||||
w->as.ansiTerm.savedCol = 0;
|
||||
w->as.ansiTerm.scrollTop = 0;
|
||||
w->as.ansiTerm.scrollBot = rows - 1;
|
||||
w->as.ansiTerm.scrollbackMax = sbMax;
|
||||
w->as.ansiTerm.scrollbackCount = 0;
|
||||
w->as.ansiTerm.scrollbackHead = 0;
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ static int sClientFd = -1;
|
|||
|
||||
static int connectToBbs(const char *host, int port);
|
||||
static int createListenSocket(int port);
|
||||
static void hexDump(const char *label, const uint8_t *data, int len);
|
||||
static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
||||
static void seedRng(void);
|
||||
static void sigHandler(int sig);
|
||||
|
|
@ -152,28 +151,11 @@ static int createListenSocket(int port) {
|
|||
}
|
||||
|
||||
|
||||
static void hexDump(const char *label, const uint8_t *data, int len) {
|
||||
printf("%s (%d bytes):", label, len);
|
||||
for (int i = 0; i < len && i < 64; i++) {
|
||||
if (i % 16 == 0) {
|
||||
printf("\n ");
|
||||
}
|
||||
printf("%02X ", data[i]);
|
||||
}
|
||||
if (len > 64) {
|
||||
printf("\n ...");
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel) {
|
||||
int bbsFd = *(int *)ctx;
|
||||
|
||||
(void)channel;
|
||||
|
||||
hexDump("DOS->proxy (decrypted)", data, len);
|
||||
|
||||
// Check for ENTER before BBS is connected
|
||||
if (!sGotEnter) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
|
|
@ -186,8 +168,6 @@ static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t chann
|
|||
return;
|
||||
}
|
||||
|
||||
hexDump("DOS->BBS", data, len);
|
||||
|
||||
int sent = 0;
|
||||
while (sent < len) {
|
||||
ssize_t n = write(bbsFd, data + sent, len - sent);
|
||||
|
|
@ -487,8 +467,6 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
int cleanLen = telnetFilter(bbsFd, raw, (int)n, clean);
|
||||
if (cleanLen > 0) {
|
||||
hexDump("BBS->DOS", clean, cleanLen);
|
||||
|
||||
// Retry with ACK processing if the send window is full
|
||||
int rc = secLinkSend(link, clean, cleanLen, CHANNEL_TERMINAL, true, false);
|
||||
while (rc != SECLINK_SUCCESS && sRunning) {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@
|
|||
#include "dvxWidget.h"
|
||||
#include "secLink.h"
|
||||
#include "security.h"
|
||||
#include "../rs232/rs232.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -190,15 +188,6 @@ int main(int argc, char *argv[]) {
|
|||
TermContextT tc;
|
||||
memset(&tc, 0, sizeof(tc));
|
||||
|
||||
// Test rs232 directly first for diagnostics
|
||||
printf("Testing rs232Open on COM%d...\n", comPort + 1);
|
||||
int rsRc = rs232Open(comPort, baudRate, 8, 'N', 1, 0);
|
||||
printf("rs232Open returned %d\n", rsRc);
|
||||
if (rsRc == RS232_SUCCESS) {
|
||||
printf("UART type: %d\n", rs232GetUartType(comPort));
|
||||
rs232Close(comPort);
|
||||
}
|
||||
|
||||
// Open secLink on the specified COM port
|
||||
printf("Opening SecLink on COM%d...\n", comPort + 1);
|
||||
tc.link = secLinkOpen(comPort, baudRate, 8, 'N', 1, 0, onRecv, &tc);
|
||||
|
|
@ -282,15 +271,9 @@ int main(int argc, char *argv[]) {
|
|||
ctx.idleCallback = idlePoll;
|
||||
ctx.idleCtx = &tc;
|
||||
|
||||
// Main loop — poll serial, render immediately, composite
|
||||
// Main loop — poll serial, dvxUpdate handles terminal widget automatically
|
||||
while (ctx.running) {
|
||||
secLinkPoll(tc.link);
|
||||
wgtAnsiTermPoll(term);
|
||||
|
||||
if (wgtAnsiTermRepaint(term) > 0) {
|
||||
win->contentDirty = true;
|
||||
dvxInvalidateWindow(&ctx, win);
|
||||
}
|
||||
|
||||
if (!dvxUpdate(&ctx)) {
|
||||
break;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue