/
jswrap_terminal.c
232 lines (217 loc) · 7.12 KB
/
jswrap_terminal.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2017 Gordon Williams <gw@pur3.co.uk>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ----------------------------------------------------------------------------
* This file is designed to be parsed during the build process
*
* Contains VT100 terminal emulator
* ----------------------------------------------------------------------------
*/
#include "jswrap_terminal.h"
#include "jswrap_graphics.h"
#include "graphics.h"
#include "jsparse.h"
#include "jsdevices.h"
#include "jshardware.h"
/*JSON{
"type" : "object",
"name" : "Terminal",
"instanceof" : "Serial",
"ifdef" : "USE_TERMINAL"
}
A simple VT100 terminal emulator.
When data is sent to the `Terminal` object, `Graphics.getInstance()` is called
and if an instance of `Graphics` is found then characters are written to it.
*/
#ifdef USE_FONT_6X8
#define TERMINAL_CHAR_W (6)
#define TERMINAL_CHAR_H (8)
#include "bitmap_font_6x8.h"
#define TERMINAL_CHAR_CMD graphicsDrawChar6x8
#else
#define TERMINAL_CHAR_W (4)
#define TERMINAL_CHAR_H (6)
#include "bitmap_font_4x6.h"
#define TERMINAL_CHAR_CMD graphicsDrawChar4x6
#endif
#ifdef LCD_HEIGHT
#define TERMINAL_HEIGHT (LCD_HEIGHT/TERMINAL_CHAR_H)
#define TERMINAL_OFFSET_Y (LCD_HEIGHT-(TERMINAL_HEIGHT*TERMINAL_CHAR_H))
#else
#define TERMINAL_HEIGHT (10)
#define TERMINAL_OFFSET_Y (4)
#endif
#define TERMINAL_OFFSET_X (0)
char terminalControlChars[4];
unsigned char terminalX = 0;
unsigned char terminalY = TERMINAL_HEIGHT-1;
bool terminalNeedsFlip = false;
static void terminalControlCharsReset() {
terminalControlChars[0]=0;
terminalControlChars[1]=0;
terminalControlChars[2]=0;
terminalControlChars[3]=0;
}
// Try and find something to use for Graphics - MUST call terminalSetGFX after if this returns true
bool terminalGetGFX(JsGraphics *gfx) {
#ifdef ESPR_GRAPHICS_INTERNAL
// FIXME: not ideal, we should really be using the pointer directly
if (!graphicsInternal.setPixel) return false; // not set up yet
*gfx = graphicsInternal;
return true;
#else
JsVar *v = jswrap_graphics_getInstance();
if (!v) return false;
if (graphicsGetFromVar(gfx, v))
return true;
jsvUnLock(v);
return false;
#endif
}
/// Setup the graphics var state and flip the screen
void terminalSetGFX(JsGraphics *gfx) {
#ifdef ESPR_GRAPHICS_INTERNAL
graphicsInternal = *gfx;
#else
graphicsSetVar(gfx);
jsvUnLock(gfx->graphicsVar);
#endif
terminalNeedsFlip = true; // force a flip to the screen next idle
}
/// Scroll up to leave one more line free at the bottom
void terminalScroll() {
terminalY--;
JsGraphics gfx;
if (terminalGetGFX(&gfx)) {
unsigned int cb = gfx.data.bgColor;
#ifdef GRAPHICS_THEME
gfx.data.bgColor = graphicsTheme.bg;
#else
gfx.data.bgColor = 0;
#endif
graphicsScroll(&gfx, 0, -TERMINAL_CHAR_H); // always fill background in black
gfx.data.bgColor = cb;
terminalSetGFX(&gfx); // save
// if we're not in an IRQ, flip this now
if (!jshIsInInterrupt())
jswrap_terminal_idle();
}
}
/// Handle data sent to the VT100 terminal
void terminalSendChar(char chn) {
if (terminalControlChars[0] == 0) {
if (chn==8) {
if (terminalX>0) terminalX--;
} else if (chn==10) { // line feed
terminalX = 0; terminalY++;
while (terminalY >= TERMINAL_HEIGHT)
terminalScroll();
} else if (chn==13) { // carriage return
terminalX = 0;
} else if (chn==27) {
terminalControlChars[0] = 27;
} else if (chn==19 || chn==17) { // XOFF/XON
} else {
// Else actually add character
JsGraphics gfx;
if (terminalGetGFX(&gfx)) {
short cx = (short)(TERMINAL_OFFSET_X + terminalX*TERMINAL_CHAR_W);
short cy = (short)(TERMINAL_OFFSET_Y + terminalY*TERMINAL_CHAR_H + gfx.data.height - LCD_HEIGHT);
// draw char
unsigned int cf = gfx.data.fgColor, cb = gfx.data.bgColor;
#ifdef GRAPHICS_THEME
gfx.data.fgColor = graphicsTheme.fg;
gfx.data.bgColor = graphicsTheme.bg;
#else
gfx.data.fgColor = -1; // always white on black
gfx.data.bgColor = 0;
#endif
TERMINAL_CHAR_CMD(&gfx, cx, cy, chn, 1, 1, true/*solid background - so no need to clear*/);
gfx.data.fgColor = cf;
gfx.data.bgColor = cb;
terminalSetGFX(&gfx);
}
if (terminalX<255) terminalX++;
}
} else if (terminalControlChars[0]==27) {
if (terminalControlChars[1]==91) {
if (terminalControlChars[2]==63) {
if (terminalControlChars[3]==55) {
// if (chn!=108) jsiConsolePrintf("Expected 27, 91, 63, 55, 108 - no line overflow sequence\n");
terminalControlCharsReset();
} else {
if (chn==55) {
terminalControlChars[3] = 55;
} else terminalControlCharsReset();
}
} else {
if (chn == 63) {
terminalControlChars[2] = 63;
} else {
terminalControlCharsReset();
switch (chn) {
case 65: if (terminalY > 0) terminalY--; break;
case 66: terminalY++; while (terminalY >= TERMINAL_HEIGHT) terminalScroll(); break; // down
case 67: if (terminalX<255) terminalX++; break; // right
case 68: if (terminalX > 0) terminalX--; break; // left
case 74: { // delete all to right and down
JsGraphics gfx;
if (terminalGetGFX(&gfx)) {
short cx = (short)(TERMINAL_OFFSET_X + terminalX*TERMINAL_CHAR_W);
short cy = (short)(TERMINAL_OFFSET_Y + terminalY*TERMINAL_CHAR_H + gfx.data.height - LCD_HEIGHT);
short w = (gfx.data.flags & JSGRAPHICSFLAGS_SWAP_XY) ? gfx.data.height : gfx.data.width;
short h = (gfx.data.flags & JSGRAPHICSFLAGS_SWAP_XY) ? gfx.data.width : gfx.data.height;
// Clear to right and down
graphicsFillRect(&gfx, cx, cy, w-1, cy+TERMINAL_CHAR_H-1, 0/*black*/); // current line
graphicsFillRect(&gfx, TERMINAL_OFFSET_X, cy+TERMINAL_CHAR_H, w-1, h-1, 0/*black*/); // everything under
terminalSetGFX(&gfx);
}
} break;
}
}
}
} else {
switch (chn) {
case 91: terminalControlChars[1] = 91; break;
default: terminalControlCharsReset(); break;
}
}
} else {
terminalControlCharsReset();
}
}
/*JSON{
"type" : "init",
"generate" : "jswrap_terminal_init"
}*/
void jswrap_terminal_init() {
terminalControlCharsReset();
terminalX = 0;
terminalY = (unsigned char)(TERMINAL_HEIGHT-1);
}
/*JSON{
"type" : "idle",
"generate" : "jswrap_terminal_idle"
}*/
bool jswrap_terminal_idle() {
if (terminalNeedsFlip) {
#ifdef ESPR_GRAPHICS_INTERNAL
graphicsInternalFlip();
#else
JsGraphics gfx;
if (terminalGetGFX(&gfx)) {
JsVar *flip = jsvObjectGetChildIfExists(gfx.graphicsVar, "flip");
if (flip) jsvUnLock2(jspExecuteFunction(flip,gfx.graphicsVar,0,0),flip);
jsvUnLock(gfx.graphicsVar);
}
#endif
terminalNeedsFlip = false;
}
return false;
}