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 handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t buttons);
|
||||||
static void initColorScheme(AppContextT *ctx);
|
static void initColorScheme(AppContextT *ctx);
|
||||||
static void initMouse(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 pollKeyboard(AppContextT *ctx);
|
||||||
static void pollMouse(AppContextT *ctx);
|
static void pollMouse(AppContextT *ctx);
|
||||||
static void refreshMinimizedIcons(AppContextT *ctx);
|
static void refreshMinimizedIcons(AppContextT *ctx);
|
||||||
|
|
@ -536,6 +538,7 @@ bool dvxUpdate(AppContextT *ctx) {
|
||||||
pollMouse(ctx);
|
pollMouse(ctx);
|
||||||
pollKeyboard(ctx);
|
pollKeyboard(ctx);
|
||||||
dispatchEvents(ctx);
|
dispatchEvents(ctx);
|
||||||
|
pollAnsiTermWidgets(ctx);
|
||||||
|
|
||||||
// Periodically refresh one minimized window thumbnail (staggered)
|
// Periodically refresh one minimized window thumbnail (staggered)
|
||||||
ctx->frameCount++;
|
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
|
// pollKeyboard
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,7 @@ typedef struct WidgetT {
|
||||||
bool cursorVisible;
|
bool cursorVisible;
|
||||||
bool wrapMode; // auto-wrap at right margin
|
bool wrapMode; // auto-wrap at right margin
|
||||||
bool bold; // SGR bold flag (brightens foreground)
|
bool bold; // SGR bold flag (brightens foreground)
|
||||||
|
bool originMode; // cursor positioning relative to scroll region
|
||||||
bool csiPrivate; // '?' prefix in CSI sequence
|
bool csiPrivate; // '?' prefix in CSI sequence
|
||||||
uint8_t curAttr; // current text attribute (fg | bg<<4)
|
uint8_t curAttr; // current text attribute (fg | bg<<4)
|
||||||
uint8_t parseState; // 0=normal, 1=ESC, 2=CSI
|
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 paramCount; // number of CSI params collected
|
||||||
int32_t savedRow; // saved cursor position (SCP)
|
int32_t savedRow; // saved cursor position (SCP)
|
||||||
int32_t savedCol;
|
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
|
// Scrollback
|
||||||
uint8_t *scrollback; // circular buffer of scrollback lines
|
uint8_t *scrollback; // circular buffer of scrollback lines
|
||||||
int32_t scrollbackMax; // max lines in scrollback buffer
|
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 ansiTermAddToScrollback(WidgetT *w, int32_t screenRow);
|
||||||
static void ansiTermDeleteLines(WidgetT *w, int32_t count);
|
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 ansiTermDirtyRange(WidgetT *w, int32_t startCell, int32_t count);
|
||||||
static void ansiTermDirtyRow(WidgetT *w, int32_t row);
|
static void ansiTermDirtyRow(WidgetT *w, int32_t row);
|
||||||
static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd);
|
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
|
// ansiTermDirtyRange
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -148,11 +138,11 @@ static void ansiTermDirtyRow(WidgetT *w, int32_t row) {
|
||||||
|
|
||||||
static void ansiTermDeleteLines(WidgetT *w, int32_t count) {
|
static void ansiTermDeleteLines(WidgetT *w, int32_t count) {
|
||||||
int32_t cols = w->as.ansiTerm.cols;
|
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;
|
int32_t row = w->as.ansiTerm.cursorRow;
|
||||||
|
|
||||||
if (count > rows - row) {
|
if (count > bot - row + 1) {
|
||||||
count = rows - row;
|
count = bot - row + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count <= 0) {
|
if (count <= 0) {
|
||||||
|
|
@ -160,20 +150,22 @@ static void ansiTermDeleteLines(WidgetT *w, int32_t count) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *cells = w->as.ansiTerm.cells;
|
uint8_t *cells = w->as.ansiTerm.cells;
|
||||||
|
|
||||||
// Shift lines up
|
|
||||||
int32_t bytesPerRow = cols * 2;
|
int32_t bytesPerRow = cols * 2;
|
||||||
|
|
||||||
|
// Shift lines up within region
|
||||||
|
if (row + count <= bot) {
|
||||||
memmove(cells + row * bytesPerRow,
|
memmove(cells + row * bytesPerRow,
|
||||||
cells + (row + count) * bytesPerRow,
|
cells + (row + count) * bytesPerRow,
|
||||||
(rows - row - count) * bytesPerRow);
|
(bot - row - count + 1) * bytesPerRow);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the bottom lines
|
// Clear the bottom lines of the region
|
||||||
for (int32_t r = rows - count; r < rows; r++) {
|
for (int32_t r = bot - count + 1; r <= bot; r++) {
|
||||||
ansiTermFillCells(w, r * cols, cols);
|
ansiTermFillCells(w, r * cols, cols);
|
||||||
}
|
}
|
||||||
|
|
||||||
// All rows from cursorRow down are affected
|
// Dirty affected rows
|
||||||
for (int32_t r = row; r < rows; r++) {
|
for (int32_t r = row; r <= bot; r++) {
|
||||||
ansiTermDirtyRow(w, r);
|
ansiTermDirtyRow(w, r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -192,6 +184,12 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
int32_t mode = (n >= 1) ? p[0] : 0;
|
int32_t mode = (n >= 1) ? p[0] : 0;
|
||||||
|
|
||||||
if (cmd == 'h') {
|
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) {
|
if (mode == 7) {
|
||||||
w->as.ansiTerm.wrapMode = true;
|
w->as.ansiTerm.wrapMode = true;
|
||||||
}
|
}
|
||||||
|
|
@ -200,6 +198,12 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
w->as.ansiTerm.cursorVisible = true;
|
w->as.ansiTerm.cursorVisible = true;
|
||||||
}
|
}
|
||||||
} else if (cmd == 'l') {
|
} 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) {
|
if (mode == 7) {
|
||||||
w->as.ansiTerm.wrapMode = false;
|
w->as.ansiTerm.wrapMode = false;
|
||||||
}
|
}
|
||||||
|
|
@ -213,13 +217,40 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (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
|
case 'A': // CUU - cursor up
|
||||||
{
|
{
|
||||||
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
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;
|
w->as.ansiTerm.cursorRow -= count;
|
||||||
|
|
||||||
if (w->as.ansiTerm.cursorRow < 0) {
|
if (w->as.ansiTerm.cursorRow < minRow) {
|
||||||
w->as.ansiTerm.cursorRow = 0;
|
w->as.ansiTerm.cursorRow = minRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -228,10 +259,11 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
case 'B': // CUD - cursor down
|
case 'B': // CUD - cursor down
|
||||||
{
|
{
|
||||||
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
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;
|
w->as.ansiTerm.cursorRow += count;
|
||||||
|
|
||||||
if (w->as.ansiTerm.cursorRow >= w->as.ansiTerm.rows) {
|
if (w->as.ansiTerm.cursorRow > maxRow) {
|
||||||
w->as.ansiTerm.cursorRow = w->as.ansiTerm.rows - 1;
|
w->as.ansiTerm.cursorRow = maxRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -261,12 +293,44 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
break;
|
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 'H': // CUP - cursor position
|
||||||
case 'f': // HVP - same
|
case 'f': // HVP - same
|
||||||
{
|
{
|
||||||
int32_t row = (n >= 1 && p[0]) ? p[0] - 1 : 0;
|
int32_t row = (n >= 1 && p[0]) ? p[0] - 1 : 0;
|
||||||
int32_t col = (n >= 2 && p[1]) ? p[1] - 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) {
|
if (row < 0) {
|
||||||
row = 0;
|
row = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -316,6 +380,32 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
break;
|
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
|
case 'S': // SU - scroll up
|
||||||
{
|
{
|
||||||
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
int32_t count = (n >= 1 && p[0]) ? p[0] : 1;
|
||||||
|
|
@ -338,10 +428,72 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
|
||||||
break;
|
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
|
case 'm': // SGR - select graphic rendition
|
||||||
ansiTermProcessSgr(w);
|
ansiTermProcessSgr(w);
|
||||||
break;
|
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
|
case 's': // SCP - save cursor position
|
||||||
w->as.ansiTerm.savedRow = w->as.ansiTerm.cursorRow;
|
w->as.ansiTerm.savedRow = w->as.ansiTerm.cursorRow;
|
||||||
w->as.ansiTerm.savedCol = w->as.ansiTerm.cursorCol;
|
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) {
|
static void ansiTermInsertLines(WidgetT *w, int32_t count) {
|
||||||
int32_t cols = w->as.ansiTerm.cols;
|
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;
|
int32_t row = w->as.ansiTerm.cursorRow;
|
||||||
|
|
||||||
if (count > rows - row) {
|
if (count > bot - row + 1) {
|
||||||
count = rows - row;
|
count = bot - row + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count <= 0) {
|
if (count <= 0) {
|
||||||
|
|
@ -487,20 +639,22 @@ static void ansiTermInsertLines(WidgetT *w, int32_t count) {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *cells = w->as.ansiTerm.cells;
|
uint8_t *cells = w->as.ansiTerm.cells;
|
||||||
|
|
||||||
// Shift lines down
|
|
||||||
int32_t bytesPerRow = cols * 2;
|
int32_t bytesPerRow = cols * 2;
|
||||||
|
|
||||||
|
// Shift lines down within region
|
||||||
|
if (row + count <= bot) {
|
||||||
memmove(cells + (row + count) * bytesPerRow,
|
memmove(cells + (row + count) * bytesPerRow,
|
||||||
cells + row * bytesPerRow,
|
cells + row * bytesPerRow,
|
||||||
(rows - row - count) * bytesPerRow);
|
(bot - row - count + 1) * bytesPerRow);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the inserted lines
|
// Clear the inserted lines
|
||||||
for (int32_t r = row; r < row + count; r++) {
|
for (int32_t r = row; r < row + count; r++) {
|
||||||
ansiTermFillCells(w, r * cols, cols);
|
ansiTermFillCells(w, r * cols, cols);
|
||||||
}
|
}
|
||||||
|
|
||||||
// All rows from cursorRow down are affected
|
// Dirty affected rows
|
||||||
for (int32_t r = row; r < rows; r++) {
|
for (int32_t r = row; r <= bot; r++) {
|
||||||
ansiTermDirtyRow(w, 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.
|
// Move cursor to next line, scrolling if at the bottom.
|
||||||
|
|
||||||
static void ansiTermNewline(WidgetT *w) {
|
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) {
|
if (w->as.ansiTerm.cursorRow == bot) {
|
||||||
w->as.ansiTerm.cursorRow = w->as.ansiTerm.rows - 1;
|
|
||||||
ansiTermScrollUp(w);
|
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) {
|
if (w->as.ansiTerm.cursorCol >= w->as.ansiTerm.cols) {
|
||||||
w->as.ansiTerm.cursorCol = w->as.ansiTerm.cols - 1;
|
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') {
|
} else if (ch == '\a') {
|
||||||
// Bell — ignored
|
// Bell — ignored
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -562,6 +722,27 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
|
||||||
w->as.ansiTerm.paramCount = 0;
|
w->as.ansiTerm.paramCount = 0;
|
||||||
w->as.ansiTerm.csiPrivate = false;
|
w->as.ansiTerm.csiPrivate = false;
|
||||||
memset(w->as.ansiTerm.params, 0, sizeof(w->as.ansiTerm.params));
|
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 {
|
} else {
|
||||||
// Unknown escape — return to normal
|
// Unknown escape — return to normal
|
||||||
w->as.ansiTerm.parseState = PARSE_NORMAL;
|
w->as.ansiTerm.parseState = PARSE_NORMAL;
|
||||||
|
|
@ -635,6 +816,9 @@ static void ansiTermProcessSgr(WidgetT *w) {
|
||||||
uint8_t tmp = fg;
|
uint8_t tmp = fg;
|
||||||
fg = bg;
|
fg = bg;
|
||||||
bg = tmp;
|
bg = tmp;
|
||||||
|
} else if (code == 8) {
|
||||||
|
// Invisible — foreground same as background
|
||||||
|
fg = bg & 0x07;
|
||||||
} else if (code == 22) {
|
} else if (code == 22) {
|
||||||
// Normal intensity
|
// Normal intensity
|
||||||
w->as.ansiTerm.bold = false;
|
w->as.ansiTerm.bold = false;
|
||||||
|
|
@ -698,17 +882,25 @@ static void ansiTermPutChar(WidgetT *w, uint8_t ch) {
|
||||||
|
|
||||||
static void ansiTermScrollDown(WidgetT *w) {
|
static void ansiTermScrollDown(WidgetT *w) {
|
||||||
int32_t cols = w->as.ansiTerm.cols;
|
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;
|
uint8_t *cells = w->as.ansiTerm.cells;
|
||||||
int32_t bytesPerRow = cols * 2;
|
int32_t bytesPerRow = cols * 2;
|
||||||
|
|
||||||
// Shift all lines down by one
|
// Shift lines within region down by one
|
||||||
memmove(cells + bytesPerRow, cells, (rows - 1) * bytesPerRow);
|
if (bot > top) {
|
||||||
|
memmove(cells + (top + 1) * bytesPerRow,
|
||||||
|
cells + top * bytesPerRow,
|
||||||
|
(bot - top) * bytesPerRow);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the top line
|
// Clear the top line of the region
|
||||||
ansiTermFillCells(w, 0, cols);
|
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) {
|
static void ansiTermScrollUp(WidgetT *w) {
|
||||||
int32_t cols = w->as.ansiTerm.cols;
|
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;
|
uint8_t *cells = w->as.ansiTerm.cells;
|
||||||
int32_t bytesPerRow = cols * 2;
|
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);
|
bool wasAtBottom = (w->as.ansiTerm.scrollPos == w->as.ansiTerm.scrollbackCount);
|
||||||
|
|
||||||
// Push top line to scrollback
|
|
||||||
ansiTermAddToScrollback(w, 0);
|
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) {
|
if (wasAtBottom) {
|
||||||
w->as.ansiTerm.scrollPos = w->as.ansiTerm.scrollbackCount;
|
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.cursorVisible = true;
|
||||||
w->as.ansiTerm.wrapMode = true;
|
w->as.ansiTerm.wrapMode = true;
|
||||||
w->as.ansiTerm.bold = false;
|
w->as.ansiTerm.bold = false;
|
||||||
|
w->as.ansiTerm.originMode = false;
|
||||||
w->as.ansiTerm.csiPrivate = false;
|
w->as.ansiTerm.csiPrivate = false;
|
||||||
w->as.ansiTerm.curAttr = ANSI_DEFAULT_ATTR;
|
w->as.ansiTerm.curAttr = ANSI_DEFAULT_ATTR;
|
||||||
w->as.ansiTerm.parseState = PARSE_NORMAL;
|
w->as.ansiTerm.parseState = PARSE_NORMAL;
|
||||||
w->as.ansiTerm.paramCount = 0;
|
w->as.ansiTerm.paramCount = 0;
|
||||||
w->as.ansiTerm.savedRow = 0;
|
w->as.ansiTerm.savedRow = 0;
|
||||||
w->as.ansiTerm.savedCol = 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.scrollbackMax = sbMax;
|
||||||
w->as.ansiTerm.scrollbackCount = 0;
|
w->as.ansiTerm.scrollbackCount = 0;
|
||||||
w->as.ansiTerm.scrollbackHead = 0;
|
w->as.ansiTerm.scrollbackHead = 0;
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,6 @@ static int sClientFd = -1;
|
||||||
|
|
||||||
static int connectToBbs(const char *host, int port);
|
static int connectToBbs(const char *host, int port);
|
||||||
static int createListenSocket(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 onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
||||||
static void seedRng(void);
|
static void seedRng(void);
|
||||||
static void sigHandler(int sig);
|
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) {
|
static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel) {
|
||||||
int bbsFd = *(int *)ctx;
|
int bbsFd = *(int *)ctx;
|
||||||
|
|
||||||
(void)channel;
|
(void)channel;
|
||||||
|
|
||||||
hexDump("DOS->proxy (decrypted)", data, len);
|
|
||||||
|
|
||||||
// Check for ENTER before BBS is connected
|
// Check for ENTER before BBS is connected
|
||||||
if (!sGotEnter) {
|
if (!sGotEnter) {
|
||||||
for (int i = 0; i < len; i++) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
hexDump("DOS->BBS", data, len);
|
|
||||||
|
|
||||||
int sent = 0;
|
int sent = 0;
|
||||||
while (sent < len) {
|
while (sent < len) {
|
||||||
ssize_t n = write(bbsFd, data + sent, len - sent);
|
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);
|
int cleanLen = telnetFilter(bbsFd, raw, (int)n, clean);
|
||||||
if (cleanLen > 0) {
|
if (cleanLen > 0) {
|
||||||
hexDump("BBS->DOS", clean, cleanLen);
|
|
||||||
|
|
||||||
// Retry with ACK processing if the send window is full
|
// Retry with ACK processing if the send window is full
|
||||||
int rc = secLinkSend(link, clean, cleanLen, CHANNEL_TERMINAL, true, false);
|
int rc = secLinkSend(link, clean, cleanLen, CHANNEL_TERMINAL, true, false);
|
||||||
while (rc != SECLINK_SUCCESS && sRunning) {
|
while (rc != SECLINK_SUCCESS && sRunning) {
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@
|
||||||
#include "dvxWidget.h"
|
#include "dvxWidget.h"
|
||||||
#include "secLink.h"
|
#include "secLink.h"
|
||||||
#include "security.h"
|
#include "security.h"
|
||||||
#include "../rs232/rs232.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
@ -190,15 +188,6 @@ int main(int argc, char *argv[]) {
|
||||||
TermContextT tc;
|
TermContextT tc;
|
||||||
memset(&tc, 0, sizeof(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
|
// Open secLink on the specified COM port
|
||||||
printf("Opening SecLink on COM%d...\n", comPort + 1);
|
printf("Opening SecLink on COM%d...\n", comPort + 1);
|
||||||
tc.link = secLinkOpen(comPort, baudRate, 8, 'N', 1, 0, onRecv, &tc);
|
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.idleCallback = idlePoll;
|
||||||
ctx.idleCtx = &tc;
|
ctx.idleCtx = &tc;
|
||||||
|
|
||||||
// Main loop — poll serial, render immediately, composite
|
// Main loop — poll serial, dvxUpdate handles terminal widget automatically
|
||||||
while (ctx.running) {
|
while (ctx.running) {
|
||||||
secLinkPoll(tc.link);
|
secLinkPoll(tc.link);
|
||||||
wgtAnsiTermPoll(term);
|
|
||||||
|
|
||||||
if (wgtAnsiTermRepaint(term) > 0) {
|
|
||||||
win->contentDirty = true;
|
|
||||||
dvxInvalidateWindow(&ctx, win);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dvxUpdate(&ctx)) {
|
if (!dvxUpdate(&ctx)) {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue