734 lines
20 KiB
C
734 lines
20 KiB
C
/*
|
|
* Kangaroo Punch MultiPlayer Game Server Mark II
|
|
* Copyright (C) 2020-2021 Scott Duensing
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
// http://www.ansi-bbs.org/BANSI/bansi.txt
|
|
// http://ansi-bbs.org/ansi-bbs-core-server.html
|
|
|
|
|
|
#include "terminal.h"
|
|
|
|
|
|
//#define termWrite(...) logWrite(__VA_ARGS__)
|
|
#define termWrite(...) while(0){}
|
|
|
|
|
|
#define TERMINAL_CELL_GET_FLAG(t,x,y,f) (((t)->cells[(x)][(y)].flags & (1 << (f))) != 0)
|
|
#define TERMINAL_CELL_SET_FLAG(t,x,y,f) ((t)->cells[(x)][(y)].flags |= (1 << (f)))
|
|
#define TERMINAL_CELL_CLEAR_FLAG(t,x,y,f) ((t)->cells[(x)][(y)].flags &= (~(1 << (f))))
|
|
|
|
|
|
static void terminalDel(WidgetT **widget);
|
|
static void terminalFocusEvent(WidgetT *widget, uint8_t focused);
|
|
static int16_t terminalIntConvert(char *number, int16_t defaultValue);
|
|
static void terminalMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event);
|
|
static void terminalKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt);
|
|
static void terminalPaint(WidgetT *widget, uint8_t enabled, RectT pos);
|
|
static void terminalSequenceReset(TerminalT *terminal);
|
|
|
|
|
|
void terminalBackgroundSet(TerminalT *terminal, uint8_t color) {
|
|
terminal->background = color;
|
|
}
|
|
|
|
|
|
void terminalCharPrint(TerminalT *terminal, uint8_t c) {
|
|
int16_t x;
|
|
int16_t y;
|
|
uint16_t p;
|
|
|
|
// Find leading ESC.
|
|
if (c == 27 && !terminal->escFound && !terminal->inEscape) {
|
|
terminal->escFound = 1;
|
|
return;
|
|
}
|
|
|
|
// Find [.
|
|
if (c == '[' && terminal->escFound && !terminal->inEscape) {
|
|
terminal->inEscape = 1;
|
|
return;
|
|
}
|
|
|
|
// Is this a sequence that is ESC and then not [ ?
|
|
if (terminal->escFound && !terminal->inEscape) {
|
|
termWrite("Unexpected character: '%c' %d\n", c, c);
|
|
terminalSequenceReset(terminal);
|
|
return;
|
|
}
|
|
|
|
if (terminal->inEscape) {
|
|
switch (c) {
|
|
|
|
case '[':
|
|
case 27:
|
|
termWrite("Escape found inside sequence\n");
|
|
// ESC sequence inside sequence. Invalid ANSIBBS.
|
|
//***TODO*** Can stick custom sequences here.
|
|
break;
|
|
|
|
// End of a parameter
|
|
case ';':
|
|
arrput(terminal->parameters, strdup(terminal->number));
|
|
terminal->number[0] = 0;
|
|
terminal->numberIndex = 0;
|
|
break;
|
|
|
|
// Cursor up.
|
|
case 'A':
|
|
y = terminalIntConvert(terminal->number, 1);
|
|
termWrite("Moving cursor up %d\n", y);
|
|
terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY - y);
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Cursor down.
|
|
case 'B':
|
|
y = terminalIntConvert(terminal->number, 1);
|
|
termWrite("Moving cursor down %d\n", y);
|
|
terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY + y);
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Cursor forward.
|
|
case 'C':
|
|
x = terminalIntConvert(terminal->number, 1);
|
|
termWrite("Moving cursor forward %d\n", x);
|
|
terminalCursorMove(terminal, terminal->cursorX + x, terminal->cursorY);
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Cursor backward.
|
|
case 'D':
|
|
x = terminalIntConvert(terminal->number, 1);
|
|
termWrite("Moving cursor backward %d\n", x);
|
|
terminalCursorMove(terminal, terminal->cursorX - x, terminal->cursorY);
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Cursor line down.
|
|
case 'E':
|
|
y = terminalIntConvert(terminal->number, 1);
|
|
termWrite("Moving cursor down %d lines\n", y);
|
|
terminalCursorMove(terminal, 1, terminal->cursorY + y);
|
|
//***TODO*** This should allow scrolling.
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Cursor line up.
|
|
case 'F':
|
|
y = terminalIntConvert(terminal->number, 1);
|
|
termWrite("Moving cursor up %d lines\n", y);
|
|
terminalCursorMove(terminal, 1, terminal->cursorY - y);
|
|
//***TODO*** This should allow scrolling.
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Cursor horizontal absolute.
|
|
case 'G':
|
|
x = terminalIntConvert(terminal->number, 1);
|
|
termWrite("Moving cursor horizontal absolute %d\n", x);
|
|
terminalCursorMove(terminal, x, terminal->cursorY);
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Cursor move.
|
|
case 'H':
|
|
case 'f':
|
|
switch (arrlen(terminal->parameters)) {
|
|
// Cursor vertical absolute. Kinda. Moves X to 1.
|
|
case 0:
|
|
y = terminalIntConvert(terminal->number, 1);
|
|
termWrite("Moving cursor kinda vertictal absolute %d\n", y);
|
|
terminalCursorMove(terminal, 1, y);
|
|
break;
|
|
|
|
// Absolute position.
|
|
case 1:
|
|
x = terminalIntConvert(terminal->number, 1);
|
|
y = terminalIntConvert(terminal->parameters[0], 1);
|
|
termWrite("Moving cursor absolute %d %d\n", x, y);
|
|
terminalCursorMove(terminal, x, y);
|
|
break;
|
|
|
|
// Invalid nonsense.
|
|
default:
|
|
termWrite("Unknown cursor move: '%c' %d\n", c, c);
|
|
break;
|
|
}
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Clear display.
|
|
case 'J':
|
|
if (arrlen(terminal->parameters) == 0 && terminalIntConvert(terminal->number, 1) == 2) {
|
|
termWrite("Clear display\n");
|
|
terminalScreenClear(terminal);
|
|
terminalCursorMove(terminal, 1, 1);
|
|
} else {
|
|
//***TODO*** Unimplemented screen clear.
|
|
termWrite("Unimplemented clear display\n");
|
|
}
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Clear from cursor to end of line.
|
|
case 'K':
|
|
x = terminalIntConvert(terminal->number, 0);
|
|
if (x == 0) {
|
|
termWrite("Clear to end of line\n");
|
|
for (y=terminal->cursorX-1; y<terminal->cols; y++) {
|
|
terminal->cells[y][terminal->cursorY - 1].character = ' ';
|
|
TERMINAL_CELL_SET_FLAG(terminal, y, terminal->cursorY - 1, TERMINAL_FLAG_DIRTY);
|
|
GUI_SET_FLAG((WidgetT *)terminal, WIDGET_FLAG_DIRTY);
|
|
}
|
|
} else {
|
|
//***TODO*** Unimplemented line clear.
|
|
termWrite("Unimplemented line clear\n");
|
|
}
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Insert lines.
|
|
case 'L':
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented insert lines\n");
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Delete lines.
|
|
case 'M':
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented delete lines\n");
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Delete characters.
|
|
case 'P':
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented delete characters\n");
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Scroll up.
|
|
case 'S':
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented scroll up\n");
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Scroll down.
|
|
case 'T':
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented scroll down\n");
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Clear screen with normal attribute.
|
|
case 'U':
|
|
termWrite("Clear screen with normal attribute\n");
|
|
x = terminal->background;
|
|
terminal->background = TERMINAL_COLOR_BLACK;
|
|
terminalScreenClear(terminal);
|
|
terminal->background = x;
|
|
terminalCursorMove(terminal, 1, 1);
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Back-TAB.
|
|
case 'Z':
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented back-TAB\n");
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Set attributes.
|
|
case 'm':
|
|
arrput(terminal->parameters, strdup(terminal->number));
|
|
for (p=0; p<arrlen(terminal->parameters); p++) {
|
|
x = terminalIntConvert(terminal->parameters[p], 0);
|
|
switch (x) {
|
|
// Reset.
|
|
case 0:
|
|
termWrite("Attribute reset\n");
|
|
terminal->bold = 0;
|
|
terminal->blink = 0;
|
|
terminal->reverse = 0;
|
|
terminal->foreground = TERMINAL_COLOR_LIGHT_GRAY;
|
|
terminal->background = TERMINAL_COLOR_BLACK;
|
|
break;
|
|
|
|
// Bold.
|
|
case 1:
|
|
termWrite("Bold\n");
|
|
terminal->bold = 1;
|
|
break;
|
|
|
|
// Blink slow.
|
|
case 5:
|
|
termWrite("Blink slow\n");
|
|
terminal->blink = TERMINAL_FLAG_BLINK_SLOW;
|
|
break;
|
|
|
|
// Blink fast.
|
|
case 6:
|
|
termWrite("Blink fast\n");
|
|
terminal->blink = TERMINAL_FLAG_BLINK_FAST;
|
|
break;
|
|
|
|
// Reverse.
|
|
case 7:
|
|
if (!terminal->reverse) {
|
|
termWrite("Reverse\n");
|
|
x = terminal->foreground;
|
|
terminal->foreground = terminal->background;
|
|
terminal->background = x;
|
|
terminal->reverse = 1;
|
|
} else {
|
|
termWrite("Already reversed\n");
|
|
}
|
|
break;
|
|
|
|
// Normal intensity. (22 is the actual code, unsure exactly what 21 is.)
|
|
case 21:
|
|
case 22:
|
|
termWrite("Normal intensity\n");
|
|
terminal->bold = 0;
|
|
break;
|
|
|
|
// Steady.
|
|
case 25:
|
|
termWrite("Steady\n");
|
|
terminal->blink = 0;
|
|
break;
|
|
|
|
// Normal
|
|
case 27:
|
|
if (terminal->reverse) {
|
|
termWrite("Normal\n");
|
|
x = terminal->foreground;
|
|
terminal->foreground = terminal->background;
|
|
terminal->background = x;
|
|
terminal->reverse = 0;
|
|
} else {
|
|
termWrite("Already normal\n");
|
|
}
|
|
break;
|
|
|
|
// Color change.
|
|
default:
|
|
if (x > 29 && x < 38) {
|
|
terminal->foreground = x - 30;
|
|
termWrite("Foreground %d\n", terminal->foreground);
|
|
}
|
|
if (x > 39 && x < 48) {
|
|
terminal->background = x - 40;
|
|
termWrite("Background %d\n", terminal->background);
|
|
}
|
|
break;
|
|
|
|
} // switch (x)
|
|
}
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Define scroll region.
|
|
case 'r':
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented define scroll region\n");
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Cursor save.
|
|
case 's':
|
|
terminal->saveX = terminal->cursorX;
|
|
terminal->saveY = terminal->cursorY;
|
|
termWrite("Cursor save %d %d\n", terminal->saveX, terminal->saveX);
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Cursor restore.
|
|
case 'u':
|
|
termWrite("Cursor restore %d %d\n", terminal->saveX, terminal->saveX);
|
|
terminal->cursorX = terminal->saveX;
|
|
terminal->cursorY = terminal->saveY;
|
|
terminalSequenceReset(terminal);
|
|
break;
|
|
|
|
// Something else.
|
|
default:
|
|
// Number?
|
|
if (c >= '0' && c <= '9') {
|
|
terminal->number[terminal->numberIndex++] = c;
|
|
terminal->number[terminal->numberIndex] = 0;
|
|
} else {
|
|
// Unknown sequence.
|
|
termWrite("Unknown sequence: '%c' %d\n", c, c);
|
|
terminalSequenceReset(terminal);
|
|
}
|
|
|
|
} // switch (c)
|
|
|
|
} else { // inEscape
|
|
|
|
switch (c) {
|
|
|
|
// Bell.
|
|
case 7:
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented bell\n");
|
|
break;
|
|
|
|
// Backspace.
|
|
case 8:
|
|
case 128:
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented backspace\n");
|
|
break;
|
|
|
|
// TAB
|
|
case 9:
|
|
//***TODO*** Unimplemented.
|
|
termWrite("Unimplemented TAB\n");
|
|
break;
|
|
|
|
// Line feed.
|
|
case 10:
|
|
terminal->cursorY++;
|
|
termWrite("Line feed\n");
|
|
if (terminal->cursorY > terminal->rows) {
|
|
terminal->cursorY--;
|
|
//***TODO*** Scroll.
|
|
termWrite("Unimplemented line feed scroll\n");
|
|
}
|
|
terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY);
|
|
break;
|
|
|
|
// Clear screen (form feed).
|
|
case 12:
|
|
termWrite("Form feed\n");
|
|
x = terminal->background;
|
|
terminal->background = TERMINAL_COLOR_BLACK;
|
|
terminalScreenClear(terminal);
|
|
terminal->background = x;
|
|
terminalCursorMove(terminal, 1, 1);
|
|
break;
|
|
|
|
// Carrage return.
|
|
case 13:
|
|
termWrite("Carrage return\n");
|
|
terminalCursorMove(terminal, 1, terminal->cursorY);
|
|
break;
|
|
|
|
// Non-special character.
|
|
default:
|
|
// Render character.
|
|
terminal->cells[terminal->cursorX - 1][terminal->cursorY - 1].character = c;
|
|
terminal->cells[terminal->cursorX - 1][terminal->cursorY - 1].background = terminal->background;
|
|
terminal->cells[terminal->cursorX - 1][terminal->cursorY - 1].foreground = terminal->foreground + (terminal->bold ? 8 : 0);
|
|
terminal->cells[terminal->cursorX - 1][terminal->cursorY - 1].flags = 0;
|
|
if (terminal->blink == TERMINAL_FLAG_BLINK_SLOW) TERMINAL_CELL_SET_FLAG(terminal, terminal->cursorX - 1, terminal->cursorY - 1, TERMINAL_FLAG_BLINK_SLOW);
|
|
if (terminal->blink == TERMINAL_FLAG_BLINK_FAST) TERMINAL_CELL_SET_FLAG(terminal, terminal->cursorX - 1, terminal->cursorY - 1, TERMINAL_FLAG_BLINK_FAST);
|
|
TERMINAL_CELL_SET_FLAG(terminal, terminal->cursorX - 1, terminal->cursorY - 1, TERMINAL_FLAG_DIRTY);
|
|
GUI_SET_FLAG((WidgetT *)terminal, WIDGET_FLAG_DIRTY);
|
|
// Move cursor.
|
|
terminal->cursorX++;
|
|
if (terminal->cursorX > terminal->cols) {
|
|
terminal->cursorX = 1;
|
|
terminal->cursorY++;
|
|
if (terminal->cursorY > terminal->rows) {
|
|
terminal->cursorY--;
|
|
//***TODO*** Scroll.
|
|
termWrite("Unimplemented render scroll\n");
|
|
}
|
|
}
|
|
terminalCursorMove(terminal, terminal->cursorX, terminal->cursorY);
|
|
break;
|
|
|
|
} // switch (c)
|
|
|
|
} // inEscape
|
|
}
|
|
|
|
|
|
void terminalCursorMove(TerminalT *terminal, uint16_t col, uint16_t row) {
|
|
|
|
// Clamp X.
|
|
if (col < 1) {
|
|
terminal->cursorX = 1;
|
|
} else {
|
|
if (col > terminal->cols) {
|
|
terminal->cursorX = terminal->cols;
|
|
} else {
|
|
terminal->cursorX = col;
|
|
}
|
|
}
|
|
|
|
// Clamp Y.
|
|
if (row < 1) {
|
|
terminal->cursorY = 1;
|
|
} else {
|
|
if (row > terminal->rows) {
|
|
terminal->cursorY = terminal->rows;
|
|
} else {
|
|
terminal->cursorY = row;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void terminalDel(WidgetT **widget) {
|
|
TerminalT *t = (TerminalT *)*widget;
|
|
uint16_t x;
|
|
|
|
terminalSequenceReset(t);
|
|
|
|
for (x=0; x<t->cols; x++) {
|
|
free(t->cells[x]);
|
|
}
|
|
free(t->cells);
|
|
}
|
|
|
|
|
|
static void terminalFocusEvent(WidgetT *widget, uint8_t focused) {
|
|
TerminalT *t = (TerminalT *)widget;
|
|
uint16_t x;
|
|
uint16_t y;
|
|
|
|
(void)focused;
|
|
|
|
// When focus changes, we need to mark ALL the cells dirty so they get redrawn when the window is redrawn.
|
|
for (y=0; y<t->rows; y++) {
|
|
for (x=0; x<t->cols; x++) {
|
|
TERMINAL_CELL_SET_FLAG(t, x, y, TERMINAL_FLAG_DIRTY);
|
|
}
|
|
}
|
|
|
|
GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
|
|
}
|
|
|
|
|
|
void terminalForegroundSet(TerminalT *terminal, uint8_t color) {
|
|
terminal->foreground = color;
|
|
terminal->bold = (color > 7);
|
|
}
|
|
|
|
|
|
WidgetT *terminalInit(WidgetT *widget, uint16_t cols, uint16_t rows) {
|
|
TerminalT *t = (TerminalT *)widget;
|
|
uint16_t x;
|
|
uint16_t y;
|
|
|
|
t->base.delMethod = terminalDel;
|
|
t->base.focusMethod = terminalFocusEvent;
|
|
t->base.paintMethod = terminalPaint;
|
|
t->base.keyboardEventMethod = terminalKeyboardEvent;
|
|
t->base.mouseEventMethod = terminalMouseEvent;
|
|
t->font = __guiFont; // Default font. Also see terminalNew.
|
|
t->cols = cols;
|
|
t->rows = rows;
|
|
t->cursorX = 1;
|
|
t->cursorY = 1;
|
|
t->saveX = 1;
|
|
t->saveY = 1;
|
|
t->bold = 0;
|
|
t->blink = 0;
|
|
t->reverse = 0;
|
|
t->background = TERMINAL_COLOR_BLACK;
|
|
t->foreground = TERMINAL_COLOR_LIGHT_GRAY;
|
|
t->escFound = 0;
|
|
t->inEscape = 0;
|
|
t->numberIndex = 0;
|
|
t->number[0] = 0;
|
|
t->parameters = NULL;
|
|
|
|
// Allocate space for column data.
|
|
t->cells = (CellT **)malloc(sizeof(CellT *) * cols);
|
|
if (!t->cells) return NULL;
|
|
// Allocate space for row data.
|
|
for (x=0; x<cols; x++) {
|
|
t->cells[x] = (CellT *)malloc(sizeof(CellT) * rows);
|
|
if (!t->cells[x]) {
|
|
for (y=0; y<x; y++) {
|
|
free(t->cells[y]);
|
|
}
|
|
free(t->cells);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// Set up default palette.
|
|
t->palette[TERMINAL_COLOR_BLACK] = vbeColorMake( 0, 0, 0);
|
|
t->palette[TERMINAL_COLOR_BLUE] = vbeColorMake(170, 0, 0);
|
|
t->palette[TERMINAL_COLOR_GREEN] = vbeColorMake( 0, 170, 0);
|
|
t->palette[TERMINAL_COLOR_CYAN] = vbeColorMake(170, 85, 0);
|
|
t->palette[TERMINAL_COLOR_RED] = vbeColorMake( 0, 0, 170);
|
|
t->palette[TERMINAL_COLOR_MAGENTA] = vbeColorMake(170, 0, 170);
|
|
t->palette[TERMINAL_COLOR_BROWN] = vbeColorMake( 0, 170, 170);
|
|
t->palette[TERMINAL_COLOR_LIGHT_GRAY] = vbeColorMake(170, 170, 170);
|
|
t->palette[TERMINAL_COLOR_DARK_GRAY] = vbeColorMake( 85, 85, 85);
|
|
t->palette[TERMINAL_COLOR_BRIGHT_BLUE] = vbeColorMake(255, 85, 85);
|
|
t->palette[TERMINAL_COLOR_BRIGHT_GREEN] = vbeColorMake( 85, 255, 85);
|
|
t->palette[TERMINAL_COLOR_BRIGHT_CYAN] = vbeColorMake(255, 255, 85);
|
|
t->palette[TERMINAL_COLOR_BRIGHT_RED] = vbeColorMake( 85, 85, 255);
|
|
t->palette[TERMINAL_COLOR_BRIGHT_MAGENTA] = vbeColorMake(255, 85, 255);
|
|
t->palette[TERMINAL_COLOR_YELLOW] = vbeColorMake( 85, 255, 255);
|
|
t->palette[TERMINAL_COLOR_WHITE] = vbeColorMake(255, 255, 255);
|
|
|
|
// Default attributes is gray on black, no bold, no blink, and dirty.
|
|
for (y=0; y<rows; y++) {
|
|
for (x=0; x<cols; x++) {
|
|
t->cells[x][y].character = ' ';
|
|
t->cells[x][y].background = TERMINAL_COLOR_BLACK;
|
|
t->cells[x][y].foreground = TERMINAL_COLOR_LIGHT_GRAY;
|
|
t->cells[x][y].flags = 0;
|
|
TERMINAL_CELL_SET_FLAG(t, x, y, TERMINAL_FLAG_DIRTY);
|
|
}
|
|
}
|
|
|
|
return widget;
|
|
}
|
|
|
|
|
|
static int16_t terminalIntConvert(char *number, int16_t defaultValue) {
|
|
char *end = NULL;
|
|
int16_t result = (int16_t)strtol(number, &end, 10);
|
|
|
|
if (end == NULL || number[0] == 0) result = defaultValue;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static void terminalKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt) {
|
|
|
|
}
|
|
|
|
|
|
static void terminalMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event) {
|
|
TerminalT *t = (TerminalT *)widget;
|
|
|
|
(void)mouse;
|
|
|
|
// Fire callback on mouse up.
|
|
if (event == MOUSE_EVENT_LEFT_UP) {
|
|
|
|
}
|
|
}
|
|
|
|
|
|
TerminalT *terminalNew(uint16_t x, uint16_t y, uint16_t cols, uint16_t rows) {
|
|
TerminalT *terminal = (TerminalT *)malloc(sizeof(TerminalT));
|
|
WidgetT *widget = NULL;
|
|
uint16_t w;
|
|
uint16_t h;
|
|
|
|
if (!terminal) return NULL;
|
|
|
|
// Default font. Also see terminalInit.
|
|
w = fontWidthGet(__guiFont) * cols;
|
|
h = fontHeightGet(__guiFont) * rows;
|
|
|
|
widget = widgetInit(W(terminal), MAGIC_TERMINAL, x, y, w, h, 0, 0, 0, 0);
|
|
if (!widget) {
|
|
free(terminal);
|
|
return NULL;
|
|
}
|
|
|
|
terminal = (TerminalT *)terminalInit((WidgetT *)terminal, cols, rows);
|
|
if (!terminal) {
|
|
free(terminal);
|
|
return NULL;
|
|
}
|
|
|
|
return terminal;
|
|
}
|
|
|
|
|
|
static void terminalPaint(WidgetT *widget, uint8_t enabled, RectT pos) {
|
|
TerminalT *t = (TerminalT *)widget;
|
|
uint16_t x;
|
|
uint16_t y;
|
|
uint16_t xp;
|
|
uint16_t yp;
|
|
char c[2];
|
|
|
|
c[1] = 0;
|
|
|
|
yp = pos.y;
|
|
for (y=0; y<t->rows; y++) {
|
|
xp = pos.x;
|
|
for (x=0; x<t->cols; x++) {
|
|
if (TERMINAL_CELL_GET_FLAG(t, x, y, TERMINAL_FLAG_DIRTY)) {
|
|
c[0] = t->cells[x][y].character;
|
|
fontRender(t->font, c, t->palette[t->cells[x][y].foreground], t->palette[t->cells[x][y].background], xp, yp);
|
|
TERMINAL_CELL_CLEAR_FLAG(t, x, y, TERMINAL_FLAG_DIRTY);
|
|
}
|
|
xp += fontWidthGet(t->font);
|
|
}
|
|
yp += fontHeightGet(t->font);
|
|
}
|
|
}
|
|
|
|
|
|
void terminalScreenClear(TerminalT *terminal) {
|
|
uint16_t x;
|
|
uint16_t y;
|
|
|
|
// Does not move cursor.
|
|
|
|
for (y=0; y<terminal->rows; y++) {
|
|
for (x=0; x<terminal->cols; x++) {
|
|
terminal->cells[x][y].character = ' ';
|
|
TERMINAL_CELL_CLEAR_FLAG(terminal, x, y, TERMINAL_FLAG_BLINK_SLOW);
|
|
TERMINAL_CELL_CLEAR_FLAG(terminal, x, y, TERMINAL_FLAG_BLINK_FAST);
|
|
TERMINAL_CELL_SET_FLAG(terminal, x, y, TERMINAL_FLAG_DIRTY);
|
|
}
|
|
}
|
|
GUI_SET_FLAG((WidgetT *)terminal, WIDGET_FLAG_DIRTY);
|
|
|
|
}
|
|
|
|
|
|
static void terminalSequenceReset(TerminalT *terminal) {
|
|
char *parms = NULL;
|
|
|
|
terminal->escFound = 0;
|
|
terminal->inEscape = 0;
|
|
terminal->number[0] = 0;
|
|
terminal->numberIndex = 0;
|
|
|
|
if (terminal->parameters != NULL) {
|
|
while (arrlen(terminal->parameters) > 0) {
|
|
parms = arrpop(terminal->parameters);
|
|
free(parms);
|
|
}
|
|
terminal->parameters = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void terminalStringPrint(TerminalT *terminal, char *string) {
|
|
uint16_t i;
|
|
|
|
for (i=0; i<strlen(string); i++) {
|
|
terminalCharPrint(terminal, string[i]);
|
|
}
|
|
}
|
|
|
|
|