Initial commit
This commit is contained in:
parent
02deaf49ba
commit
5b40a9b083
@ -10,6 +10,11 @@ Physical recreation of a video terminal device for connecting to a serial consol
|
|||||||
- [TinTTY](https://github.com/unframework/tintty) for VT100 emulation
|
- [TinTTY](https://github.com/unframework/tintty) for VT100 emulation
|
||||||
- [ESP32Lib](https://github.com/bitluni/ESP32Lib/tree/development) for composite video output
|
- [ESP32Lib](https://github.com/bitluni/ESP32Lib/tree/development) for composite video output
|
||||||
|
|
||||||
|
## Building
|
||||||
|
1. Connect up the hardware (TODO).
|
||||||
|
2. Get the Arduino IDE, Install ESP32 board package, Install ESP32Lib library.
|
||||||
|
3. Compile and upload the sketch in `video-terminal/video-terminal.ino`
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
- [LK201 Interface](http://www.netbsd.org/docs/Hardware/Machines/DEC/lk201.html) for keyboard specification
|
- [LK201 Interface](http://www.netbsd.org/docs/Hardware/Machines/DEC/lk201.html) for keyboard specification
|
||||||
|
|
||||||
|
119
video-terminal/Display.h
Normal file
119
video-terminal/Display.h
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
CONNECTION from ESP32 to a composite video TV
|
||||||
|
|
||||||
|
A) voltageDivider = false; B) voltageDivider = true
|
||||||
|
|
||||||
|
55 shades 179 shades
|
||||||
|
|
||||||
|
ESP32 TV ESP32 TV
|
||||||
|
-----+ -----+ ____ 100 ohm
|
||||||
|
G|- G|---|____|+
|
||||||
|
pin25|--------- Comp pin25|---|____|+--------- Comp
|
||||||
|
pin26|- pin26|- 220 ohm
|
||||||
|
| |
|
||||||
|
| |
|
||||||
|
-----+ -----+
|
||||||
|
|
||||||
|
Connect pin 25 or 26
|
||||||
|
|
||||||
|
C) R–2R resistor ladder; D) unequal rungs ladder
|
||||||
|
|
||||||
|
55 shades up to 254 shades?
|
||||||
|
|
||||||
|
ESP32 TV ESP32 TV
|
||||||
|
-----+ -----+ ____
|
||||||
|
G|-+_____ G|---|____|
|
||||||
|
pinA0|-| R2R |- Comp pinA0|---|____|+--------- Comp
|
||||||
|
pinA1|-| | pinA1|---|____|
|
||||||
|
pinA2|-| | ...|
|
||||||
|
...|-|_____| |
|
||||||
|
-----+ -----+
|
||||||
|
|
||||||
|
Connect pins of your choice (A0...A8=any pins).
|
||||||
|
Custom ladders can be used by tweaking colorMinValue and colorMaxValue
|
||||||
|
*/
|
||||||
|
#include <ESP32Lib.h>
|
||||||
|
#include <Ressources/Font6x8.h>
|
||||||
|
//#include <Ressources/CodePage437_8x8.h>
|
||||||
|
//#include <Ressources/CodePage437_8x14.h>
|
||||||
|
//#include <Ressources/CodePage437_8x16.h>
|
||||||
|
//#include <Ressources/CodePage437_8x19.h>
|
||||||
|
#include <Ressources/CodePage437_9x16.h>
|
||||||
|
|
||||||
|
//pin configuration for DAC
|
||||||
|
const int outputPin = 25;
|
||||||
|
// A) B)
|
||||||
|
CompositeGrayDAC videodisplay;
|
||||||
|
// C) D)
|
||||||
|
//CompositeGrayLadder videodisplay;
|
||||||
|
|
||||||
|
class Display {
|
||||||
|
|
||||||
|
public:
|
||||||
|
// view(able) size
|
||||||
|
int vw = 350;
|
||||||
|
int vh = 235;
|
||||||
|
|
||||||
|
// offsets (to fix overscan, depends on your display)
|
||||||
|
int ow = 10;
|
||||||
|
int oh = 26;
|
||||||
|
|
||||||
|
// display size
|
||||||
|
int w = 365; // vw + 2*ow
|
||||||
|
int h = 275; // vh + 2*oh
|
||||||
|
|
||||||
|
#define FONT CodePage437_9x16 // for options, see the Resources includes ^^^
|
||||||
|
int font_w = 9;
|
||||||
|
int font_h = 16;
|
||||||
|
|
||||||
|
int cols = vw / font_w;
|
||||||
|
int rows = vh / font_h;
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
// Composite video init, see options in the header ^^^
|
||||||
|
// A)
|
||||||
|
//videodisplay.init(CompMode::MODEPAL288P, 25, false);
|
||||||
|
// B)
|
||||||
|
videodisplay.init(CompMode::MODEPAL288P, 25, true);
|
||||||
|
videodisplay.clear();
|
||||||
|
|
||||||
|
videodisplay.xres = w;
|
||||||
|
videodisplay.yres = h;
|
||||||
|
|
||||||
|
videodisplay.setFont(FONT);
|
||||||
|
|
||||||
|
/*/ view area test
|
||||||
|
videodisplay.rect(ow, oh, vw, vh, 50);
|
||||||
|
/**/
|
||||||
|
}
|
||||||
|
|
||||||
|
void scroll(int d) {
|
||||||
|
videodisplay.scroll(d, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fill_rect(int x, int y, int w, int h, int color) {
|
||||||
|
videodisplay.fillRect(x, y, w, h, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pixel(int x, int y, int color) {
|
||||||
|
videodisplay.dotFast(x, y, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_character(int col, int row, int fg_color, int bg_color, char character) {
|
||||||
|
int x = ow + col*font_w;
|
||||||
|
int y = oh + row*font_h;
|
||||||
|
videodisplay.setCursor(x, y);
|
||||||
|
videodisplay.frontColor = fg_color;
|
||||||
|
videodisplay.backColor = bg_color;
|
||||||
|
videodisplay.print(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_display_width() { return w; }
|
||||||
|
int get_display_height() { return h; }
|
||||||
|
int get_view_width() { return vw; }
|
||||||
|
int get_view_height() { return vh; }
|
||||||
|
int get_view_width_offset() { return ow; }
|
||||||
|
int get_view_height_offset() { return oh; }
|
||||||
|
|
||||||
|
};
|
216
video-terminal/Keyboard.h
Normal file
216
video-terminal/Keyboard.h
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
/* SOURCE: http://www.netbsd.org/docs/Hardware/Machines/DEC/lk201.html */
|
||||||
|
|
||||||
|
/**********************************************************************/
|
||||||
|
|
||||||
|
/* requires LED number data */
|
||||||
|
#define LK_LED_ENABLE 0x13 /* light LED */
|
||||||
|
#define LK_LED_DISABLE 0x11 /* turn off LED */
|
||||||
|
|
||||||
|
#define LED_WAIT 0x81 /* Wait LED */
|
||||||
|
#define LED_COMP 0x82 /* Compose LED */
|
||||||
|
#define LED_LOCK 0x84 /* Lock LED */
|
||||||
|
#define LED_HOLD 0x88 /* Hold Screen LED */
|
||||||
|
#define LED_ALL 0x8F /* All LED's */
|
||||||
|
|
||||||
|
/**********************************************************************/
|
||||||
|
|
||||||
|
/* Requires volume data byte */
|
||||||
|
#define LK_CL_ENABLE 0x1B /* keyclick enable. Requires volume */
|
||||||
|
/* byte. This does not affect the */
|
||||||
|
/* SHIFT key. The CTRL key requires */
|
||||||
|
/* LK_CL_ENABLE and LK_CCL_ENABLE to */
|
||||||
|
/* have been sent before it clicks. */
|
||||||
|
/* All other keys are only controlled */
|
||||||
|
/* by LK_CL_ENABLE. */
|
||||||
|
|
||||||
|
#define LK_CCL_ENABLE 0xBB /* Enable keyclicks for the CTRL key. */
|
||||||
|
/* The CTRL keyclick volume is set to */
|
||||||
|
/* be the same as the rest of the keys */
|
||||||
|
/* LK_CCL_ENABLE sets a flag in the */
|
||||||
|
/* keyboard with is logically AND'ed */
|
||||||
|
/* with the LK_CL_ENABLE flag to enable*/
|
||||||
|
/* CTRL key keyclicks. */
|
||||||
|
|
||||||
|
#define LK_CL_DISABLE 0x99 /* keyclick disable */
|
||||||
|
#define LK_CCL_DISABLE 0xB9 /* CTRL key keyclick disable */
|
||||||
|
|
||||||
|
#define LK_SOUND_CLICK 0x9F /* causes the LK201 to sound a keyclick*/
|
||||||
|
|
||||||
|
/* max volume is 0, lowest is 0x7 */
|
||||||
|
#define LK_PARAM_VOLUME(v) (0x80|((v)&0x7))
|
||||||
|
|
||||||
|
/**********************************************************************/
|
||||||
|
|
||||||
|
/* requires bell volume data */
|
||||||
|
#define LK_BELL_ENABLE 0x23 /* enable the keyboard bell. Requires */
|
||||||
|
/* volume data byte. */
|
||||||
|
|
||||||
|
#define LK_BELL_DISABLE 0xA1 /* disable the keyboard bell. */
|
||||||
|
|
||||||
|
#define LK_RING_BELL 0xA7 /* ring the keyboard bell */
|
||||||
|
|
||||||
|
/* max volume is 0, lowest is 0x7 */
|
||||||
|
#define LK_PARAM_VOLUME(v) (0x80|((v)&0x7))
|
||||||
|
|
||||||
|
/**********************************************************************/
|
||||||
|
|
||||||
|
#define LK_UPDOWN 0x86
|
||||||
|
#define LK_AUTODOWN 0x82
|
||||||
|
#define LK_DOWN 0x80
|
||||||
|
|
||||||
|
#define LK_CMD_MODE(m,div) ((m)|((div)<<3))
|
||||||
|
|
||||||
|
#define LK_MODECHG_ACK 0xBA /* sent by the keyboard to acknowledge a */
|
||||||
|
/* successful mode change. */
|
||||||
|
|
||||||
|
|
||||||
|
#define LK_PFX_KEYDOWN 0xB9 /* indicates that the next byte is a key- */
|
||||||
|
/* code for a key already down in a */
|
||||||
|
/* division that has been changed to */
|
||||||
|
/* LK_UPDOWN. I think this means that if */
|
||||||
|
/* for example, the 'a' key is in LK_DOWN */
|
||||||
|
/* mode and the key is being held down and*/
|
||||||
|
/* division 1 is switched to LK_UPDOWN */
|
||||||
|
/* mode, the keyboard will produce the */
|
||||||
|
/* byte LK_PFX_KEYDOWN followed by 0xC2 */
|
||||||
|
/* (KEY_A). */
|
||||||
|
|
||||||
|
#define LK_CMD_RPT_TO_DOWN 0xD9 /* This command causes all divisions which */
|
||||||
|
/* are programmed for LK_AUTODOWN mode to */
|
||||||
|
/* be switched to LK_DOWN mode. */
|
||||||
|
|
||||||
|
|
||||||
|
#define LK_CMD_ENB_RPT 0xE3 /* enables auto repeat on the keys */
|
||||||
|
/* which are in LK_AUTODOWN mode */
|
||||||
|
|
||||||
|
#define LK_CMD_DIS_RPT 0xE1 /* disables auto repeat on all keys, but */
|
||||||
|
/* does not change the mode that the */
|
||||||
|
/* divisions are programmed to. */
|
||||||
|
|
||||||
|
#define LK_CMD_TMP_NORPT 0xD1 /* temporary auto repeat disable. This */
|
||||||
|
/* command disables auto repeat for the key*/
|
||||||
|
/* which is currently pressed down. Auto */
|
||||||
|
/* repeat is re-enabled when another key is*/
|
||||||
|
/* pressed. */
|
||||||
|
|
||||||
|
|
||||||
|
#define LK_INPUT_ERROR 0xB6 /* sent by the keyboard if it receives an */
|
||||||
|
/* invalid command. */
|
||||||
|
|
||||||
|
#define LK_NO_ERROR 0x00 /* No Error */
|
||||||
|
#define LK_KDOWN_ERROR 0x3D /* Key down on powerup error */
|
||||||
|
#define LK_POWER_ERROR 0x3E /* Keyboard failure on pwrup tst */
|
||||||
|
|
||||||
|
#define LK_ALLUP 0xB3
|
||||||
|
|
||||||
|
/**********************************************************************/
|
||||||
|
|
||||||
|
bool mod_shift = false;
|
||||||
|
bool mod_ctrl = false;
|
||||||
|
#define KB_CHAR(key_base, key_shift) c = ((mod_shift) ? (key_shift) : (key_base));
|
||||||
|
|
||||||
|
void keyboard_handle_key(int key)
|
||||||
|
{
|
||||||
|
int c = -1;
|
||||||
|
switch (key) {
|
||||||
|
/* Key Division 191-255 */
|
||||||
|
case 191: KB_CHAR('`', '~'); break; // (xbf): KEY_TILDE
|
||||||
|
case 192: KB_CHAR('1', '!'); break; // (xc0): KEY_TR_1
|
||||||
|
case 193: KB_CHAR('q', 'Q'); break; // (xc1): KEY_Q
|
||||||
|
case 194: KB_CHAR('a', 'A'); break; // (xc2): KEY_A
|
||||||
|
case 195: KB_CHAR('z', 'Z'); break; // (xc3): KEY_Z
|
||||||
|
case 197: KB_CHAR('2', '@'); break; // (xc5): KEY_TR_2
|
||||||
|
case 198: KB_CHAR('w', 'W'); break; // (xc6): KEY_W
|
||||||
|
case 199: KB_CHAR('s', 'S'); break; // (xc7): KEY_S
|
||||||
|
case 200: KB_CHAR('x', 'X'); break; // (xc8): KEY_X
|
||||||
|
case 201: KB_CHAR('<', '>'); break; // (xc9): KEY_LANGLE_RANGLE
|
||||||
|
case 203: KB_CHAR('3', '#'); break; // (xcb): KEY_TR_3
|
||||||
|
case 204: KB_CHAR('e', 'E'); break; // (xcc): KEY_E
|
||||||
|
case 205: KB_CHAR('d', 'D'); break; // (xcd): KEY_D
|
||||||
|
case 206: KB_CHAR('c', 'C'); break; // (xce): KEY_C
|
||||||
|
case 208: KB_CHAR('4', '$'); break; // (xd0): KEY_TR_4
|
||||||
|
case 209: KB_CHAR('r', 'R'); break; // (xd1): KEY_R
|
||||||
|
case 210: KB_CHAR('f', 'F'); break; // (xd2): KEY_F
|
||||||
|
case 211: KB_CHAR('v', 'V'); break; // (xd3): KEY_V
|
||||||
|
case 212: KB_CHAR(' ', ' '); break; // (xd4): KEY_SPACE
|
||||||
|
case 214: KB_CHAR('5', '%'); break; // (xd6): KEY_TR_5
|
||||||
|
case 215: KB_CHAR('t', 'T'); break; // (xd7): KEY_T
|
||||||
|
case 216: KB_CHAR('g', 'G'); break; // (xd8): KEY_G
|
||||||
|
case 217: KB_CHAR('b', 'B'); break; // (xd9): KEY_B
|
||||||
|
case 219: KB_CHAR('6', '^'); break; // (xdb): KEY_TR_6
|
||||||
|
case 220: KB_CHAR('y', 'Y'); break; // (xdc): KEY_Y
|
||||||
|
case 221: KB_CHAR('h', 'H'); break; // (xdd): KEY_H
|
||||||
|
case 222: KB_CHAR('n', 'N'); break; // (xde): KEY_N
|
||||||
|
case 224: KB_CHAR('7', '&'); break; // (xe0): KEY_TR_7
|
||||||
|
case 225: KB_CHAR('u', 'U'); break; // (xe1): KEY_U
|
||||||
|
case 226: KB_CHAR('j', 'J'); break; // (xe2): KEY_J
|
||||||
|
case 227: KB_CHAR('m', 'M'); break; // (xe3): KEY_M
|
||||||
|
case 229: KB_CHAR('8', '*'); break; // (xe5): KEY_TR_8
|
||||||
|
case 230: KB_CHAR('i', 'I'); break; // (xe6): KEY_I
|
||||||
|
case 231: KB_CHAR('k', 'K'); break; // (xe7): KEY_K
|
||||||
|
case 232: KB_CHAR(',', '<'); break; // (xe8): KEY_COMMA
|
||||||
|
case 234: KB_CHAR('9', '('); break; // (xea): KEY_TR_9
|
||||||
|
case 235: KB_CHAR('o', 'O'); break; // (xeb): KEY_O
|
||||||
|
case 236: KB_CHAR('l', 'L'); break; // (xec): KEY_L
|
||||||
|
case 237: KB_CHAR('.', '>'); break; // (xed): KEY_PERIOD
|
||||||
|
case 239: KB_CHAR('0', ')'); break; // (xef): KEY_TR_0
|
||||||
|
case 240: KB_CHAR('p', 'P'); break; // (xf0): KEY_P
|
||||||
|
case 242: KB_CHAR(';', ':'); break; // (xf2): KEY_SEMICOLON
|
||||||
|
case 243: KB_CHAR('/', '?'); break; // (xf3): KEY_QMARK
|
||||||
|
case 245: KB_CHAR('=', '+'); break; // (xf5): KEY_PLUS
|
||||||
|
case 246: KB_CHAR(']', '}'); break; // (xf6): KEY_RBRACE
|
||||||
|
case 247: KB_CHAR('\\', '|'); break; // (xf7): KEY_VBAR
|
||||||
|
case 249: KB_CHAR('-', '_'); break; // (xf9): KEY_UBAR
|
||||||
|
case 250: KB_CHAR('[', '{'); break; // (xfa): KEY_LBRACE
|
||||||
|
case 251: KB_CHAR('\'', '"'); break; // (xfb): KEY_QUOTE
|
||||||
|
/* Key Division 2: 145 - 165 */
|
||||||
|
/*146 (x92): KEY_KP_0
|
||||||
|
148 (x94): KEY_KP_PERIOD
|
||||||
|
149 (x95): KEY_KP_ENTER
|
||||||
|
150 (x96): KEY_KP_1
|
||||||
|
151 (x97): KEY_KP_2
|
||||||
|
152 (x98): KEY_KP_3
|
||||||
|
153 (x99): KEY_KP_4
|
||||||
|
154 (x9a): KEY_KP_5
|
||||||
|
155 (x9b): KEY_KP_6
|
||||||
|
156 (x9c): KEY_KP_COMMA
|
||||||
|
157 (x9d): KEY_KP_7
|
||||||
|
158 (x9e): KEY_KP_8
|
||||||
|
159 (x9f): KEY_KP_9
|
||||||
|
160 (xa0): KEY_KP_HYPHEN
|
||||||
|
161 (xa1): KEY_KP_PF1
|
||||||
|
162 (xa2): KEY_KP_PF2
|
||||||
|
163 (xa3): KEY_KP_PF3
|
||||||
|
164 (xa4): KEY_KP_PF4*/
|
||||||
|
/* Key Division 3: 188 - 188 */
|
||||||
|
case 188: c = 0177; Serial.print("<DELETE>"); break; // (xbc): KEY_DELETE
|
||||||
|
/* Key Division 4: 189 - 190 */
|
||||||
|
case 189: c = '\n'; break; // (xbd): KEY_RETURN
|
||||||
|
case 190: c = '\t'; break; // (xbe): KEY_TAB
|
||||||
|
/* Key Division 5: (176 - 178) */
|
||||||
|
/*176 (xb0): KEY_LOCK
|
||||||
|
177 (xb1): KEY_META*/
|
||||||
|
/* Key Division 6: (173 - 175) */
|
||||||
|
case 174: mod_shift = !mod_shift; Serial.print("<SHIFT "); Serial.print(mod_shift); Serial.print(">"); break; // (xae): KEY_SHIFT
|
||||||
|
case 175: mod_ctrl = !mod_ctrl; Serial.print("<CTRL "); Serial.print(mod_ctrl); Serial.print(">"); break; // (xaf): KEY_CTRL*/
|
||||||
|
/* Key Division 7: (166 - 168) */
|
||||||
|
case 167: SerialTty.print("\033[D"); Serial.print("LEFT "); break; // (xa7): KEY_LEFT
|
||||||
|
case 168: SerialTty.print("\033[C"); Serial.print("RIGHT "); break; // (xa8): KEY_RIGHT
|
||||||
|
/* Key Division 8: (169 - 172) */
|
||||||
|
case 169: SerialTty.print("\033[B"); Serial.print("DOWN "); break; // (xa9): KEY_DOWN
|
||||||
|
case 170: SerialTty.print("\033[A"); Serial.print("UP "); break; // (xaa): KEY_UP
|
||||||
|
case 171: break; // (xab): KEY_R_SHIFT
|
||||||
|
|
||||||
|
case LK_ALLUP:
|
||||||
|
mod_shift = false;
|
||||||
|
mod_ctrl = false;
|
||||||
|
Serial.print("<ALLUP>");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Serial.print(key); Serial.print(' ');
|
||||||
|
}
|
||||||
|
if (c != -1) {
|
||||||
|
Serial.print((char) c);
|
||||||
|
SerialTty.print((char) c);
|
||||||
|
}
|
||||||
|
}
|
274
video-terminal/font454.h
Normal file
274
video-terminal/font454.h
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
|
||||||
|
|
||||||
|
#ifndef FONT454_H
|
||||||
|
#define FONT454_H
|
||||||
|
|
||||||
|
//#include <avr/io.h>
|
||||||
|
//#include <avr/pgmspace.h>
|
||||||
|
|
||||||
|
// generated from https://bitbucket.org/thesheep/font454
|
||||||
|
|
||||||
|
// 4-pixel-wide matrix of 0-3 intensity, packed AABBCCDD
|
||||||
|
static const unsigned char font454[] PROGMEM = {
|
||||||
|
// default ASCII character set
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x30, 0x30, 0x20, 0x00, 0x30, 0x00,
|
||||||
|
0xcc, 0x88, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x88, 0xfc, 0x88, 0xfc, 0x88, 0x00,
|
||||||
|
0x20, 0xb8, 0xf0, 0x3c, 0xb8, 0x00,
|
||||||
|
0xcc, 0x08, 0x30, 0x80, 0xcc, 0x00,
|
||||||
|
0xb0, 0xcc, 0x34, 0xcc, 0xbc, 0x00,
|
||||||
|
0x30, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x70, 0x80, 0xc0, 0x80, 0x70, 0x00,
|
||||||
|
0x34, 0x08, 0x0c, 0x08, 0x34, 0x00,
|
||||||
|
0x00, 0x98, 0x74, 0x98, 0x00, 0x00,
|
||||||
|
0x00, 0x30, 0xfc, 0x30, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x30, 0x80, 0x00,
|
||||||
|
0x00, 0x00, 0xfc, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
|
||||||
|
0x0c, 0x18, 0x30, 0x90, 0xc0, 0x00,
|
||||||
|
0x78, 0xcc, 0xcc, 0xcc, 0xb4, 0x00,
|
||||||
|
0x30, 0xb0, 0x30, 0x30, 0x30, 0x00,
|
||||||
|
0xe4, 0x0c, 0x74, 0x80, 0xfc, 0x00,
|
||||||
|
0xb8, 0x0c, 0x34, 0x0c, 0xb8, 0x00,
|
||||||
|
0x30, 0x90, 0xcc, 0xfc, 0x0c, 0x00,
|
||||||
|
0xfc, 0xc0, 0xf8, 0x0c, 0xf8, 0x00,
|
||||||
|
0x78, 0xc0, 0xb8, 0xcc, 0xb8, 0x00,
|
||||||
|
0xfc, 0x08, 0x30, 0x80, 0xc0, 0x00,
|
||||||
|
0xb8, 0xcc, 0x74, 0xcc, 0xb8, 0x00,
|
||||||
|
0xb8, 0xcc, 0xbc, 0x0c, 0xb4, 0x00,
|
||||||
|
0x00, 0x30, 0x00, 0x30, 0x00, 0x00,
|
||||||
|
0x00, 0x30, 0x00, 0x30, 0x80, 0x00,
|
||||||
|
0x0c, 0x30, 0xc0, 0x30, 0x0c, 0x00,
|
||||||
|
0x00, 0xfc, 0x00, 0xfc, 0x00, 0x00,
|
||||||
|
0xc0, 0x30, 0x0c, 0x30, 0xc0, 0x00,
|
||||||
|
0xb4, 0x0c, 0x34, 0x00, 0x30, 0x00,
|
||||||
|
0x78, 0x8c, 0xcc, 0x80, 0x6c, 0x00,
|
||||||
|
0x74, 0x88, 0xcc, 0xec, 0xcc, 0x00,
|
||||||
|
0xf4, 0xcc, 0xf4, 0xcc, 0xf4, 0x00,
|
||||||
|
0x7c, 0x80, 0xc0, 0x80, 0x7c, 0x00,
|
||||||
|
0xe4, 0xc8, 0xcc, 0xc8, 0xe4, 0x00,
|
||||||
|
0xfc, 0xc0, 0xf0, 0xc0, 0xfc, 0x00,
|
||||||
|
0xfc, 0xc0, 0xf0, 0xc0, 0xc0, 0x00,
|
||||||
|
0x7c, 0x80, 0xc0, 0x8c, 0x7c, 0x00,
|
||||||
|
0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0x00,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x00,
|
||||||
|
0x0c, 0x0c, 0x0c, 0xcc, 0xb8, 0x00,
|
||||||
|
0xcc, 0xc8, 0xe0, 0xc8, 0xcc, 0x00,
|
||||||
|
0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0x00,
|
||||||
|
0x88, 0xec, 0xfc, 0xdc, 0xcc, 0x00,
|
||||||
|
0x8c, 0xdc, 0xec, 0xdc, 0xc8, 0x00,
|
||||||
|
0x74, 0x88, 0xcc, 0x88, 0x74, 0x00,
|
||||||
|
0xe4, 0xcc, 0xcc, 0xe4, 0xc0, 0x00,
|
||||||
|
0x74, 0x88, 0xcc, 0x8c, 0x7c, 0x00,
|
||||||
|
0xe4, 0xcc, 0xcc, 0xe4, 0xcc, 0x00,
|
||||||
|
0xb8, 0xc0, 0xb8, 0x0c, 0xb8, 0x00,
|
||||||
|
0xfc, 0x30, 0x30, 0x30, 0x30, 0x00,
|
||||||
|
0xcc, 0xcc, 0xcc, 0x8c, 0x68, 0x00,
|
||||||
|
0xcc, 0xcc, 0xcc, 0x98, 0x30, 0x00,
|
||||||
|
0xcc, 0xcc, 0xdc, 0xfc, 0xec, 0x00,
|
||||||
|
0xcc, 0x88, 0x20, 0x88, 0xcc, 0x00,
|
||||||
|
0xcc, 0x88, 0x64, 0x30, 0x30, 0x00,
|
||||||
|
0xfc, 0x08, 0x30, 0x80, 0xfc, 0x00,
|
||||||
|
0xf0, 0xc0, 0xc0, 0xc0, 0xf0, 0x00,
|
||||||
|
0xc0, 0x90, 0x30, 0x18, 0x0c, 0x00,
|
||||||
|
0x3c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00,
|
||||||
|
0x30, 0xdc, 0x88, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
|
||||||
|
0xc0, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x6c, 0x8c, 0x8c, 0x6c, 0x00,
|
||||||
|
0xc0, 0xe4, 0xc8, 0xc8, 0xe4, 0x00,
|
||||||
|
0x00, 0x6c, 0x80, 0x80, 0x6c, 0x00,
|
||||||
|
0x0c, 0x6c, 0x8c, 0x8c, 0x6c, 0x00,
|
||||||
|
0x00, 0x38, 0x8c, 0xe0, 0x7c, 0x00,
|
||||||
|
0x1c, 0x30, 0xb8, 0x30, 0x30, 0x30,
|
||||||
|
0x00, 0xe4, 0x88, 0x6c, 0x08, 0xe4,
|
||||||
|
0xc0, 0xe4, 0xc8, 0xcc, 0xcc, 0x00,
|
||||||
|
0x30, 0x00, 0x70, 0x30, 0x2c, 0x00,
|
||||||
|
0x0c, 0x00, 0x1c, 0x0c, 0xcc, 0xb8,
|
||||||
|
0xc0, 0xcc, 0xe0, 0xc8, 0xcc, 0x00,
|
||||||
|
0x70, 0x30, 0x30, 0x20, 0x18, 0x00,
|
||||||
|
0x00, 0xf4, 0xfc, 0xec, 0xcc, 0x00,
|
||||||
|
0x00, 0xe4, 0xc8, 0xcc, 0xcc, 0x00,
|
||||||
|
0x00, 0x74, 0x88, 0x88, 0x74, 0x00,
|
||||||
|
0x00, 0xe4, 0xc8, 0xc8, 0xe4, 0xc0,
|
||||||
|
0x00, 0x64, 0x88, 0x8c, 0x6c, 0x0c,
|
||||||
|
0x00, 0x8c, 0xe0, 0xc0, 0xc0, 0x00,
|
||||||
|
0x00, 0x6c, 0x90, 0x18, 0xf4, 0x00,
|
||||||
|
0x30, 0xb8, 0x30, 0x20, 0x18, 0x00,
|
||||||
|
0x00, 0xcc, 0xcc, 0x8c, 0x78, 0x00,
|
||||||
|
0x00, 0xcc, 0xcc, 0x98, 0x20, 0x00,
|
||||||
|
0x00, 0xcc, 0xdc, 0xfc, 0xec, 0x00,
|
||||||
|
0x00, 0xcc, 0x64, 0x64, 0xcc, 0x00,
|
||||||
|
0x00, 0xcc, 0xcc, 0x78, 0x08, 0xa4,
|
||||||
|
0x00, 0xfc, 0x18, 0x90, 0xfc, 0x00,
|
||||||
|
0x2c, 0x30, 0xd0, 0x30, 0x2c, 0x00,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0xe0, 0x30, 0x1c, 0x30, 0xe0, 0x00,
|
||||||
|
0xb0, 0xc0, 0x74, 0x0c, 0x38, 0x00,
|
||||||
|
0xec, 0x88, 0x88, 0x88, 0xec, 0x00,
|
||||||
|
|
||||||
|
// line-drawing character set
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x30, 0x30, 0x20, 0x00, 0x30, 0x00,
|
||||||
|
0xcc, 0x88, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x88, 0xfc, 0x88, 0xfc, 0x88, 0x00,
|
||||||
|
0x20, 0xb8, 0xf0, 0x3c, 0xb8, 0x00,
|
||||||
|
0xcc, 0x08, 0x30, 0x80, 0xcc, 0x00,
|
||||||
|
0xb0, 0xcc, 0x34, 0xcc, 0xbc, 0x00,
|
||||||
|
0x30, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x70, 0x80, 0xc0, 0x80, 0x70, 0x00,
|
||||||
|
0x34, 0x08, 0x0c, 0x08, 0x34, 0x00,
|
||||||
|
0x00, 0x98, 0x74, 0x98, 0x00, 0x00,
|
||||||
|
0x00, 0x30, 0xfc, 0x30, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x30, 0x80, 0x00,
|
||||||
|
0x00, 0x00, 0xfc, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
|
||||||
|
0x0c, 0x18, 0x30, 0x90, 0xc0, 0x00,
|
||||||
|
0x78, 0xcc, 0xcc, 0xcc, 0xb4, 0x00,
|
||||||
|
0x30, 0xb0, 0x30, 0x30, 0x30, 0x00,
|
||||||
|
0xe4, 0x0c, 0x74, 0x80, 0xfc, 0x00,
|
||||||
|
0xb8, 0x0c, 0x34, 0x0c, 0xb8, 0x00,
|
||||||
|
0x30, 0x90, 0xcc, 0xfc, 0x0c, 0x00,
|
||||||
|
0xfc, 0xc0, 0xf8, 0x0c, 0xf8, 0x00,
|
||||||
|
0x78, 0xc0, 0xb8, 0xcc, 0xb8, 0x00,
|
||||||
|
0xfc, 0x08, 0x30, 0x80, 0xc0, 0x00,
|
||||||
|
0xb8, 0xcc, 0x74, 0xcc, 0xb8, 0x00,
|
||||||
|
0xb8, 0xcc, 0xbc, 0x0c, 0xb4, 0x00,
|
||||||
|
0x00, 0x30, 0x00, 0x30, 0x00, 0x00,
|
||||||
|
0x00, 0x30, 0x00, 0x30, 0x80, 0x00,
|
||||||
|
0x0c, 0x30, 0xc0, 0x30, 0x0c, 0x00,
|
||||||
|
0x00, 0xfc, 0x00, 0xfc, 0x00, 0x00,
|
||||||
|
0xc0, 0x30, 0x0c, 0x30, 0xc0, 0x00,
|
||||||
|
0xb4, 0x0c, 0x34, 0x00, 0x30, 0x00,
|
||||||
|
0x78, 0x8c, 0xcc, 0x80, 0x6c, 0x00,
|
||||||
|
0x74, 0x88, 0xcc, 0xec, 0xcc, 0x00,
|
||||||
|
0xf4, 0xcc, 0xf4, 0xcc, 0xf4, 0x00,
|
||||||
|
0x7c, 0x80, 0xc0, 0x80, 0x7c, 0x00,
|
||||||
|
0xe4, 0xc8, 0xcc, 0xc8, 0xe4, 0x00,
|
||||||
|
0xfc, 0xc0, 0xf0, 0xc0, 0xfc, 0x00,
|
||||||
|
0xfc, 0xc0, 0xf0, 0xc0, 0xc0, 0x00,
|
||||||
|
0x7c, 0x80, 0xc0, 0x8c, 0x7c, 0x00,
|
||||||
|
0xcc, 0xcc, 0xfc, 0xcc, 0xcc, 0x00,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x00,
|
||||||
|
0x0c, 0x0c, 0x0c, 0xcc, 0xb8, 0x00,
|
||||||
|
0xcc, 0xc8, 0xe0, 0xc8, 0xcc, 0x00,
|
||||||
|
0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0x00,
|
||||||
|
0x88, 0xec, 0xfc, 0xdc, 0xcc, 0x00,
|
||||||
|
0x8c, 0xdc, 0xec, 0xdc, 0xc8, 0x00,
|
||||||
|
0x74, 0x88, 0xcc, 0x88, 0x74, 0x00,
|
||||||
|
0xe4, 0xcc, 0xcc, 0xe4, 0xc0, 0x00,
|
||||||
|
0x74, 0x88, 0xcc, 0x8c, 0x7c, 0x00,
|
||||||
|
0xe4, 0xcc, 0xcc, 0xe4, 0xcc, 0x00,
|
||||||
|
0xb8, 0xc0, 0xb8, 0x0c, 0xb8, 0x00,
|
||||||
|
0xfc, 0x30, 0x30, 0x30, 0x30, 0x00,
|
||||||
|
0xcc, 0xcc, 0xcc, 0x8c, 0x68, 0x00,
|
||||||
|
0xcc, 0xcc, 0xcc, 0x98, 0x30, 0x00,
|
||||||
|
0xcc, 0xcc, 0xdc, 0xfc, 0xec, 0x00,
|
||||||
|
0xcc, 0x88, 0x20, 0x88, 0xcc, 0x00,
|
||||||
|
0xcc, 0x88, 0x64, 0x30, 0x30, 0x00,
|
||||||
|
0xfc, 0x08, 0x30, 0x80, 0xfc, 0x00,
|
||||||
|
0xf0, 0xc0, 0xc0, 0xc0, 0xf0, 0x00,
|
||||||
|
0xc0, 0x90, 0x30, 0x18, 0x0c, 0x00,
|
||||||
|
0x3c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00,
|
||||||
|
0x30, 0xdc, 0x88, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
|
||||||
|
0xc0, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x6c, 0x8c, 0x8c, 0x6c, 0x00,
|
||||||
|
0xc0, 0xe4, 0xc8, 0xc8, 0xe4, 0x00,
|
||||||
|
0x00, 0x6c, 0x80, 0x80, 0x6c, 0x00,
|
||||||
|
0x0c, 0x6c, 0x8c, 0x8c, 0x6c, 0x00,
|
||||||
|
0x00, 0x38, 0x8c, 0xe0, 0x7c, 0x00,
|
||||||
|
0x1c, 0x30, 0xb8, 0x30, 0x30, 0x30,
|
||||||
|
0x00, 0xe4, 0x88, 0x6c, 0x08, 0xe4,
|
||||||
|
0xc0, 0xe4, 0xc8, 0xcc, 0xcc, 0x00,
|
||||||
|
0x30, 0x00, 0x70, 0x30, 0x2c, 0x00,
|
||||||
|
0x30, 0x30, 0xf0, 0x00, 0x00, 0x00, // 0x6A j
|
||||||
|
0x00, 0x00, 0xf0, 0x30, 0x30, 0x30, // 0x6B k
|
||||||
|
0x00, 0x00, 0x3f, 0x30, 0x30, 0x30, // 0x6C l
|
||||||
|
0x30, 0x30, 0x3f, 0x00, 0x00, 0x00, // 0x6D m
|
||||||
|
0x30, 0x30, 0xff, 0x30, 0x30, 0x30, // 0x6E n
|
||||||
|
0x00, 0x74, 0x88, 0x88, 0x74, 0x00,
|
||||||
|
0x00, 0xe4, 0xc8, 0xc8, 0xe4, 0xc0,
|
||||||
|
0x00, 0x00, 0xff, 0x00, 0x00, 0x00, // 0x71 q
|
||||||
|
0x00, 0x8c, 0xe0, 0xc0, 0xc0, 0x00,
|
||||||
|
0x00, 0x6c, 0x90, 0x18, 0xf4, 0x00,
|
||||||
|
0x30, 0x30, 0x3f, 0x30, 0x30, 0x30, // 0x74 t
|
||||||
|
0x30, 0x30, 0xf0, 0x30, 0x30, 0x30, // 0x75 u
|
||||||
|
0x30, 0x30, 0xff, 0x00, 0x00, 0x00, // 0x76 v
|
||||||
|
0x00, 0x00, 0xff, 0x30, 0x30, 0x30, // 0x77 w
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, // 0x78 x
|
||||||
|
0x00, 0xcc, 0xcc, 0x78, 0x08, 0xa4,
|
||||||
|
0x00, 0xfc, 0x18, 0x90, 0xfc, 0x00,
|
||||||
|
0x2c, 0x30, 0xd0, 0x30, 0x2c, 0x00,
|
||||||
|
0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
|
||||||
|
0xe0, 0x30, 0x1c, 0x30, 0xe0, 0x00,
|
||||||
|
0xb0, 0xc0, 0x74, 0x0c, 0x38, 0x00,
|
||||||
|
0xec, 0x88, 0x88, 0x88, 0xec, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
758
video-terminal/tintty.cpp
Normal file
758
video-terminal/tintty.cpp
Normal file
@ -0,0 +1,758 @@
|
|||||||
|
#define TFT_BLACK 0x0000
|
||||||
|
#define TFT_BLUE 0x0014
|
||||||
|
#define TFT_RED 0xA000
|
||||||
|
#define TFT_GREEN 0x0500
|
||||||
|
#define TFT_CYAN 0x0514
|
||||||
|
#define TFT_MAGENTA 0xA014
|
||||||
|
#define TFT_YELLOW 0xA500
|
||||||
|
#define TFT_WHITE 0xA514
|
||||||
|
|
||||||
|
#define TFT_BOLD_BLACK 0x8410
|
||||||
|
#define TFT_BOLD_BLUE 0x001F
|
||||||
|
#define TFT_BOLD_RED 0xF800
|
||||||
|
#define TFT_BOLD_GREEN 0x07E0
|
||||||
|
#define TFT_BOLD_CYAN 0x07FF
|
||||||
|
#define TFT_BOLD_MAGENTA 0xF81F
|
||||||
|
#define TFT_BOLD_YELLOW 0xFFE0
|
||||||
|
#define TFT_BOLD_WHITE 0xFFFF
|
||||||
|
|
||||||
|
#include "tintty.h"
|
||||||
|
#include "font454.h"
|
||||||
|
|
||||||
|
// exported variable for input logic
|
||||||
|
// @todo refactor
|
||||||
|
bool tintty_cursor_key_mode_application;
|
||||||
|
|
||||||
|
const uint16_t ANSI_COLORS[] = {
|
||||||
|
TFT_BLACK,
|
||||||
|
TFT_RED,
|
||||||
|
TFT_GREEN,
|
||||||
|
TFT_YELLOW,
|
||||||
|
TFT_BLUE,
|
||||||
|
TFT_MAGENTA,
|
||||||
|
TFT_CYAN,
|
||||||
|
TFT_WHITE
|
||||||
|
};
|
||||||
|
|
||||||
|
const uint16_t ANSI_BOLD_COLORS[] = {
|
||||||
|
TFT_BOLD_BLACK,
|
||||||
|
TFT_BOLD_RED,
|
||||||
|
TFT_BOLD_GREEN,
|
||||||
|
TFT_BOLD_YELLOW,
|
||||||
|
TFT_BOLD_BLUE,
|
||||||
|
TFT_BOLD_MAGENTA,
|
||||||
|
TFT_BOLD_CYAN,
|
||||||
|
TFT_BOLD_WHITE
|
||||||
|
};
|
||||||
|
|
||||||
|
// cursor animation
|
||||||
|
const int16_t IDLE_CYCLE_MAX = 500;
|
||||||
|
const int16_t IDLE_CYCLE_ON = (IDLE_CYCLE_MAX/2);
|
||||||
|
|
||||||
|
const int16_t TAB_SIZE = 4;
|
||||||
|
|
||||||
|
// cursor and character position is in global buffer coordinate space (may exceed screen height)
|
||||||
|
struct tintty_state {
|
||||||
|
// @todo consider storing cursor position as single int offset
|
||||||
|
int16_t cursor_col, cursor_row;
|
||||||
|
uint16_t bg_ansi_color, fg_ansi_color;
|
||||||
|
bool bold;
|
||||||
|
|
||||||
|
// cursor mode
|
||||||
|
bool cursor_key_mode_application;
|
||||||
|
|
||||||
|
// saved DEC cursor info (in screen coords)
|
||||||
|
int16_t dec_saved_col, dec_saved_row, dec_saved_bg, dec_saved_fg;
|
||||||
|
uint8_t dec_saved_g4bank;
|
||||||
|
bool dec_saved_bold, dec_saved_no_wrap;
|
||||||
|
|
||||||
|
// @todo deal with integer overflow
|
||||||
|
int16_t top_row; // first displayed row in a logical scrollback buffer
|
||||||
|
bool no_wrap;
|
||||||
|
bool cursor_hidden;
|
||||||
|
|
||||||
|
char out_char;
|
||||||
|
int16_t out_char_col, out_char_row;
|
||||||
|
uint8_t out_char_g4bank; // current set shift state, G0 to G3
|
||||||
|
int16_t out_clear_before, out_clear_after;
|
||||||
|
|
||||||
|
uint8_t g4bank_char_set[4];
|
||||||
|
|
||||||
|
int16_t idle_cycle_count; // @todo track during blocking reads mid-command
|
||||||
|
} state;
|
||||||
|
|
||||||
|
struct tintty_rendered {
|
||||||
|
int16_t cursor_col, cursor_row;
|
||||||
|
int16_t top_row;
|
||||||
|
} rendered;
|
||||||
|
|
||||||
|
// @todo support negative cursor_row
|
||||||
|
void _render(tintty_display *display) {
|
||||||
|
// expose the cursor key mode state
|
||||||
|
tintty_cursor_key_mode_application = state.cursor_key_mode_application;
|
||||||
|
|
||||||
|
// if scrolling, prepare the "recycled" screen area
|
||||||
|
if (state.top_row != rendered.top_row) {
|
||||||
|
// clear the new piece of screen to be recycled as blank space
|
||||||
|
// @todo handle scroll-up
|
||||||
|
if (state.top_row > rendered.top_row) {
|
||||||
|
// pre-clear the lines at the bottom
|
||||||
|
// @todo always use black instead of current background colour?
|
||||||
|
// @todo deal with overflow from multiplication by CHAR_HEIGHT
|
||||||
|
/*int16_t old_bottom_y = rendered.top_row * FONT_HEIGHT + display->screen_row_count * FONT_HEIGHT; // bottom of text may not align with screen height
|
||||||
|
int16_t new_bottom_y = state.top_row * FONT_HEIGHT + display->screen_height; // extend to bottom edge of new displayed area
|
||||||
|
int16_t clear_sbuf_bottom = new_bottom_y % display->screen_height;
|
||||||
|
int16_t clear_height = min((int)display->screen_height, new_bottom_y - old_bottom_y);
|
||||||
|
int16_t clear_sbuf_top = clear_sbuf_bottom - clear_height;*/
|
||||||
|
|
||||||
|
// if rectangle straddles the screen buffer top edge, render that slice at bottom edge
|
||||||
|
/*if (clear_sbuf_top < 0) {
|
||||||
|
display->fill_rect(
|
||||||
|
0,
|
||||||
|
clear_sbuf_top + display->screen_height,
|
||||||
|
display->screen_width,
|
||||||
|
-clear_sbuf_top,
|
||||||
|
ANSI_COLORS[state.bg_ansi_color]
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// if rectangle is not entirely above top edge, render the normal slice
|
||||||
|
/*if (clear_sbuf_bottom > 0) {
|
||||||
|
display->fill_rect(
|
||||||
|
0,
|
||||||
|
max(0, (int)clear_sbuf_top),
|
||||||
|
display->screen_width,
|
||||||
|
clear_sbuf_bottom - max(0, (int)clear_sbuf_top),
|
||||||
|
ANSI_COLORS[state.bg_ansi_color]
|
||||||
|
);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// update displayed scroll
|
||||||
|
display->set_vscroll((state.top_row) % display->screen_row_count); // @todo deal with overflow from multiplication
|
||||||
|
|
||||||
|
// save rendered state
|
||||||
|
rendered.top_row = state.top_row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// render character if needed
|
||||||
|
if (state.out_char != 0) {
|
||||||
|
const uint16_t fg_tft_color = state.bold ? ANSI_BOLD_COLORS[state.fg_ansi_color] : ANSI_COLORS[state.fg_ansi_color];
|
||||||
|
const uint16_t bg_tft_color = ANSI_COLORS[state.bg_ansi_color];
|
||||||
|
const uint8_t char_set = state.g4bank_char_set[state.out_char_g4bank & 0x03]; // ensure 0-3 value
|
||||||
|
|
||||||
|
display->print_character(state.out_char_col, state.out_char_row, fg_tft_color, bg_tft_color, state.out_char);
|
||||||
|
|
||||||
|
// clear for next render
|
||||||
|
state.out_char = 0;
|
||||||
|
state.out_clear_before = 0;
|
||||||
|
state.out_clear_after = 0;
|
||||||
|
|
||||||
|
// the char draw may overpaint the cursor, in which case
|
||||||
|
// mark it for repaint
|
||||||
|
if (
|
||||||
|
rendered.cursor_col == state.out_char_col &&
|
||||||
|
rendered.cursor_row == state.out_char_row
|
||||||
|
) {
|
||||||
|
display->print_cursor(rendered.cursor_col, rendered.cursor_row, ANSI_COLORS[state.bg_ansi_color]);
|
||||||
|
rendered.cursor_col = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reflect new cursor bar render state
|
||||||
|
const bool cursor_bar_shown = (
|
||||||
|
!state.cursor_hidden &&
|
||||||
|
state.idle_cycle_count < IDLE_CYCLE_ON
|
||||||
|
);
|
||||||
|
|
||||||
|
// clear existing rendered cursor bar if needed
|
||||||
|
// @todo detect if it is already cleared during scroll
|
||||||
|
if (rendered.cursor_col >= 0) {
|
||||||
|
if (
|
||||||
|
!cursor_bar_shown ||
|
||||||
|
rendered.cursor_col != state.cursor_col ||
|
||||||
|
rendered.cursor_row != state.cursor_row
|
||||||
|
) {
|
||||||
|
display->print_cursor(rendered.cursor_col, rendered.cursor_row, ANSI_COLORS[state.bg_ansi_color]);
|
||||||
|
|
||||||
|
// record the fact that cursor bar is not on screen
|
||||||
|
rendered.cursor_col = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// render new cursor bar if not already shown
|
||||||
|
// (sometimes right after clearing existing bar)
|
||||||
|
if (rendered.cursor_col < 0) {
|
||||||
|
if (cursor_bar_shown) {
|
||||||
|
display->print_cursor(rendered.cursor_col, rendered.cursor_row, ANSI_COLORS[state.bg_ansi_color]);
|
||||||
|
display->print_cursor(state.cursor_col, state.cursor_row, state.bold ? ANSI_BOLD_COLORS[state.fg_ansi_color] : ANSI_COLORS[state.fg_ansi_color]);
|
||||||
|
|
||||||
|
// save new rendered state
|
||||||
|
rendered.cursor_col = state.cursor_col;
|
||||||
|
rendered.cursor_row = state.cursor_row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bell() {
|
||||||
|
// TODO
|
||||||
|
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html
|
||||||
|
// https://github.com/lbernstone/Tone32
|
||||||
|
}
|
||||||
|
|
||||||
|
void _ensure_cursor_vscroll(tintty_display *display) {
|
||||||
|
// move displayed window down to cover cursor
|
||||||
|
// @todo support scrolling up as well
|
||||||
|
if (state.cursor_row - state.top_row >= display->screen_row_count) {
|
||||||
|
state.top_row = state.cursor_row - display->screen_row_count + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _send_sequence(
|
||||||
|
void (*send_char)(char ch),
|
||||||
|
char* str
|
||||||
|
) {
|
||||||
|
// send zero-terminated sequence character by character
|
||||||
|
while (*str) {
|
||||||
|
send_char(*str);
|
||||||
|
str += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char _read_decimal(
|
||||||
|
char (*peek_char)(),
|
||||||
|
char (*read_char)()
|
||||||
|
) {
|
||||||
|
uint16_t accumulator = 0;
|
||||||
|
|
||||||
|
while (isdigit(peek_char())) {
|
||||||
|
const char digit_character = read_char();
|
||||||
|
const uint16_t digit = digit_character - '0';
|
||||||
|
accumulator = accumulator * 10 + digit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _apply_graphic_rendition(
|
||||||
|
uint16_t* arg_list,
|
||||||
|
uint16_t arg_count
|
||||||
|
) {
|
||||||
|
if (arg_count == 0) {
|
||||||
|
// special case for resetting to default style
|
||||||
|
state.bg_ansi_color = 0;
|
||||||
|
state.fg_ansi_color = 7;
|
||||||
|
state.bold = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process commands
|
||||||
|
// @todo support bold/etc for better colour support
|
||||||
|
// @todo 39/49?
|
||||||
|
for (uint16_t arg_index = 0; arg_index < arg_count; arg_index += 1) {
|
||||||
|
const uint16_t arg_value = arg_list[arg_index];
|
||||||
|
|
||||||
|
if (arg_value == 0) {
|
||||||
|
// reset to default style
|
||||||
|
state.bg_ansi_color = 0;
|
||||||
|
state.fg_ansi_color = 7;
|
||||||
|
state.bold = false;
|
||||||
|
} else if (arg_value == 1) {
|
||||||
|
// bold
|
||||||
|
state.bold = true;
|
||||||
|
} else if (arg_value >= 30 && arg_value <= 37) {
|
||||||
|
// foreground ANSI colour
|
||||||
|
state.fg_ansi_color = arg_value - 30;
|
||||||
|
} else if (arg_value >= 40 && arg_value <= 47) {
|
||||||
|
// background ANSI colour
|
||||||
|
state.bg_ansi_color = arg_value - 40;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _apply_mode_setting(
|
||||||
|
bool mode_on,
|
||||||
|
uint16_t* arg_list,
|
||||||
|
uint16_t arg_count
|
||||||
|
) {
|
||||||
|
// process modes
|
||||||
|
for (uint16_t arg_index = 0; arg_index < arg_count; arg_index += 1) {
|
||||||
|
const uint16_t mode_id = arg_list[arg_index];
|
||||||
|
|
||||||
|
switch (mode_id) {
|
||||||
|
case 4:
|
||||||
|
// insert/replace mode
|
||||||
|
// @todo this should be off for most practical purposes anyway?
|
||||||
|
// ... otherwise visually shifting line text is expensive
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 20:
|
||||||
|
// auto-LF
|
||||||
|
// ignoring per http://vt100.net/docs/vt220-rm/chapter4.html section 4.6.6
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 34:
|
||||||
|
// cursor visibility
|
||||||
|
state.cursor_hidden = !mode_on;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _exec_escape_question_command(
|
||||||
|
char (*peek_char)(),
|
||||||
|
char (*read_char)(),
|
||||||
|
void (*send_char)(char ch)
|
||||||
|
) {
|
||||||
|
// @todo support multiple mode commands
|
||||||
|
// per http://vt100.net/docs/vt220-rm/chapter4.html section 4.6.1,
|
||||||
|
// ANSI and DEC modes cannot mix; that is, '[?25;20;?7l' is not a valid Esc-command
|
||||||
|
// (noting this because https://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html
|
||||||
|
// makes it look like the question mark is a prefix)
|
||||||
|
const uint16_t mode = _read_decimal(peek_char, read_char);
|
||||||
|
const bool mode_on = (read_char() != 'l');
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case 1:
|
||||||
|
// cursor key mode (normal/application)
|
||||||
|
state.cursor_key_mode_application = mode_on;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
// auto wrap mode
|
||||||
|
state.no_wrap = !mode_on;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 25:
|
||||||
|
// cursor visibility
|
||||||
|
state.cursor_hidden = !mode_on;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo cursor position report
|
||||||
|
void _exec_escape_bracket_command_with_args(
|
||||||
|
char (*peek_char)(),
|
||||||
|
char (*read_char)(),
|
||||||
|
void (*send_char)(char ch),
|
||||||
|
tintty_display *display,
|
||||||
|
uint16_t* arg_list,
|
||||||
|
uint16_t arg_count
|
||||||
|
) {
|
||||||
|
// convenient arg getter
|
||||||
|
#define ARG(index, default_value) (arg_count > index ? arg_list[index] : default_value)
|
||||||
|
|
||||||
|
// process next character after Escape-code, bracket and any numeric arguments
|
||||||
|
const char command_character = read_char();
|
||||||
|
|
||||||
|
switch (command_character) {
|
||||||
|
case '?':
|
||||||
|
// question-mark commands
|
||||||
|
_exec_escape_question_command(peek_char, read_char, send_char);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'A':
|
||||||
|
// cursor up (no scroll)
|
||||||
|
state.cursor_row = max((int)state.top_row, state.cursor_row - ARG(0, 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'B':
|
||||||
|
// cursor down (no scroll)
|
||||||
|
state.cursor_row = min(state.top_row + display->screen_row_count - 1, state.cursor_row + ARG(0, 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'C':
|
||||||
|
// cursor right (no scroll)
|
||||||
|
state.cursor_col = min(display->screen_col_count - 1, state.cursor_col + ARG(0, 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D':
|
||||||
|
// cursor left (no scroll)
|
||||||
|
state.cursor_col = max(0, state.cursor_col - ARG(0, 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'H':
|
||||||
|
case 'f':
|
||||||
|
// Direct Cursor Addressing (row;col)
|
||||||
|
state.cursor_col = max(0, min(display->screen_col_count - 1, ARG(1, 1) - 1));
|
||||||
|
state.cursor_row = state.top_row + max(0, min(display->screen_row_count - 1, ARG(0, 1) - 1));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'J':
|
||||||
|
// clear screen
|
||||||
|
state.out_char = ' ';
|
||||||
|
state.out_char_col = state.cursor_col;
|
||||||
|
state.out_char_row = state.cursor_row;
|
||||||
|
|
||||||
|
{
|
||||||
|
const int16_t rel_row = state.cursor_row - state.top_row;
|
||||||
|
|
||||||
|
state.out_clear_before = ARG(0, 0) != 0
|
||||||
|
? rel_row * display->screen_col_count + state.cursor_col
|
||||||
|
: 0;
|
||||||
|
state.out_clear_after = ARG(0, 0) != 1
|
||||||
|
? (display->screen_row_count - 1 - rel_row) * display->screen_col_count + (display->screen_col_count - 1 - state.cursor_col)
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'K':
|
||||||
|
// clear line
|
||||||
|
state.out_char = ' ';
|
||||||
|
state.out_char_col = state.cursor_col;
|
||||||
|
state.out_char_row = state.cursor_row;
|
||||||
|
|
||||||
|
state.out_clear_before = ARG(0, 0) != 0
|
||||||
|
? state.cursor_col
|
||||||
|
: 0;
|
||||||
|
state.out_clear_after = ARG(0, 0) != 1
|
||||||
|
? display->screen_col_count - 1 - state.cursor_col
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
// graphic rendition mode
|
||||||
|
_apply_graphic_rendition(arg_list, arg_count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
// set mode
|
||||||
|
_apply_mode_setting(true, arg_list, arg_count);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
// unset mode
|
||||||
|
_apply_mode_setting(false, arg_list, arg_count);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _exec_escape_bracket_command(
|
||||||
|
char (*peek_char)(),
|
||||||
|
char (*read_char)(),
|
||||||
|
void (*send_char)(char ch),
|
||||||
|
tintty_display *display
|
||||||
|
) {
|
||||||
|
const uint16_t MAX_COMMAND_ARG_COUNT = 10;
|
||||||
|
uint16_t arg_list[MAX_COMMAND_ARG_COUNT];
|
||||||
|
uint16_t arg_count = 0;
|
||||||
|
|
||||||
|
// start parsing arguments if any
|
||||||
|
// (this means that '' is treated as no arguments, but '0;' is treated as two arguments, each being zero)
|
||||||
|
// @todo ignore trailing semi-colon instead of treating it as marking an extra zero arg?
|
||||||
|
if (isdigit(peek_char())) {
|
||||||
|
// keep consuming arguments while we have space
|
||||||
|
while (arg_count < MAX_COMMAND_ARG_COUNT) {
|
||||||
|
// consume decimal number
|
||||||
|
arg_list[arg_count] = _read_decimal(peek_char, read_char);
|
||||||
|
arg_count += 1;
|
||||||
|
|
||||||
|
// stop processing if next char is not separator
|
||||||
|
if (peek_char() != ';') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume separator before starting next argument
|
||||||
|
read_char();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_exec_escape_bracket_command_with_args(
|
||||||
|
peek_char,
|
||||||
|
read_char,
|
||||||
|
send_char,
|
||||||
|
display,
|
||||||
|
arg_list,
|
||||||
|
arg_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the characters displayed for given G0-G3 bank
|
||||||
|
void _exec_character_set(
|
||||||
|
uint8_t g4bank_index,
|
||||||
|
char (*read_char)()
|
||||||
|
) {
|
||||||
|
switch (read_char()) {
|
||||||
|
case 'A':
|
||||||
|
case 'B':
|
||||||
|
// normal character set (UK/US)
|
||||||
|
state.g4bank_char_set[g4bank_index] = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '0':
|
||||||
|
// line-drawing
|
||||||
|
state.g4bank_char_set[g4bank_index] = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// alternate sets are unsupported
|
||||||
|
state.g4bank_char_set[g4bank_index] = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo terminal reset
|
||||||
|
// @todo parse modes with arguments even if they are no-op
|
||||||
|
void _exec_escape_code(
|
||||||
|
char (*peek_char)(),
|
||||||
|
char (*read_char)(),
|
||||||
|
void (*send_char)(char ch),
|
||||||
|
tintty_display *display
|
||||||
|
) {
|
||||||
|
// read next character after Escape-code
|
||||||
|
// @todo time out?
|
||||||
|
char esc_character = read_char();
|
||||||
|
|
||||||
|
// @todo support for (, ), #, c, cursor save/restore
|
||||||
|
switch (esc_character) {
|
||||||
|
case '[':
|
||||||
|
_exec_escape_bracket_command(peek_char, read_char, send_char, display);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'D':
|
||||||
|
// index (move down and possibly scroll)
|
||||||
|
state.cursor_row += 1;
|
||||||
|
_ensure_cursor_vscroll(display);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'M':
|
||||||
|
// reverse index (move up and possibly scroll)
|
||||||
|
state.cursor_row -= 1;
|
||||||
|
_ensure_cursor_vscroll(display);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'E':
|
||||||
|
// next line
|
||||||
|
state.cursor_row += 1;
|
||||||
|
state.cursor_col = 0;
|
||||||
|
_ensure_cursor_vscroll(display);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Z':
|
||||||
|
// Identify Terminal (DEC Private)
|
||||||
|
_send_sequence(send_char, "\e[?1;0c"); // DA response: no options
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '7':
|
||||||
|
// save cursor
|
||||||
|
// @todo verify that the screen-relative coordinate approach is valid
|
||||||
|
state.dec_saved_col = state.cursor_col;
|
||||||
|
state.dec_saved_row = state.cursor_row - state.top_row; // relative to top
|
||||||
|
state.dec_saved_bg = state.bg_ansi_color;
|
||||||
|
state.dec_saved_fg = state.fg_ansi_color;
|
||||||
|
state.dec_saved_g4bank = state.out_char_g4bank;
|
||||||
|
state.dec_saved_bold = state.bold;
|
||||||
|
state.dec_saved_no_wrap = state.no_wrap;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '8':
|
||||||
|
// restore cursor
|
||||||
|
state.cursor_col = state.dec_saved_col;
|
||||||
|
state.cursor_row = state.dec_saved_row + state.top_row; // relative to top
|
||||||
|
state.bg_ansi_color = state.dec_saved_bg;
|
||||||
|
state.fg_ansi_color = state.dec_saved_fg;
|
||||||
|
state.out_char_g4bank = state.dec_saved_g4bank;
|
||||||
|
state.bold = state.dec_saved_bold;
|
||||||
|
state.no_wrap = state.dec_saved_no_wrap;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '=':
|
||||||
|
case '>':
|
||||||
|
// keypad mode setting - ignoring
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '(':
|
||||||
|
// set G0
|
||||||
|
_exec_character_set(0, read_char);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ')':
|
||||||
|
// set G1
|
||||||
|
_exec_character_set(1, read_char);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '*':
|
||||||
|
// set G2
|
||||||
|
_exec_character_set(2, read_char);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '+':
|
||||||
|
// set G3
|
||||||
|
_exec_character_set(3, read_char);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// unrecognized character, silently ignore
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _main(
|
||||||
|
char (*peek_char)(),
|
||||||
|
char (*read_char)(),
|
||||||
|
void (*send_char)(char str),
|
||||||
|
tintty_display *display
|
||||||
|
) {
|
||||||
|
// start in default idle state
|
||||||
|
char initial_character = read_char();
|
||||||
|
|
||||||
|
if (initial_character >= 0x20 && initial_character <= 0x7e) {
|
||||||
|
// output displayable character
|
||||||
|
state.out_char = initial_character;
|
||||||
|
state.out_char_col = state.cursor_col;
|
||||||
|
state.out_char_row = state.cursor_row;
|
||||||
|
|
||||||
|
// update caret
|
||||||
|
state.cursor_col += 1;
|
||||||
|
|
||||||
|
if (state.cursor_col >= display->screen_col_count) {
|
||||||
|
if (state.no_wrap) {
|
||||||
|
state.cursor_col = display->screen_col_count - 1;
|
||||||
|
} else {
|
||||||
|
state.cursor_col = 0;
|
||||||
|
state.cursor_row += 1;
|
||||||
|
_ensure_cursor_vscroll(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset idle state
|
||||||
|
state.idle_cycle_count = 0;
|
||||||
|
} else {
|
||||||
|
// @todo bell, answer-back (0x05), delete
|
||||||
|
switch (initial_character) {
|
||||||
|
case '\a':
|
||||||
|
// bell
|
||||||
|
bell();
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
// line-feed
|
||||||
|
state.cursor_row += 1;
|
||||||
|
_ensure_cursor_vscroll(display);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\r':
|
||||||
|
// carriage-return
|
||||||
|
state.cursor_col = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\b':
|
||||||
|
// backspace
|
||||||
|
state.cursor_col -= 1;
|
||||||
|
|
||||||
|
if (state.cursor_col < 0) {
|
||||||
|
if (state.no_wrap) {
|
||||||
|
state.cursor_col = 0;
|
||||||
|
} else {
|
||||||
|
state.cursor_col = display->screen_col_count - 1;
|
||||||
|
state.cursor_row -= 1;
|
||||||
|
_ensure_cursor_vscroll(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\t':
|
||||||
|
// tab
|
||||||
|
{
|
||||||
|
// @todo blank out the existing characters? not sure if that is expected
|
||||||
|
const int16_t tab_num = state.cursor_col / TAB_SIZE;
|
||||||
|
state.cursor_col = min(display->screen_col_count - 1, (tab_num + 1) * TAB_SIZE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\e':
|
||||||
|
// Escape-command
|
||||||
|
_exec_escape_code(peek_char, read_char, send_char, display);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\x0f':
|
||||||
|
// Shift-In (use G0)
|
||||||
|
// see also the fun reason why these are called this way:
|
||||||
|
// https://en.wikipedia.org/wiki/Shift_Out_and_Shift_In_characters
|
||||||
|
state.out_char_g4bank = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\x0e':
|
||||||
|
// Shift-Out (use G1)
|
||||||
|
state.out_char_g4bank = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// nothing, just animate cursor
|
||||||
|
delay(1);
|
||||||
|
state.idle_cycle_count = (state.idle_cycle_count + 1) % IDLE_CYCLE_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_render(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tintty_run(
|
||||||
|
char (*peek_char)(),
|
||||||
|
char (*read_char)(),
|
||||||
|
void (*send_char)(char str),
|
||||||
|
tintty_display *display
|
||||||
|
) {
|
||||||
|
// set up initial state
|
||||||
|
state.cursor_col = 0;
|
||||||
|
state.cursor_row = 0;
|
||||||
|
state.top_row = 0;
|
||||||
|
state.no_wrap = 0;
|
||||||
|
state.cursor_hidden = 0;
|
||||||
|
state.bg_ansi_color = 0;
|
||||||
|
state.fg_ansi_color = 7;
|
||||||
|
state.bold = false;
|
||||||
|
|
||||||
|
state.cursor_key_mode_application = false;
|
||||||
|
|
||||||
|
state.dec_saved_col = 0;
|
||||||
|
state.dec_saved_row = 0;
|
||||||
|
state.dec_saved_bg = state.bg_ansi_color;
|
||||||
|
state.dec_saved_fg = state.fg_ansi_color;
|
||||||
|
state.dec_saved_g4bank = 0;
|
||||||
|
state.dec_saved_bold = state.bold;
|
||||||
|
state.dec_saved_no_wrap = false;
|
||||||
|
|
||||||
|
state.out_char = 0;
|
||||||
|
state.out_char_g4bank = 0;
|
||||||
|
state.g4bank_char_set[0] = 0;
|
||||||
|
state.g4bank_char_set[1] = 0;
|
||||||
|
state.g4bank_char_set[2] = 0;
|
||||||
|
state.g4bank_char_set[3] = 0;
|
||||||
|
|
||||||
|
rendered.cursor_col = -1;
|
||||||
|
rendered.cursor_row = -1;
|
||||||
|
|
||||||
|
// clear screen
|
||||||
|
display->fill_rect(0, 0, display->screen_width, display->screen_height, TFT_BLACK);
|
||||||
|
|
||||||
|
// reset TFT scroll to default
|
||||||
|
display->set_vscroll(0);
|
||||||
|
|
||||||
|
// initial render
|
||||||
|
_render(display);
|
||||||
|
|
||||||
|
// send CR to indicate that the screen is ready
|
||||||
|
// (this works with the agetty --wait-cr option to help wait until Arduino boots)
|
||||||
|
send_char('\r');
|
||||||
|
|
||||||
|
// main read cycle
|
||||||
|
while (1) {
|
||||||
|
_main(peek_char, read_char, send_char, display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tintty_idle(
|
||||||
|
tintty_display *display
|
||||||
|
) {
|
||||||
|
delay(1);
|
||||||
|
|
||||||
|
// animate cursor
|
||||||
|
state.idle_cycle_count = (state.idle_cycle_count + 1) % IDLE_CYCLE_MAX;
|
||||||
|
|
||||||
|
// re-render
|
||||||
|
_render(display);
|
||||||
|
}
|
37
video-terminal/tintty.h
Normal file
37
video-terminal/tintty.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
extern bool tintty_cursor_key_mode_application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renderer callbacks.
|
||||||
|
*/
|
||||||
|
struct tintty_display {
|
||||||
|
int16_t screen_width, screen_height;
|
||||||
|
int16_t screen_col_count, screen_row_count; // width and height divided by char size
|
||||||
|
|
||||||
|
void (*fill_rect)(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
|
||||||
|
void (*draw_pixels)(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pixels);
|
||||||
|
void (*print_character)(int16_t col, int16_t row, uint16_t fg_color, uint16_t bg_color, char character);
|
||||||
|
void (*print_cursor)(int16_t col, int16_t row, uint16_t color);
|
||||||
|
void (*set_vscroll)(int16_t offset); // scroll offset for entire screen
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point.
|
||||||
|
* Peek/read callbacks are expected to block until input is available;
|
||||||
|
* while sketch is waiting for input, it should call the tintty_idle() hook
|
||||||
|
* to allow animating cursor, etc.
|
||||||
|
*/
|
||||||
|
void tintty_run(
|
||||||
|
char (*peek_char)(),
|
||||||
|
char (*read_char)(),
|
||||||
|
void (*send_char)(char str),
|
||||||
|
tintty_display *display
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to call while e.g. sketch is waiting for input
|
||||||
|
*/
|
||||||
|
void tintty_idle(
|
||||||
|
tintty_display *display
|
||||||
|
);
|
153
video-terminal/video-terminal.ino
Normal file
153
video-terminal/video-terminal.ino
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* Video Terminal
|
||||||
|
* VT100 emulated by ESP32 + TV display + LK201 keyboard
|
||||||
|
*
|
||||||
|
* TinTTY main sketch
|
||||||
|
* by Nick Matantsev 2017
|
||||||
|
*
|
||||||
|
* Original reference: VT100 emulation code written by Martin K. Schroeder
|
||||||
|
* and modified by Peter Scargill.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
|
#include <Adafruit_GFX.h>
|
||||||
|
|
||||||
|
#include "tintty.h"
|
||||||
|
|
||||||
|
#define SerialTty Serial1
|
||||||
|
#define SerialKbd Serial2
|
||||||
|
|
||||||
|
#include "Keyboard.h"
|
||||||
|
|
||||||
|
#include "Display.h"
|
||||||
|
Display display;
|
||||||
|
|
||||||
|
int16_t scrolled = 0;
|
||||||
|
|
||||||
|
#define FONT_SCALE 2
|
||||||
|
|
||||||
|
uint16_t make_bw_color(uint16_t color) {
|
||||||
|
return (color >> 8) | (color & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tintty_display ili9341_display = {
|
||||||
|
display.vw,//ILI9341_WIDTH,
|
||||||
|
display.vh,//(ILI9341_HEIGHT - KEYBOARD_HEIGHT),
|
||||||
|
display.vw / display.font_w,//ILI9341_WIDTH / TINTTY_CHAR_WIDTH,
|
||||||
|
display.vh / display.font_h,//(ILI9341_HEIGHT - KEYBOARD_HEIGHT) / TINTTY_CHAR_HEIGHT,
|
||||||
|
|
||||||
|
[=](int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color){
|
||||||
|
//tft.fillRect(x, y, w, h, color);
|
||||||
|
//display.fill_rect(x*FONT_SCALE + display.ow, (y*FONT_SCALE + display.oh, w*FONT_SCALE, h*FONT_SCALE, make_bw_color(color));
|
||||||
|
},
|
||||||
|
|
||||||
|
[=](int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pixels){
|
||||||
|
//tft.setAddrWindow(x, y, x + w - 1, y + h - 1);
|
||||||
|
//tft.pushColors(pixels, w * h, 1);
|
||||||
|
for (int px = 0; px < w; px++) {
|
||||||
|
for (int py = 0; py < h; py++) {
|
||||||
|
int i = py*w + px;
|
||||||
|
//display.pixel(x + display.ow + px, y + display.oh + py, pixels[i]);
|
||||||
|
//display.fill_rect(x*FONT_SCALE + display.ow + px*FONT_SCALE, y*FONT_SCALE + display.oh + py*FONT_SCALE, FONT_SCALE, FONT_SCALE, make_bw_color(pixels[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[=](int16_t col, int16_t row, uint16_t fg_color, uint16_t bg_color, char character){
|
||||||
|
display.print_character(col, row - scrolled, make_bw_color(fg_color), make_bw_color(bg_color), character);
|
||||||
|
},
|
||||||
|
|
||||||
|
[=](int16_t col, int16_t row, uint16_t color){
|
||||||
|
display.fill_rect(col*display.font_w + display.ow, (row+1 - scrolled)*display.font_h-1 + display.oh, display.font_w, 1, make_bw_color(color));
|
||||||
|
//display.print_character(col, row, make_bw_color(color), 0, '_');
|
||||||
|
},
|
||||||
|
|
||||||
|
[=](int16_t offset){
|
||||||
|
//tft.vertScroll(0, (ILI9341_HEIGHT - KEYBOARD_HEIGHT), offset);
|
||||||
|
int16_t rows = display.vh / display.font_h;
|
||||||
|
int16_t diff = (rows + offset - (scrolled % rows)) % rows;
|
||||||
|
display.scroll(diff*display.font_h);
|
||||||
|
scrolled += diff;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void tty_keyboard_process()
|
||||||
|
{
|
||||||
|
// read keyboard and send it to the host
|
||||||
|
if (SerialKbd.available() > 0) {
|
||||||
|
int key = SerialKbd.read();
|
||||||
|
keyboard_handle_key(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// buffer to test various input sequences
|
||||||
|
char *test_buffer = "-- \e[1mTinTTY\e[m --\r\n";
|
||||||
|
uint8_t test_buffer_cursor = 0;
|
||||||
|
|
||||||
|
void input_init() {};
|
||||||
|
void input_idle() {};
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
// Debug port
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("Running!");
|
||||||
|
|
||||||
|
// TTY host
|
||||||
|
SerialTty.begin(9600, SERIAL_8N1, 18, 19, false, 100);
|
||||||
|
|
||||||
|
// LK201 keyboard connected to pins 16 and 17
|
||||||
|
SerialKbd.begin(4800);
|
||||||
|
|
||||||
|
display.setup();
|
||||||
|
//uint16_t tftID = tft.readID();
|
||||||
|
//tft.begin(tftID);
|
||||||
|
|
||||||
|
input_init();
|
||||||
|
|
||||||
|
tintty_run(
|
||||||
|
[=](){
|
||||||
|
// peek idle loop
|
||||||
|
while (true) {
|
||||||
|
// first peek from the test buffer
|
||||||
|
if (test_buffer[test_buffer_cursor]) {
|
||||||
|
return test_buffer[test_buffer_cursor];
|
||||||
|
}
|
||||||
|
|
||||||
|
tty_keyboard_process();
|
||||||
|
|
||||||
|
// fall back to normal blocking serial input
|
||||||
|
if (SerialTty.available() > 0) {
|
||||||
|
return (char)SerialTty.peek();
|
||||||
|
}
|
||||||
|
|
||||||
|
// idle logic only after peeks failed
|
||||||
|
tintty_idle(&ili9341_display);
|
||||||
|
input_idle();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[=](){
|
||||||
|
while(true) {
|
||||||
|
// process at least one idle loop first to allow input to happen
|
||||||
|
tintty_idle(&ili9341_display);
|
||||||
|
input_idle();
|
||||||
|
tty_keyboard_process();
|
||||||
|
|
||||||
|
// first read from the test buffer
|
||||||
|
if (test_buffer[test_buffer_cursor]) {
|
||||||
|
return test_buffer[test_buffer_cursor++];
|
||||||
|
}
|
||||||
|
|
||||||
|
// fall back to normal blocking serial input
|
||||||
|
if (SerialTty.available() > 0) {
|
||||||
|
return (char)SerialTty.read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[=](char ch){ SerialTty.print(ch); },
|
||||||
|
&ili9341_display
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user