FabGL
ESP32 Display Controller and Graphics Library
terminal.cpp
1/*
2 Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3 Copyright (c) 2019-2022 Fabrizio Di Vittorio.
4 All rights reserved.
5
6
7* Please contact fdivitto2013@gmail.com if you need a commercial license.
8
9
10* This library and related software is available under GPL v3.
11
12 FabGL is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 FabGL is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27#include <stdarg.h>
28#include <string.h>
29
30#include "freertos/FreeRTOS.h"
31#include "freertos/task.h"
32#include "freertos/timers.h"
33
34#include "esp_attr.h"
35#if __has_include("esp32/rom/uart.h")
36 #include "esp32/rom/uart.h"
37#else
38 #include "rom/uart.h"
39#endif
40#include "soc/uart_reg.h"
41#include "soc/uart_struct.h"
42#include "soc/io_mux_reg.h"
43#include "soc/gpio_sig_map.h"
44#include "soc/dport_reg.h"
45#include "soc/rtc.h"
46#include "esp_intr_alloc.h"
47
48
49#include "fabutils.h"
50#include "terminal.h"
51#include "devdrivers/mouse.h"
52
53
54
55#pragma GCC optimize ("O2")
56
57
58namespace fabgl {
59
60
61// Terminal identification ID
62// 64 = VT420
63// 1 = support for 132 columns
64// 6 = selective erase
65// 22 = color
66const char TERMID[] = "?64;1;6;22c";
67
68// to send 8 bit (S8C1T) or 7 bit control characters
69const char CSI_7BIT[] = "\e[";
70const char CSI_8BIT[] = "\x9B";
71const char DCS_7BIT[] = "\eP";
72const char DCS_8BIT[] = "\x90";
73const char SS2_7BIT[] = "\eN";
74const char SS2_8BIT[] = "\x8E";
75const char SS3_7BIT[] = "\eO";
76const char SS3_8BIT[] = "\x8F";
77const char ST_7BIT[] = "\e\\";
78const char ST_8BIT[] = "\x9C";
79const char OSC_7BIT[] = "\e]";
80const char OSC_8BIT[] = "\x9D";
81
82
83
84#define ISCTRLCHAR(c) ((c) <= ASCII_US || (c) == ASCII_DEL)
85
86
87// Map "DEC Special Graphics Character Set" to CP437
88static const uint8_t DECGRAPH_TO_CP437[255] = {0, 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,
89 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
90 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
91 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94,
92 /* 95 */ 32, /* 96 */ 4, /* 97 */ 177, /* 98 (not implemented) */ 63, /* 99 (not implemented) */ 63,
93 /* 100 (not implemented) */ 63, /* 101 (not implemented) */ 63, /* 102 */ 248, /* 103 */ 241,
94 /* 104 (not implemented) */ 63, /* 105 (not implemented) */ 63, /* 106 */ 217, /* 107 */ 191, /* 108 */ 218,
95 /* 109 */ 192, /* 110 */ 197, /* 111 (not implemented) */ 63, /* 112 (not implemented) */ 63, /* 113 */ 196,
96 /* 114 (not implemented) */ 63, /* 115 (not implemented) */ 63, /* 116 */ 195, /* 117 */ 180, /* 118 */ 193,
97 /* 119 */ 194, /* 120 */ 179, /* 121 */ 243, /* 122 */ 242, /* 123 */ 227, /* 124 (not implemented) */ 63,
98 /* 125 */ 156, /* 126 */ 249};
99
100
101const char * CTRLCHAR_TO_STR[] = {"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BELL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "XON", "DC2",
102 "XOFF", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US", "SPC"};
103
104
105// '_' (APC, ESC+0x5f is DEC-ANSI code for APC - Application Program Command)
106// it should terminate with "ST", but we don't!
107#define FABGLEXT_STARTCODE '_'
108#define FABGLEXT_CMD "\e_"
109#define FABGLEXT_ENDCODE '$'
110#define FABGLEXT_REPLYCODE '$'
111
112
113// sub commands of FABGLEXT_STARTCODE (user command):
114#define FABGLEXT_USERSEQ '#'
115// sub commands of FABGLEXT_STARTCODE (binary encoded parameters):
116#define FABGLEXTB_GETCURSORPOS 'a'
117#define FABGLEXTB_GETCURSORCOL 'b'
118#define FABGLEXTB_GETCURSORROW 'c'
119#define FABGLEXTB_SETCURSORPOS 'd'
120#define FABGLEXTB_INSERTSPACE 'e'
121#define FABGLEXTB_DELETECHAR 'f'
122#define FABGLEXTB_CURSORLEFT 'g'
123#define FABGLEXTB_CURSORRIGHT 'h'
124#define FABGLEXTB_SETCHAR 'i'
125#define FABGLEXTB_DISABLEFABSEQ 'j'
126#define FABGLEXTB_SETTERMTYPE 'k'
127#define FABGLEXTB_ISVKDOWN 'K'
128#define FABGLEXTB_SETFGCOLOR 'l'
129#define FABGLEXTB_SETBGCOLOR 'm'
130#define FABGLEXTB_SETCHARSTYLE 'n'
131// sub commands of FABGLEXT_STARTCODE (text encoded parameters):
132#define FABGLEXTX_SETUPADC 'A'
133#define FABGLEXTX_CLEAR 'B'
134#define FABGLEXTX_READADC 'C'
135#define FABGLEXTX_SETUPGPIO 'D'
136#define FABGLEXTX_ENABLECURSOR 'E'
137#define FABGLEXTX_SETCURSORPOS 'F'
138#define FABGLEXTX_GRAPHICSCMD 'G'
139#define FABGLEXTX_SHOWMOUSE 'H'
140#define FABGLEXTX_GETMOUSEPOS 'M'
141#define FABGLEXTX_GETGPIO 'R'
142#define FABGLEXTX_SOUND 'S'
143#define FABGLEXTX_SETGPIO 'W'
144#define FABGLEXTX_DELAY 'Y'
145
146// maximum length of sub commands (includes ending zero)
147#define FABGLEXT_MAXSUBCMDLEN 16
148
149// sub commands of FABGLEXTX_GRAPHICSCMD
150#define FABGLEXT_GCLEAR "CLEAR"
151#define FABGLEXT_GSETBRUSHCOLOR "BRUSH"
152#define FABGLEXT_GSETPENCOLOR "PEN"
153#define FABGLEXT_GSETPIXEL "PIXEL"
154#define FABGLEXT_GSCROLL "SCROLL"
155#define FABGLEXT_GPENWIDTH "PENW"
156#define FABGLEXT_GLINE "LINE"
157#define FABGLEXT_GRECT "RECT"
158#define FABGLEXT_GFILLRECT "FILLRECT"
159#define FABGLEXT_GELLIPSE "ELLIPSE"
160#define FABGLEXT_GFILLELLIPSE "FILLELLIPSE"
161#define FABGLEXT_GPATH "PATH"
162#define FABGLEXT_GFILLPATH "FILLPATH"
163#define FABGLEXT_GSPRITECOUNT "SPRITECOUNT"
164#define FABGLEXT_GSPRITEDEF "SPRITEDEF"
165#define FABGLEXT_GSPRITESET "SPRITESET"
166
167
168// specifies color attribute index for m_coloredAttributesColor[]
169#define COLORED_ATTRIBUTE_BOLD 0
170#define COLORED_ATTRIBUTE_FAINT 1
171#define COLORED_ATTRIBUTE_ITALIC 2
172#define COLORED_ATTRIBUTE_UNDERLINE 3
173#define COLORED_ATTRIBUTE_INVALID 4
174
175
176
177
178Terminal * Terminal::s_activeTerminal = nullptr;
179
180
182
184
186
187
188
189Terminal::Terminal()
190 : m_canvas(nullptr),
191 m_mutex(nullptr),
192 m_uartRXEnabled(true),
193 m_soundGenerator(nullptr),
194 m_sprites(nullptr),
195 m_spritesCount(0)
196{
197 if (s_activeTerminal == nullptr)
198 s_activeTerminal = this;
199}
200
201
202Terminal::~Terminal()
203{
204 // end() called?
205 if (m_mutex)
206 end();
207
208 if (m_soundGenerator)
209 delete m_soundGenerator;
210
211 freeSprites();
212}
213
214
215void Terminal::activate(TerminalTransition transition)
216{
217 xSemaphoreTake(m_mutex, portMAX_DELAY);
218 if (s_activeTerminal != this) {
219
220 if (s_activeTerminal && transition != TerminalTransition::None) {
221 if (m_bitmappedDisplayController) {
222 // bitmapped controller, use Canvas to perform the animation
223 s_activeTerminal = nullptr;
224 switch (transition) {
225 case TerminalTransition::LeftToRight:
226 for (int x = 0; x < m_columns; ++x) {
227 m_canvas->scroll(m_font.width, 0);
228 m_canvas->setOrigin(-m_font.width * (m_columns - x - 1), 0);
229 for (int y = 0; y < m_rows; ++y)
230 m_canvas->renderGlyphsBuffer(m_columns - x - 1, y, &m_glyphsBuffer);
231 m_canvas->waitCompletion(false);
232 vTaskDelay(2 / portTICK_PERIOD_MS);
233 }
234 break;
235 case TerminalTransition::RightToLeft:
236 for (int x = 0; x < m_columns; ++x) {
237 m_canvas->scroll(-m_font.width, 0);
238 m_canvas->setOrigin(m_font.width * (m_columns - x - 1), 0);
239 for (int y = 0; y < m_rows; ++y)
240 m_canvas->renderGlyphsBuffer(x, y, &m_glyphsBuffer);
241 m_canvas->waitCompletion(false);
242 vTaskDelay(2 / portTICK_PERIOD_MS);
243 }
244 break;
245 default:
246 break;
247 }
248 } else {
249 // textual controller, use temporary buffer to perform animation
250 auto txtCtrl = static_cast<TextualDisplayController*>(m_displayController);
251 auto map = (uint32_t*) heap_caps_malloc(sizeof(uint32_t) * m_columns * m_rows, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
252 memcpy(map, s_activeTerminal->m_glyphsBuffer.map, sizeof(uint32_t) * m_columns * m_rows);
253 txtCtrl->enableCursor(false);
254 txtCtrl->setTextMap(map, m_rows);
255 switch (transition) {
256 case TerminalTransition::LeftToRight:
257 for (int x = 0; x < m_columns; ++x) {
258 for (int y = 0; y < m_rows; ++y) {
259 memmove(map + y * m_columns + 1, map + y * m_columns, sizeof(uint32_t) * (m_columns - 1));
260 map[y * m_columns] = m_glyphsBuffer.map[y * m_columns + m_columns - x - 1];
261 }
262 vTaskDelay(5);
263 }
264 break;
265 case TerminalTransition::RightToLeft:
266 for (int x = 0; x < m_columns; ++x) {
267 for (int y = 0; y < m_rows; ++y) {
268 memmove(map + y * m_columns, map + y * m_columns + 1, sizeof(uint32_t) * (m_columns - 1));
269 map[y * m_columns + m_columns - 1] = m_glyphsBuffer.map[y * m_columns + x];
270 }
271 vTaskDelay(5);
272 }
273 break;
274 default:
275 break;
276 }
277 txtCtrl->setTextMap(m_glyphsBuffer.map, m_rows);
278 free(map);
279 }
280 }
281
282 s_activeTerminal = this;
283 vTaskResume(m_keyboardReaderTaskHandle);
284 syncDisplayController();
285 }
286 xSemaphoreGive(m_mutex);
287}
288
289
290void Terminal::deactivate()
291{
292 xSemaphoreTake(m_mutex, portMAX_DELAY);
293 if (s_activeTerminal == this) {
294 s_activeTerminal = nullptr;
295 }
296 xSemaphoreGive(m_mutex);
297}
298
299
300// synchronize display controller (and canvas) state
301void Terminal::syncDisplayController()
302{
303 if (m_bitmappedDisplayController) {
304 // restore canvas state for bitmapped display controller
305 m_canvas->reset();
306 m_canvas->setGlyphOptions(m_glyphOptions);
307 m_canvas->setBrushColor(m_emuState.backgroundColor);
308 m_canvas->setPenColor(m_emuState.foregroundColor);
309 } else {
310 // restore textual display controller state
311 auto txtCtrl = static_cast<TextualDisplayController*>(m_displayController);
312 txtCtrl->setTextMap(m_glyphsBuffer.map, m_rows);
313 txtCtrl->setCursorBackground(m_emuState.backgroundColor);
314 txtCtrl->setCursorForeground(m_emuState.foregroundColor);
315 txtCtrl->setCursorPos(m_emuState.cursorY - 1, m_emuState.cursorX - 1);
316 txtCtrl->enableCursor(m_emuState.cursorEnabled);
317 }
318 updateCanvasScrollingRegion();
319 refresh();
320}
321
322
323bool Terminal::begin(BaseDisplayController * displayController, int maxColumns, int maxRows, Keyboard * keyboard)
324{
325 m_displayController = displayController;
326 m_bitmappedDisplayController = (m_displayController->controllerType() == DisplayControllerType::Bitmapped);
327
328 m_maxColumns = maxColumns;
329 m_maxRows = maxRows;
330
331 if (m_bitmappedDisplayController) {
332 m_canvas = new Canvas(static_cast<BitmappedDisplayController*>(m_displayController));
333 } else {
334 m_canvas = nullptr;
335 static_cast<TextualDisplayController*>(m_displayController)->adjustMapSize(&m_maxColumns, &m_maxRows);
336 }
337
338 m_keyboard = keyboard;
339 if (m_keyboard == nullptr && PS2Controller::initialized()) {
340 // get default keyboard from PS/2 controller
341 m_keyboard = PS2Controller::keyboard();
342 }
343
344 m_logStream = nullptr;
345
346 m_glyphsBuffer = (GlyphsBuffer){0, 0, nullptr, 0, 0, nullptr};
347
348 m_emuState.tabStop = nullptr;
349 m_font.data = nullptr;
350
351 m_savedCursorStateList = nullptr;
352
353 m_alternateScreenBuffer = false;
354 m_alternateMap = nullptr;
355
356 m_flowControl = FlowControl::None;
357 m_sentXOFF = false;
358 m_recvXOFF = false;
359
360 m_lastWrittenChar = 0;
361
362 m_writeDetectedFabGLSeq = false;
363
364 // conformance level
365 m_emuState.conformanceLevel = 4; // VT400
366 m_emuState.ctrlBits = 7;
367
368 // cursor setup
369 m_cursorState = false;
370 m_emuState.cursorEnabled = false;
371
372 // colored attributes
373 m_coloredAttributesMaintainStyle = true;
374 m_coloredAttributesMask = 0;
375
376 m_mutex = xSemaphoreCreateMutex();
377
378 set132ColumnMode(false);
379
380 // blink support
381 m_blinkTimer = xTimerCreate("", pdMS_TO_TICKS(FABGLIB_DEFAULT_BLINK_PERIOD_MS), pdTRUE, this, blinkTimerFunc);
382 xTimerStart(m_blinkTimer, portMAX_DELAY);
383
384 // queue and task to consume input characters
385 m_inputQueue = xQueueCreate(Terminal::inputQueueSize, sizeof(uint8_t));
386 xTaskCreate(&charsConsumerTask, "", Terminal::inputConsumerTaskStackSize, this, FABGLIB_CHARS_CONSUMER_TASK_PRIORITY, &m_charsConsumerTaskHandle);
387
388 m_defaultBackgroundColor = Color::Black;
389 m_defaultForegroundColor = Color::White;
390
391 #ifdef ARDUINO
392 m_serialPort = nullptr;
393 #endif
394
395 m_keyboardReaderTaskHandle = nullptr;
396 m_uart = false;
397
398 m_outputQueue = nullptr;
399
400 m_termInfo = nullptr;
401
402 bool success = (m_glyphsBuffer.map != nullptr);
403
404 if (success)
405 reset();
406
407 return success;
408}
409
410
411void Terminal::end()
412{
413 if (m_keyboardReaderTaskHandle)
414 vTaskDelete(m_keyboardReaderTaskHandle);
415
416 xTimerDelete(m_blinkTimer, portMAX_DELAY);
417
418 clearSavedCursorStates();
419
420 vTaskDelete(m_charsConsumerTaskHandle);
421 vQueueDelete(m_inputQueue);
422
423 if (m_outputQueue)
424 vQueueDelete(m_outputQueue);
425
426 freeFont();
427 freeTabStops();
428 freeGlyphsMap();
429
430 vSemaphoreDelete(m_mutex);
431 m_mutex = nullptr;
432
433 delete m_canvas;
434
435 if (isActive())
436 s_activeTerminal = nullptr;
437}
438
439
440#ifdef ARDUINO
441void Terminal::connectSerialPort(HardwareSerial & serialPort, bool autoXONXOFF)
442{
443 if (m_serialPort)
444 vTaskDelete(m_keyboardReaderTaskHandle);
445 m_serialPort = &serialPort;
446 m_flowControl = autoXONXOFF ? FlowControl::Software : FlowControl::None;
447
448 m_serialPort->setRxBufferSize(Terminal::inputQueueSize);
449
450 if (!m_keyboardReaderTaskHandle && m_keyboard->isKeyboardAvailable())
451 xTaskCreate(&keyboardReaderTask, "", Terminal::keyboardReaderTaskStackSize, this, FABGLIB_KEYBOARD_READER_TASK_PRIORITY, &m_keyboardReaderTaskHandle);
452
453 // just in case a reset occurred after an XOFF
454 if (autoXONXOFF)
455 send(ASCII_XON);
456}
457#endif
458
459
460// returns number of bytes received (in the UART2 rx fifo buffer)
461inline int uartGetRXFIFOCount()
462{
463 auto uart = (volatile uart_dev_t *)(DR_REG_UART2_BASE);
464 return uart->status.rxfifo_cnt | ((int)(uart->mem_cnt_status.rx_cnt) << 8);
465}
466
467
468// flushes TX buffer of UART2
469static void uartFlushTXFIFO()
470{
471 auto uart = (volatile uart_dev_t *)(DR_REG_UART2_BASE);
472 while (uart->status.txfifo_cnt || uart->status.st_utx_out)
473 ;
474}
475
476
477// flushes RX buffer of UART2
478static void uartFlushRXFIFO()
479{
480 auto uart = (volatile uart_dev_t *)(DR_REG_UART2_BASE);
481 while (uartGetRXFIFOCount() != 0 || uart->mem_rx_status.wr_addr != uart->mem_rx_status.rd_addr)
482 uart->fifo.rw_byte;
483}
484
485
486// look into input queue (m_inputQueue): if there is space for new incoming bytes send XON and reenable uart RX interrupts
487void Terminal::uartCheckInputQueueForFlowControl()
488{
489 if (m_flowControl != FlowControl::None) {
490 auto uart = (volatile uart_dev_t *)(DR_REG_UART2_BASE);
491 if (uxQueueMessagesWaiting(m_inputQueue) == 0 && uart->int_ena.rxfifo_full == 0) {
492 if (m_sentXOFF)
493 flowControl(true); // enable RX
494 // enable RX FIFO full interrupt
495 SET_PERI_REG_MASK(UART_INT_ENA_REG(2), UART_RXFIFO_FULL_INT_ENA_M);
496 }
497 }
498}
499
500
501void Terminal::setRTSStatus(bool value)
502{
503 if (m_rtsPin != GPIO_UNUSED) {
504 m_RTSStatus = value;
505 gpio_set_level(m_rtsPin, !value); // low = asserted
506 }
507}
508
509
510// enable/disable RX sending XON/XOFF and/or setting RTS
511void Terminal::flowControl(bool enableRX)
512{
513 if (enableRX) {
514 if (m_flowControl == FlowControl::Software || m_flowControl == FlowControl::Hardsoft)
515 SET_PERI_REG_MASK(UART_FLOW_CONF_REG(2), UART_SEND_XON_M); // send XON
516 if (m_flowControl == FlowControl::Hardware || m_flowControl == FlowControl::Hardsoft)
517 setRTSStatus(true); // assert RTS
518 m_sentXOFF = false;
519 } else {
520 if (m_flowControl == FlowControl::Software || m_flowControl == FlowControl::Hardsoft)
521 SET_PERI_REG_MASK(UART_FLOW_CONF_REG(2), UART_SEND_XOFF_M); // send XOFF
522 if (m_flowControl == FlowControl::Hardware || m_flowControl == FlowControl::Hardsoft)
523 setRTSStatus(false); // disable RTS
524 m_sentXOFF = true;
525 }
526}
527
528
529// check if TX is enabled looking for XOFF received or reading CTS
530bool Terminal::flowControl()
531{
532 //Serial.printf("flowControl\n");
533 if ((m_flowControl == FlowControl::Software || m_flowControl == FlowControl::Hardsoft) && m_recvXOFF)
534 return false; // TX disabled (received XOFF)
535 if ((m_flowControl == FlowControl::Hardware || m_flowControl == FlowControl::Hardsoft) && CTSStatus() == false)
536 return false; // TX disabled (CTS=high, not active)
537 return true; // TX enabled
538}
539
540
541// connect to UART2
542void Terminal::connectSerialPort(uint32_t baud, int dataLength, char parity, float stopBits, int rxPin, int txPin, FlowControl flowControl, bool inverted, int rtsPin, int ctsPin)
543{
544 auto uart = (volatile uart_dev_t *) DR_REG_UART2_BASE;
545
546 bool initialSetup = !m_uart;
547
548 if (initialSetup) {
549 // uart not configured, configure now
550
551 #ifdef ARDUINO
552 Serial2.end();
553 #endif
554
555 m_uart = true;
556
557 DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_UART2_CLK_EN);
558 DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_UART2_RST);
559
560 // flush
561 uartFlushTXFIFO();
562 uartFlushRXFIFO();
563
564 // TX/RX Pin direction
565 configureGPIO(int2gpio(rxPin), GPIO_MODE_INPUT);
566 configureGPIO(int2gpio(txPin), GPIO_MODE_OUTPUT);
567
568 // RTS
569 m_rtsPin = int2gpio(rtsPin);
570 if (m_rtsPin != GPIO_UNUSED) {
571 configureGPIO(m_rtsPin, GPIO_MODE_OUTPUT);
572 setRTSStatus(true); // assert RTS
573 }
574
575 // CTS
576 m_ctsPin = int2gpio(ctsPin);
577 if (m_ctsPin != GPIO_UNUSED) {
578 configureGPIO(m_ctsPin, GPIO_MODE_INPUT);
579 }
580
581 // setup RX interrupt
582 WRITE_PERI_REG(UART_CONF1_REG(2), (1 << UART_RXFIFO_FULL_THRHD_S) | // an interrupt for each character received
583 (2 << UART_RX_TOUT_THRHD_S) | // actually not used
584 (0 << UART_RX_TOUT_EN_S)); // timeout not enabled
585 WRITE_PERI_REG(UART_INT_ENA_REG(2), (1 << UART_RXFIFO_FULL_INT_ENA_S) | // interrupt on FIFO full (1 character - see rxfifo_full_thrhd)
586 (1 << UART_FRM_ERR_INT_ENA_S) | // interrupt on frame error
587 (0 << UART_RXFIFO_TOUT_INT_ENA_S) | // no interrupt on rx timeout (see rx_tout_en and rx_tout_thrhd)
588 (1 << UART_PARITY_ERR_INT_ENA_S) | // interrupt on rx parity error
589 (1 << UART_RXFIFO_OVF_INT_ENA_S)); // interrupt on rx overflow
590 WRITE_PERI_REG(UART_INT_CLR_REG(2), 0xffffffff);
591 esp_intr_alloc_pinnedToCore(ETS_UART2_INTR_SOURCE, 0, uart_isr, this, nullptr, CoreUsage::quietCore());
592
593 // setup FIFOs size
594 WRITE_PERI_REG(UART_MEM_CONF_REG(2), (3 << UART_RX_SIZE_S) | // RX: 3 * 128 = 384 bytes (this is the max for UART2)
595 (1 << UART_TX_SIZE_S)); // TX: 1 * 128 = 128 bytes
596
597 if (!m_keyboardReaderTaskHandle && m_keyboard->isKeyboardAvailable())
598 xTaskCreate(&keyboardReaderTask, "", Terminal::keyboardReaderTaskStackSize, this, FABGLIB_KEYBOARD_READER_TASK_PRIORITY, &m_keyboardReaderTaskHandle);
599 }
600
601 m_flowControl = flowControl;
602
603 // set baud rate
604 uint32_t clk_div = (getApbFrequency() << 4) / baud;
605 WRITE_PERI_REG(UART_CLKDIV_REG(2), ((clk_div >> 4) << UART_CLKDIV_S) |
606 ((clk_div & 0xf) << UART_CLKDIV_FRAG_S));
607
608 // frame
609 uint32_t config0 = (1 << UART_TICK_REF_ALWAYS_ON_S) | ((dataLength - 5) << UART_BIT_NUM_S);
610 if (parity == 'E')
611 config0 |= (1 << UART_PARITY_EN_S);
612 else if (parity == 'O')
613 config0 |= (1 << UART_PARITY_EN_S) | (1 << UART_PARITY_S);
614 if (stopBits == 1.0)
615 config0 |= 1 << UART_STOP_BIT_NUM_S;
616 else if (stopBits == 1.5)
617 config0 |= 2 << UART_STOP_BIT_NUM_S;
618 else if (stopBits >= 2.0) {
619 config0 |= 1 << UART_STOP_BIT_NUM_S;
620 SET_PERI_REG_BITS(UART_RS485_CONF_REG(2), UART_DL1_EN_V, 1, UART_DL1_EN_S); // additional 1 stop bit
621 if (stopBits >= 3.0)
622 SET_PERI_REG_BITS(UART_RS485_CONF_REG(2), UART_DL0_EN_V, 1, UART_DL1_EN_S); // additional 1 stop bit
623 }
624 WRITE_PERI_REG(UART_CONF0_REG(2), config0);
625
626 // TX/RX Pin logic
627 gpio_matrix_in(rxPin, U2RXD_IN_IDX, inverted);
628 gpio_matrix_out(txPin, U2TXD_OUT_IDX, inverted, false);
629
630 // Flow Control
631 WRITE_PERI_REG(UART_FLOW_CONF_REG(2), 0);
632 if (flowControl != FlowControl::None) {
633 // we actually use manual software control, using send_xon/send_xoff bits to send control characters
634 // because we have to check both RX-FIFO and input queue
635 WRITE_PERI_REG(UART_SWFC_CONF_REG(2), (0 << UART_XON_THRESHOLD_S) |
636 (0 << UART_XOFF_THRESHOLD_S) |
637 (ASCII_XON << UART_XON_CHAR_S) |
638 (ASCII_XOFF << UART_XOFF_CHAR_S));
639 if (initialSetup) {
640 // send an XON right now
641 m_sentXOFF = true;
642 SET_PERI_REG_MASK(UART_FLOW_CONF_REG(2), UART_SEND_XON_M);
643 }
644 }
645
646 // APB Change callback (TODO?)
647 //addApbChangeCallback(this, uart_on_apb_change);
648}
649
650
651void Terminal::connectLocally()
652{
653 m_outputQueue = xQueueCreate(FABGLIB_TERMINAL_OUTPUT_QUEUE_SIZE, sizeof(uint8_t));
654 if (!m_keyboardReaderTaskHandle && m_keyboard->isKeyboardAvailable())
655 xTaskCreate(&keyboardReaderTask, "", Terminal::keyboardReaderTaskStackSize, this, FABGLIB_KEYBOARD_READER_TASK_PRIORITY, &m_keyboardReaderTaskHandle);
656}
657
658
659void Terminal::disconnectLocally()
660{
661 if (m_outputQueue)
662 vQueueDelete(m_outputQueue);
663 m_outputQueue = nullptr;
664}
665
666
667void Terminal::logFmt(const char * format, ...)
668{
669 if (m_logStream) {
670 va_list ap;
671 va_start(ap, format);
672 int size = vsnprintf(nullptr, 0, format, ap) + 1;
673 if (size > 0) {
674 va_end(ap);
675 va_start(ap, format);
676 char buf[size + 1];
677 vsnprintf(buf, size, format, ap);
678 m_logStream->write(buf);
679 }
680 va_end(ap);
681 }
682}
683
684
685void Terminal::log(const char * txt)
686{
687 if (m_logStream)
688 m_logStream->write(txt);
689}
690
691
692void Terminal::log(char c)
693{
694 if (m_logStream)
695 m_logStream->write(c);
696}
697
698
699void Terminal::freeFont()
700{
701 #if FABGLIB_CACHE_FONT_IN_RAM
702 if (m_font.data) {
703 free((void*) m_font.data);
704 m_font.data = nullptr;
705 }
706 #endif
707}
708
709
710void Terminal::freeTabStops()
711{
712 if (m_emuState.tabStop) {
713 free(m_emuState.tabStop);
714 m_emuState.tabStop = nullptr;
715 }
716}
717
718
719void Terminal::freeGlyphsMap()
720{
721 if (m_glyphsBuffer.map) {
722 free((void*) m_glyphsBuffer.map);
723 m_glyphsBuffer.map = nullptr;
724 }
725 if (m_alternateMap) {
726 free((void*) m_alternateMap);
727 m_alternateMap = nullptr;
728 }
729}
730
731
732void Terminal::reset()
733{
734 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
735 log("reset()\n");
736 #endif
737
738 xSemaphoreTake(m_mutex, portMAX_DELAY);
739 m_resetRequested = false;
740
741 m_emuState.originMode = false;
742 m_emuState.wraparound = true;
743 m_emuState.insertMode = false;
744 m_emuState.newLineMode = false;
745 m_emuState.smoothScroll = false;
746 m_emuState.keypadMode = KeypadMode::Numeric;
747 m_emuState.cursorKeysMode = false;
748 m_emuState.keyAutorepeat = true;
749 m_emuState.cursorBlinkingEnabled = true;
750 m_emuState.cursorStyle = 0;
751 m_emuState.allow132ColumnMode = false;
752 m_emuState.reverseWraparoundMode = false;
753 m_emuState.backarrowKeyMode = false;
754 m_emuState.ANSIMode = true;
755 m_emuState.VT52GraphicsMode = false;
756 m_emuState.allowFabGLSequences = 1; // enabled for default
757 m_emuState.characterSetIndex = 0; // Select G0
758 for (int i = 0; i < 4; ++i)
759 m_emuState.characterSet[i] = 1; // G0, G1, G2 and G3 = USASCII
760
761 m_lastPressedKey = VK_NONE;
762
763 m_blinkingTextVisible = false;
764 m_blinkingTextEnabled = true;
765
766 m_cursorState = false;
767
768 m_convMatchedCount = 0;
769 m_convMatchedItem = nullptr;
770
771 // this also restore cursor at top-left
772 setScrollingRegion(1, m_rows);
773
774 resetTabStops();
775
776 m_glyphOptions = (GlyphOptions) {{
777 .fillBackground = 1,
778 .bold = 0,
779 .reduceLuminosity = 0,
780 .italic = 0,
781 .invert = 0,
782 .blank = 0,
783 .underline = 0,
784 .doubleWidth = 0,
785 .userOpt1 = 0, // blinking
786 .userOpt2 = 0, // 0 = erasable by DECSED or DECSEL, 1 = not erasable by DECSED or DECSEL
787 }};
788 if (m_canvas)
789 m_canvas->setGlyphOptions(m_glyphOptions);
790
791 m_paintOptions = PaintOptions();
792
793 reverseVideo(false);
794
795 int_setBackgroundColor(m_defaultBackgroundColor);
796 int_setForegroundColor(m_defaultForegroundColor);
797
798 clearSavedCursorStates();
799
800 int_clear();
801
802 xSemaphoreGive(m_mutex);
803}
804
805
806void Terminal::loadFont(FontInfo const * font)
807{
808 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
809 log("loadFont()\n");
810 #endif
811
812 int codepage = 0;
813
814 if (m_bitmappedDisplayController) {
815
816 // bitmapped display controller
817
818 freeFont();
819
820 m_font = *font;
821 #if FABGLIB_CACHE_FONT_IN_RAM
822 int size = m_font.height * 256 * ((m_font.width + 7) / 8);
823 m_font.data = (uint8_t const*) malloc(size);
824 memcpy((void*)m_font.data, font->data, size);
825 #else
826 m_font.data = font->data;
827 #endif
828
829 m_columns = m_canvas->getWidth() / m_font.width;
830 m_rows = m_canvas->getHeight() / m_font.height;
831
832 m_glyphsBuffer.glyphsWidth = m_font.width;
833 m_glyphsBuffer.glyphsHeight = m_font.height;
834 m_glyphsBuffer.glyphsData = m_font.data;
835
836 codepage = m_font.codepage;
837
838 } else {
839
840 // textual display controller
841
842 auto txtctrl = static_cast<TextualDisplayController*>(m_displayController);
843
844 m_columns = txtctrl->getColumns();
845 m_rows = txtctrl->getRows();
846 codepage = txtctrl->getFont()->codepage;
847
848 }
849
850 if (m_keyboard)
851 m_keyboard->setCodePage(CodePages::get(codepage));
852
853 // check maximum columns and rows
854 if (m_maxColumns > 0 && m_maxColumns < m_columns)
855 m_columns = m_maxColumns;
856 if (m_maxRows > 0 && m_maxRows < m_rows)
857 m_rows = m_maxRows;
858
859 freeTabStops();
860 m_emuState.tabStop = (uint8_t*) malloc(m_columns);
861 resetTabStops();
862
863 freeGlyphsMap();
864 while (true) {
865 m_glyphsBuffer.map = (uint32_t*) heap_caps_malloc(sizeof(uint32_t) * m_columns * m_rows, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
866 if (m_glyphsBuffer.map)
867 break;
868 // no enough memory, reduce m_rows
869 --m_rows;
870 }
871 m_glyphsBuffer.columns = m_columns;
872 m_glyphsBuffer.rows = m_rows;
873
874 m_alternateMap = nullptr;
875 m_alternateScreenBuffer = false;
876
877 if (!m_bitmappedDisplayController && isActive()) {
878 // associate map with textual display controller
879 static_cast<TextualDisplayController*>(m_displayController)->setTextMap(m_glyphsBuffer.map, m_rows);
880 }
881
882 setScrollingRegion(1, m_rows);
883 int_clear();
884}
885
886
887void Terminal::flush(bool waitVSync)
888{
889 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
890 log("flush()\n");
891 #endif
892 if (m_bitmappedDisplayController && isActive()) {
893 while (uxQueueMessagesWaiting(m_inputQueue) > 0)
894 ;
895 m_canvas->waitCompletion(waitVSync);
896 }
897}
898
899
900// false = setup for 80 columns mode
901// true = setup for 132 columns mode
902void Terminal::set132ColumnMode(bool value)
903{
904 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
905 log("set132ColumnMode()\n");
906 #endif
907
908 if (m_bitmappedDisplayController) {
909 if (value) {
910 // 132x25 is forced because ANSI compatibility
911 m_maxColumns = 132;
912 m_maxRows = 25;
913 }
914 loadFont(getPresetFontInfo(m_canvas->getWidth(), m_canvas->getHeight(), (value ? 132 : 80), 25));
915 } else
916 loadFont(nullptr);
917}
918
919
920void Terminal::setBackgroundColor(Color color, bool setAsDefault)
921{
922 if (setAsDefault)
923 m_defaultBackgroundColor = color;
925}
926
927
928void Terminal::int_setBackgroundColor(Color color)
929{
930 m_emuState.backgroundColor = color;
931 if (isActive()) {
932 if (m_bitmappedDisplayController)
933 m_canvas->setBrushColor(color);
934 else
935 static_cast<TextualDisplayController*>(m_displayController)->setCursorBackground(color);
936 }
937}
938
939
940void Terminal::setForegroundColor(Color color, bool setAsDefault)
941{
942 if (setAsDefault)
943 m_defaultForegroundColor = color;
945}
946
947
948void Terminal::int_setForegroundColor(Color color)
949{
950 m_emuState.foregroundColor = color;
951 if (isActive()) {
952 if (m_bitmappedDisplayController)
953 m_canvas->setPenColor(color);
954 else
955 static_cast<TextualDisplayController*>(m_displayController)->setCursorForeground(color);
956 }
957}
958
959
960int CharStyleToColorAttributeIndex(CharStyle attribute)
961{
962 switch (attribute) {
963 case CharStyle::Bold:
964 return COLORED_ATTRIBUTE_BOLD;
966 return COLORED_ATTRIBUTE_FAINT;
968 return COLORED_ATTRIBUTE_ITALIC;
970 return COLORED_ATTRIBUTE_UNDERLINE;
971 default:
972 return COLORED_ATTRIBUTE_INVALID;
973 }
974}
975
976
977void Terminal::setColorForAttribute(CharStyle attribute, Color color, bool maintainStyle)
978{
979 int cindex = CharStyleToColorAttributeIndex(attribute);
980 if (cindex != COLORED_ATTRIBUTE_INVALID) {
981 m_coloredAttributesMask |= 1 << cindex;
982 m_coloredAttributesColor[cindex] = color;
983 m_coloredAttributesMaintainStyle = maintainStyle;
984 }
985}
986
987
988void Terminal::setColorForAttribute(CharStyle attribute)
989{
990 int cindex = CharStyleToColorAttributeIndex(attribute);
991 if (cindex != COLORED_ATTRIBUTE_INVALID)
992 m_coloredAttributesMask &= ~(1 << cindex);
993}
994
995
996void Terminal::reverseVideo(bool value)
997{
998 if (m_paintOptions.swapFGBG != value) {
999 m_paintOptions.swapFGBG = value;
1000 if (isActive()) {
1001 if (m_bitmappedDisplayController) {
1002 m_canvas->setPaintOptions(m_paintOptions);
1003 m_canvas->swapRectangle(0, 0, m_canvas->getWidth() - 1, m_canvas->getHeight() - 1);
1004 }
1005 }
1006 }
1007}
1008
1009
1010void Terminal::clear(bool moveCursor)
1011{
1012 TerminalController(this).clear();
1013 if (moveCursor)
1014 TerminalController(this).setCursorPos(1, 1);
1015}
1016
1017
1018void Terminal::int_clear()
1019{
1020 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1021 log("int_clear()\n");
1022 #endif
1023
1024 if (m_bitmappedDisplayController && isActive())
1025 m_canvas->clear();
1026 clearMap(m_glyphsBuffer.map);
1027}
1028
1029
1030void Terminal::clearMap(uint32_t * map)
1031{
1032 uint32_t itemValue = GLYPHMAP_ITEM_MAKE(ASCII_SPC, m_emuState.backgroundColor, m_emuState.foregroundColor, m_glyphOptions);
1033 uint32_t * mapItemPtr = map;
1034 for (int row = 0; row < m_rows; ++row)
1035 for (int col = 0; col < m_columns; ++col, ++mapItemPtr)
1036 *mapItemPtr = itemValue;
1037}
1038
1039
1040// return True if scroll down required
1041bool Terminal::moveUp()
1042{
1043 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1044 log("moveUp()\n");
1045 #endif
1046
1047 if (m_emuState.cursorY == m_emuState.scrollingRegionTop)
1048 return true;
1049 setCursorPos(m_emuState.cursorX, m_emuState.cursorY - 1);
1050 return false;
1051}
1052
1053
1054// return True if scroll up required
1055bool Terminal::moveDown()
1056{
1057 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1058 log("moveDown()\n");
1059 #endif
1060
1061 if (m_emuState.cursorY == m_emuState.scrollingRegionDown || m_emuState.cursorY == m_rows)
1062 return true;
1063 setCursorPos(m_emuState.cursorX, m_emuState.cursorY + 1);
1064 return false;
1065}
1066
1067
1068// Move cursor at left or right, wrapping lines if necessary
1069void Terminal::move(int offset)
1070{
1071 int pos = m_emuState.cursorX - 1 + (m_emuState.cursorY - 1) * m_columns + offset; // pos is zero based
1072 int newY = pos / m_columns + 1;
1073 int newX = pos % m_columns + 1;
1074 if (newY < m_emuState.scrollingRegionTop) {
1075 newX = 1;
1076 newY = m_emuState.scrollingRegionTop;
1077 }
1078 if (newY > m_emuState.scrollingRegionDown) {
1079 newX = m_columns;
1080 newY = m_emuState.scrollingRegionDown;
1081 }
1082 setCursorPos(newX, newY);
1083}
1084
1085
1086void Terminal::setCursorPos(int X, int Y)
1087{
1088 m_emuState.cursorX = tclamp(X, 1, (int)m_columns);
1089 m_emuState.cursorY = tclamp(Y, 1, (int)m_rows);
1090 m_emuState.cursorPastLastCol = false;
1091
1092 if (!m_bitmappedDisplayController && isActive())
1093 static_cast<TextualDisplayController*>(m_displayController)->setCursorPos(m_emuState.cursorY - 1, m_emuState.cursorX - 1);
1094
1095 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCSALL
1096 logFmt("setCursorPos(%d, %d) => set to (%d, %d)\n", X, Y, m_emuState.cursorX, m_emuState.cursorY);
1097 #endif
1098}
1099
1100
1101int Terminal::getAbsoluteRow(int Y)
1102{
1103 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1104 logFmt("getAbsoluteRow(%d)\n", Y);
1105 #endif
1106
1107 if (m_emuState.originMode) {
1108 Y += m_emuState.scrollingRegionTop - 1;
1109 Y = tclamp(Y, m_emuState.scrollingRegionTop, m_emuState.scrollingRegionDown);
1110 }
1111 return Y;
1112}
1113
1114
1115void Terminal::enableCursor(bool value)
1116{
1117 TerminalController(this).enableCursor(value);
1118}
1119
1120
1121bool Terminal::int_enableCursor(bool value)
1122{
1123 bool prev = m_emuState.cursorEnabled;
1124 if (m_emuState.cursorEnabled != value) {
1125 m_emuState.cursorEnabled = value;
1126 if (m_bitmappedDisplayController) {
1127 // bitmapped display, simulated cursor
1128 if (m_emuState.cursorEnabled) {
1129 if (uxQueueMessagesWaiting(m_inputQueue) == 0) {
1130 // just to show the cursor before next blink
1131 blinkCursor();
1132 }
1133 } else {
1134 if (m_cursorState) {
1135 // make sure cursor is hidden
1136 blinkCursor();
1137 }
1138 }
1139 } else if (isActive()) {
1140 // textual display, native cursor
1141 static_cast<TextualDisplayController*>(m_displayController)->enableCursor(value);
1142 }
1143 }
1144 return prev;
1145}
1146
1147
1148bool Terminal::enableBlinkingText(bool value)
1149{
1150 bool prev = m_blinkingTextEnabled;
1151 m_blinkingTextEnabled = value;
1152 return prev;
1153}
1154
1155
1156// callback for blink timer
1157void Terminal::blinkTimerFunc(TimerHandle_t xTimer)
1158{
1159 Terminal * term = (Terminal*) pvTimerGetTimerID(xTimer);
1160
1161 if (term->isActive() && xSemaphoreTake(term->m_mutex, 0) == pdTRUE) {
1162 // cursor blink
1163 if (term->m_emuState.cursorEnabled && term->m_emuState.cursorBlinkingEnabled)
1164 term->blinkCursor();
1165
1166 // text blink
1167 if (term->m_blinkingTextEnabled)
1168 term->blinkText();
1169
1170 xSemaphoreGive(term->m_mutex);
1171
1172 }
1173}
1174
1175
1176void Terminal::blinkCursor()
1177{
1178 if (m_bitmappedDisplayController && isActive()) {
1179 m_cursorState = !m_cursorState;
1180 int X = (m_emuState.cursorX - 1) * m_font.width;
1181 int Y = (m_emuState.cursorY - 1) * m_font.height;
1182 switch (m_emuState.cursorStyle) {
1183 case 0 ... 2:
1184 // block cursor
1185 m_canvas->swapRectangle(X, Y, X + m_font.width - 1, Y + m_font.height - 1);
1186 break;
1187 case 3 ... 4:
1188 // underline cursor
1189 m_canvas->swapRectangle(X, Y + m_font.height - 2, X + m_font.width - 1, Y + m_font.height - 1);
1190 break;
1191 case 5 ... 6:
1192 // bar cursor
1193 m_canvas->swapRectangle(X, Y, X + 1, Y + m_font.height - 1);
1194 break;
1195 }
1196 }
1197}
1198
1199
1200void Terminal::blinkText()
1201{
1202 if (isActive()) {
1203 m_blinkingTextVisible = !m_blinkingTextVisible;
1204 bool keepEnabled = false;
1205 int rows = m_rows;
1206 int cols = m_columns;
1207 if (m_bitmappedDisplayController)
1208 m_canvas->beginUpdate();
1209 for (int y = 0; y < rows; ++y) {
1210 uint32_t * itemPtr = m_glyphsBuffer.map + y * cols;
1211 for (int x = 0; x < cols; ++x, ++itemPtr) {
1212 // character to blink?
1213 GlyphOptions glyphOptions = glyphMapItem_getOptions(itemPtr);
1214 if (glyphOptions.userOpt1) {
1215 glyphOptions.blank = !m_blinkingTextVisible;
1216 glyphMapItem_setOptions(itemPtr, glyphOptions);
1217 refresh(x + 1, y + 1);
1218 keepEnabled = true;
1219 }
1220 }
1221 if (m_bitmappedDisplayController)
1222 m_canvas->waitCompletion(false);
1223 }
1224 if (m_bitmappedDisplayController)
1225 m_canvas->endUpdate();
1226 if (!keepEnabled)
1227 m_blinkingTextEnabled = false;
1228 }
1229}
1230
1231
1232void Terminal::nextTabStop()
1233{
1234 int actualColumns = m_columns;
1235
1236 // if double width for current line then consider half columns
1237 if (getGlyphOptionsAt(1, m_emuState.cursorY).doubleWidth)
1238 actualColumns /= 2;
1239
1240 int x = m_emuState.cursorX;
1241 while (x < actualColumns) {
1242 ++x;
1243 if (m_emuState.tabStop[x - 1])
1244 break;
1245 }
1246 setCursorPos(x, m_emuState.cursorY);
1247}
1248
1249
1250// setup tab stops. One every 8.
1251void Terminal::resetTabStops()
1252{
1253 for (int i = 0; i < m_columns; ++i)
1254 m_emuState.tabStop[i] = (i > 0 && (i % 8) == 0 ? 1 : 0);
1255}
1256
1257
1258// if column = 0 then clear all tab stops
1259void Terminal::setTabStop(int column, bool set)
1260{
1261 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1262 logFmt("setTabStop %d %d\n", column, (int)set);
1263 #endif
1264
1265 if (column == 0)
1266 memset(m_emuState.tabStop, 0, m_columns);
1267 else
1268 m_emuState.tabStop[column - 1] = set ? 1 : 0;
1269}
1270
1271
1272void Terminal::scrollDown()
1273{
1274 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1275 log("scrollDown\n");
1276 #endif
1277
1278 if (m_bitmappedDisplayController && isActive()) {
1279 // scroll down using canvas
1280 if (m_emuState.smoothScroll) {
1281 for (int i = 0; i < m_font.height; ++i)
1282 m_canvas->scroll(0, 1);
1283 } else
1284 m_canvas->scroll(0, m_font.height);
1285 }
1286
1287 // move down screen buffer
1288 for (int y = m_emuState.scrollingRegionDown - 1; y > m_emuState.scrollingRegionTop - 1; --y)
1289 memcpy(m_glyphsBuffer.map + y * m_columns, m_glyphsBuffer.map + (y - 1) * m_columns, m_columns * sizeof(uint32_t));
1290
1291 // insert a blank line in the screen buffer
1292 uint32_t itemValue = GLYPHMAP_ITEM_MAKE(ASCII_SPC, m_emuState.backgroundColor, m_emuState.foregroundColor, m_glyphOptions);
1293 uint32_t * itemPtr = m_glyphsBuffer.map + (m_emuState.scrollingRegionTop - 1) * m_columns;
1294 for (int x = 0; x < m_columns; ++x, ++itemPtr)
1295 *itemPtr = itemValue;
1296
1297}
1298
1299
1300// startingRow: represents an absolute value, not related to the scrolling region
1301void Terminal::scrollDownAt(int startingRow)
1302{
1303 int prevScrollingRegionTop = m_emuState.scrollingRegionTop;
1304 setScrollingRegion(startingRow, m_emuState.scrollingRegionDown, false);
1305
1306 scrollDown();
1307
1308 setScrollingRegion(prevScrollingRegionTop, m_emuState.scrollingRegionDown, false);
1309}
1310
1311
1312void Terminal::scrollUp()
1313{
1314 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1315 log("scrollUp\n");
1316 #endif
1317
1318 auto ldown = m_emuState.scrollingRegionDown;
1319 if (m_emuState.cursorY == m_rows) {
1320 // scrolling occurs always when at bottom row, even if it is out of scrolling region (xterm has the same behaviour)
1321 m_emuState.scrollingRegionDown = m_rows;
1322 updateCanvasScrollingRegion();
1323 }
1324
1325 if (m_bitmappedDisplayController && isActive()) {
1326 // scroll up using canvas
1327 if (m_emuState.smoothScroll) {
1328 for (int i = 0; i < m_font.height; ++i)
1329 m_canvas->scroll(0, -1);
1330 } else
1331 m_canvas->scroll(0, -m_font.height);
1332 }
1333
1334 // move up screen buffer
1335 for (int y = m_emuState.scrollingRegionTop - 1; y < m_emuState.scrollingRegionDown - 1; ++y)
1336 memcpy(m_glyphsBuffer.map + y * m_columns, m_glyphsBuffer.map + (y + 1) * m_columns, m_columns * sizeof(uint32_t));
1337
1338 // insert a blank line in the screen buffer
1339 uint32_t itemValue = GLYPHMAP_ITEM_MAKE(ASCII_SPC, m_emuState.backgroundColor, m_emuState.foregroundColor, m_glyphOptions);
1340 uint32_t * itemPtr = m_glyphsBuffer.map + (m_emuState.scrollingRegionDown - 1) * m_columns;
1341 for (int x = 0; x < m_columns; ++x, ++itemPtr)
1342 *itemPtr = itemValue;
1343
1344 if (ldown != m_emuState.scrollingRegionDown) {
1345 m_emuState.scrollingRegionDown = ldown;
1346 updateCanvasScrollingRegion();
1347 }
1348}
1349
1350
1351void Terminal::scrollUpAt(int startingRow)
1352{
1353 int prevScrollingRegionTop = m_emuState.scrollingRegionTop;
1354 setScrollingRegion(startingRow, m_emuState.scrollingRegionDown, false);
1355
1356 scrollUp();
1357
1358 setScrollingRegion(prevScrollingRegionTop, m_emuState.scrollingRegionDown, false);
1359}
1360
1361
1362void Terminal::setScrollingRegion(int top, int down, bool resetCursorPos)
1363{
1364 m_emuState.scrollingRegionTop = tclamp(top, 1, (int)m_rows);
1365 m_emuState.scrollingRegionDown = tclamp(down, 1, (int)m_rows);
1366 updateCanvasScrollingRegion();
1367
1368 if (resetCursorPos)
1369 setCursorPos(1, m_emuState.originMode ? m_emuState.scrollingRegionTop : 1);
1370
1371 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1372 logFmt("setScrollingRegion: %d %d => %d %d\n", top, down, m_emuState.scrollingRegionTop, m_emuState.scrollingRegionDown);
1373 #endif
1374}
1375
1376
1377void Terminal::updateCanvasScrollingRegion()
1378{
1379 if (m_bitmappedDisplayController && isActive())
1380 m_canvas->setScrollingRegion(0, (m_emuState.scrollingRegionTop - 1) * m_font.height, m_canvas->getWidth() - 1, m_emuState.scrollingRegionDown * m_font.height - 1);
1381}
1382
1383
1384// Insert a blank space at current position, moving next "charsToMove" characters to the right (even on multiple lines).
1385// Characters after "charsToMove" length are overwritter.
1386// Vertical scroll may occurs (in this case returns "True")
1387bool Terminal::multilineInsertChar(int charsToMove)
1388{
1389 bool scrolled = false;
1390 int col = m_emuState.cursorX;
1391 int row = m_emuState.cursorY;
1392 if (m_emuState.cursorPastLastCol) {
1393 ++row;
1394 col = 1;
1395 }
1396 uint32_t lastColItem = 0;
1397 while (charsToMove > 0) {
1398 uint32_t * rowPtr = m_glyphsBuffer.map + (row - 1) * m_columns;
1399 uint32_t lItem = rowPtr[m_columns - 1];
1400 insertAt(col, row, 1);
1401 if (row > m_emuState.cursorY) {
1402 rowPtr[0] = lastColItem;
1403 refresh(1, row);
1404 }
1405 lastColItem = lItem;
1406 charsToMove -= m_columns - col;
1407 col = 1;
1408 if (charsToMove > 0 && row == m_emuState.scrollingRegionDown) {
1409 scrolled = true;
1410 scrollUp();
1411 setCursorPos(m_emuState.cursorX, m_emuState.cursorY - 1);
1412 } else {
1413 ++row;
1414 }
1415 if (m_bitmappedDisplayController && isActive())
1416 m_canvas->waitCompletion(false);
1417 }
1418 return scrolled;
1419}
1420
1421
1422// inserts specified number of blank spaces at the specified row and column. Characters moved past the right border are lost.
1423void Terminal::insertAt(int column, int row, int count)
1424{
1425 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1426 logFmt("insertAt(%d, %d, %d)\n", column, row, count);
1427 #endif
1428
1429 count = tmin((int)m_columns, count);
1430
1431 if (m_bitmappedDisplayController && isActive()) {
1432 // move characters on the right using canvas
1433 int charWidth = getCharWidthAt(row);
1434 m_canvas->setScrollingRegion((column - 1) * charWidth, (row - 1) * m_font.height, charWidth * getColumnsAt(row) - 1, row * m_font.height - 1);
1435 m_canvas->scroll(count * charWidth, 0);
1436 updateCanvasScrollingRegion(); // restore original scrolling region
1437 }
1438
1439 // move characters in the screen buffer
1440 uint32_t * rowPtr = m_glyphsBuffer.map + (row - 1) * m_columns;
1441 for (int i = m_columns - 1; i >= column + count - 1; --i)
1442 rowPtr[i] = rowPtr[i - count];
1443
1444 // fill blank characters
1445 GlyphOptions glyphOptions = m_glyphOptions;
1446 glyphOptions.doubleWidth = glyphMapItem_getOptions(rowPtr).doubleWidth;
1447 uint32_t itemValue = GLYPHMAP_ITEM_MAKE(ASCII_SPC, m_emuState.backgroundColor, m_emuState.foregroundColor, glyphOptions);
1448 for (int i = 0; i < count; ++i)
1449 rowPtr[column + i - 1] = itemValue;
1450}
1451
1452
1453void Terminal::multilineDeleteChar(int charsToMove)
1454{
1455 int col = m_emuState.cursorX;
1456 int row = m_emuState.cursorY;
1457 if (m_emuState.cursorPastLastCol) {
1458 ++row;
1459 col = 1;
1460 }
1461
1462 // at least one char must be deleted
1463 if (charsToMove == 0)
1464 deleteAt(col, row, 1);
1465
1466 while (charsToMove > 0) {
1467 deleteAt(col, row, 1);
1468 charsToMove -= m_columns - col;
1469 if (charsToMove > 0) {
1470 if (m_bitmappedDisplayController && isActive())
1471 m_canvas->waitCompletion(false);
1472 uint32_t * lastItem = m_glyphsBuffer.map + (row - 1) * m_columns + (m_columns - 1);
1473 lastItem[0] = lastItem[1];
1474 refresh(m_columns, row);
1475 }
1476 col = 1;
1477 ++row;
1478 if (m_bitmappedDisplayController && isActive())
1479 m_canvas->waitCompletion(false);
1480 }
1481}
1482
1483
1484// deletes "count" characters from specified "row", starting from "column", scrolling left remaining characters
1485void Terminal::deleteAt(int column, int row, int count)
1486{
1487 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1488 logFmt("deleteAt(%d, %d, %d)\n", column, row, count);
1489 #endif
1490
1491 count = imin(m_columns - column + 1, count);
1492
1493 if (m_bitmappedDisplayController && isActive()) {
1494 // move characters on the right using canvas
1495 int charWidth = getCharWidthAt(row);
1496 m_canvas->setScrollingRegion((column - 1) * charWidth, (row - 1) * m_font.height, charWidth * getColumnsAt(row) - 1, row * m_font.height - 1);
1497 m_canvas->scroll(-count * charWidth, 0);
1498 updateCanvasScrollingRegion(); // restore original scrolling region
1499 }
1500
1501 // move characters in the screen buffer
1502 uint32_t * rowPtr = m_glyphsBuffer.map + (row - 1) * m_columns;
1503 int itemsToMove = m_columns - column - count + 1;
1504 for (int i = 0; i < itemsToMove; ++i)
1505 rowPtr[column - 1 + i] = rowPtr[column - 1 + i + count];
1506
1507 // fill blank characters
1508 GlyphOptions glyphOptions = m_glyphOptions;
1509 glyphOptions.doubleWidth = glyphMapItem_getOptions(rowPtr).doubleWidth;
1510 uint32_t itemValue = GLYPHMAP_ITEM_MAKE(ASCII_SPC, m_emuState.backgroundColor, m_emuState.foregroundColor, glyphOptions);
1511 for (int i = m_columns - count + 1 ; i <= m_columns; ++i)
1512 rowPtr[i - 1] = itemValue;
1513}
1514
1515
1516// Coordinates are cursor coordinates (1,1 = top left)
1517// maintainDoubleWidth = true: Maintains line attributes (double width)
1518// selective = true: erase only characters with userOpt1 = 0
1519void Terminal::erase(int X1, int Y1, int X2, int Y2, uint8_t c, bool maintainDoubleWidth, bool selective)
1520{
1521 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1522 logFmt("erase(%d, %d, %d, %d, %d, %d)\n", X1, Y1, X2, Y2, (int)c, (int)maintainDoubleWidth);
1523 #endif
1524
1525 X1 = tclamp(X1 - 1, 0, (int)m_columns - 1);
1526 Y1 = tclamp(Y1 - 1, 0, (int)m_rows - 1);
1527 X2 = tclamp(X2 - 1, 0, (int)m_columns - 1);
1528 Y2 = tclamp(Y2 - 1, 0, (int)m_rows - 1);
1529
1530 if (m_bitmappedDisplayController && isActive()) {
1531 if (c == ASCII_SPC && !selective) {
1532 int charWidth = getCharWidthAt(m_emuState.cursorY);
1533 m_canvas->fillRectangle(X1 * charWidth, Y1 * m_font.height, (X2 + 1) * charWidth - 1, (Y2 + 1) * m_font.height - 1);
1534 }
1535 }
1536
1537 GlyphOptions glyphOptions = {.value = 0};
1538 glyphOptions.fillBackground = 1;
1539
1540 for (int y = Y1; y <= Y2; ++y) {
1541 uint32_t * itemPtr = m_glyphsBuffer.map + X1 + y * m_columns;
1542 for (int x = X1; x <= X2; ++x, ++itemPtr) {
1543 if (selective && glyphMapItem_getOptions(itemPtr).userOpt2) // bypass if protected item
1544 continue;
1545 glyphOptions.doubleWidth = maintainDoubleWidth ? glyphMapItem_getOptions(itemPtr).doubleWidth : 0;
1546 *itemPtr = GLYPHMAP_ITEM_MAKE(c, m_emuState.backgroundColor, m_emuState.foregroundColor, glyphOptions);
1547 }
1548 }
1549 if (c != ASCII_SPC || selective)
1550 refresh(X1 + 1, Y1 + 1, X2 + 1, Y2 + 1);
1551}
1552
1553
1554void Terminal::enableFabGLSequences(bool value)
1555{
1556 m_emuState.allowFabGLSequences += value ? 1 : -1;
1557 if (m_emuState.allowFabGLSequences < 0)
1558 m_emuState.allowFabGLSequences = 0;
1559}
1560
1561
1562void Terminal::clearSavedCursorStates()
1563{
1564 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1565 log("clearSavedCursorStates()\n");
1566 #endif
1567
1568 for (TerminalCursorState * curItem = m_savedCursorStateList, * next; curItem; curItem = next) {
1569 next = curItem->next;
1570 free(curItem->tabStop);
1571 free(curItem);
1572 }
1573 m_savedCursorStateList = nullptr;
1574}
1575
1576
1577void Terminal::saveCursorState()
1578{
1579 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1580 log("saveCursorState()\n");
1581 #endif
1582
1583 TerminalCursorState * s = (TerminalCursorState*) malloc(sizeof(TerminalCursorState));
1584
1585 if (s) {
1586 *s = (TerminalCursorState) {
1587 .next = m_savedCursorStateList,
1588 .cursorX = (int16_t) m_emuState.cursorX,
1589 .cursorY = (int16_t) m_emuState.cursorY,
1590 .tabStop = (uint8_t*) malloc(m_columns),
1591 .cursorPastLastCol = m_emuState.cursorPastLastCol,
1592 .originMode = m_emuState.originMode,
1593 .glyphOptions = m_glyphOptions,
1594 .characterSetIndex = m_emuState.characterSetIndex,
1595 .characterSet = {m_emuState.characterSet[0], m_emuState.characterSet[1], m_emuState.characterSet[2], m_emuState.characterSet[3]},
1596 };
1597 if (s->tabStop)
1598 memcpy(s->tabStop, m_emuState.tabStop, m_columns);
1599 m_savedCursorStateList = s;
1600 } else {
1601 #if FABGLIB_TERMINAL_DEBUG_REPORT_ERRORS
1602 log("ERROR: Unable to alloc TerminalCursorState\n");
1603 #endif
1604 }
1605}
1606
1607
1608void Terminal::restoreCursorState()
1609{
1610 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1611 log("restoreCursorState()\n");
1612 #endif
1613
1614 if (m_savedCursorStateList) {
1615 m_emuState.cursorX = m_savedCursorStateList->cursorX;
1616 m_emuState.cursorY = m_savedCursorStateList->cursorY;
1617 m_emuState.cursorPastLastCol = m_savedCursorStateList->cursorPastLastCol;
1618 m_emuState.originMode = m_savedCursorStateList->originMode;
1619 if (m_savedCursorStateList->tabStop)
1620 memcpy(m_emuState.tabStop, m_savedCursorStateList->tabStop, m_columns);
1621 m_glyphOptions = m_savedCursorStateList->glyphOptions;
1622 if (m_bitmappedDisplayController && isActive())
1623 m_canvas->setGlyphOptions(m_glyphOptions);
1624 m_emuState.characterSetIndex = m_savedCursorStateList->characterSetIndex;
1625 for (int i = 0; i < 4; ++i)
1626 m_emuState.characterSet[i] = m_savedCursorStateList->characterSet[i];
1627
1628 TerminalCursorState * next = m_savedCursorStateList->next;
1629
1630 free(m_savedCursorStateList->tabStop);
1631 free(m_savedCursorStateList);
1632 m_savedCursorStateList = next;
1633 }
1634}
1635
1636
1637void Terminal::useAlternateScreenBuffer(bool value)
1638{
1639 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
1640 logFmt("useAlternateScreenBuffer: %d\n", value);
1641 #endif
1642 if (m_alternateScreenBuffer != value) {
1643 m_alternateScreenBuffer = value;
1644 if (!m_alternateMap) {
1645 // first usage, need to setup the alternate screen
1646 m_alternateMap = (uint32_t*) heap_caps_malloc(sizeof(uint32_t) * m_columns * m_rows, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
1647 clearMap(m_alternateMap);
1648 m_alternateCursorX = 1;
1649 m_alternateCursorY = 1;
1650 m_alternateScrollingRegionTop = 1;
1651 m_alternateScrollingRegionDown = m_rows;
1652 m_alternateCursorBlinkingEnabled = true;
1653 }
1654 tswap(m_alternateMap, m_glyphsBuffer.map);
1655 tswap(m_emuState.cursorX, m_alternateCursorX);
1656 tswap(m_emuState.cursorY, m_alternateCursorY);
1657 tswap(m_emuState.scrollingRegionTop, m_alternateScrollingRegionTop);
1658 tswap(m_emuState.scrollingRegionDown, m_alternateScrollingRegionDown);
1659 tswap(m_emuState.cursorBlinkingEnabled, m_alternateCursorBlinkingEnabled);
1660 setScrollingRegion(m_emuState.scrollingRegionTop, m_emuState.scrollingRegionDown, false);
1661 m_emuState.cursorPastLastCol = false;
1662 refresh();
1663 }
1664}
1665
1666
1667void Terminal::localInsert(uint8_t c)
1668{
1669 if (m_outputQueue)
1670 xQueueSendToFront(m_outputQueue, &c, portMAX_DELAY);
1671}
1672
1673
1674void Terminal::localWrite(uint8_t c)
1675{
1676 if (m_outputQueue)
1677 xQueueSendToBack(m_outputQueue, &c, portMAX_DELAY);
1678}
1679
1680
1681void Terminal::localWrite(char const * str)
1682{
1683 if (m_outputQueue) {
1684 while (*str) {
1685 xQueueSendToBack(m_outputQueue, str, portMAX_DELAY);
1686
1687 #if FABGLIB_TERMINAL_DEBUG_REPORT_OUT_CODES
1688 logFmt("=> %02X %s%c\n", (int)*str, (*str <= ASCII_SPC ? CTRLCHAR_TO_STR[(int)(*str)] : ""), (*str > ASCII_SPC ? *str : ASCII_SPC));
1689 #endif
1690
1691 ++str;
1692 }
1693 }
1694}
1695
1696
1697int Terminal::available()
1698{
1699 return m_outputQueue ? uxQueueMessagesWaiting(m_outputQueue) : 0;
1700}
1701
1702
1703int Terminal::read()
1704{
1705 return read(-1);
1706}
1707
1708
1709int Terminal::read(int timeOutMS)
1710{
1711 if (m_outputQueue) {
1712 uint8_t c;
1713 xQueueReceive(m_outputQueue, &c, msToTicks(timeOutMS));
1714 return c;
1715 } else
1716 return -1;
1717}
1718
1719
1720bool Terminal::waitFor(int value, int timeOutMS)
1721{
1722 TimeOut timeout;
1723 while (!timeout.expired(timeOutMS)) {
1724 int c = read(timeOutMS);
1725 if (c == value)
1726 return true;
1727 }
1728 return false;
1729}
1730
1731
1732// not implemented
1733int Terminal::peek()
1734{
1735 return -1;
1736}
1737
1738
1739void Terminal::flush()
1740{
1741 flush(true);
1742}
1743
1744
1745#ifdef ARDUINO
1746void Terminal::pollSerialPort()
1747{
1748 while (true) {
1749 int avail = m_serialPort->available();
1750
1751 if (m_flowControl == FlowControl::Software) {
1752 if (m_sentXOFF) {
1753 // XOFF already sent, need to send XON?
1754 if (avail < FABGLIB_TERMINAL_XON_THRESHOLD) {
1755 send(ASCII_XON);
1756 m_sentXOFF = false;
1757 }
1758 } else {
1759 // XOFF not sent, need to send XOFF?
1760 if (avail >= FABGLIB_TERMINAL_XOFF_THRESHOLD) {
1761 send(ASCII_XOFF);
1762 m_sentXOFF = true;
1763 }
1764 }
1765 }
1766
1767 if (!avail)
1768 break;
1769
1770 auto r = m_serialPort->read();
1771 if (m_uartRXEnabled)
1772 write(r);
1773 }
1774}
1775#endif
1776
1777
1778void IRAM_ATTR Terminal::uart_isr(void *arg)
1779{
1780 Terminal * term = (Terminal*) arg;
1781 auto uart = (volatile uart_dev_t *)(DR_REG_UART2_BASE);
1782
1783 // look for overflow or RX errors
1784 if (uart->int_st.rxfifo_ovf || uart->int_st.frm_err || uart->int_st.parity_err) {
1785 // reset RX-FIFO, because hardware bug rxfifo_rst cannot be used, so just flush
1786 uartFlushRXFIFO();
1787 // clear interrupt flags
1788 SET_PERI_REG_MASK(UART_INT_CLR_REG(2), UART_RXFIFO_OVF_INT_CLR_M | UART_FRM_ERR_INT_CLR_M | UART_PARITY_ERR_INT_CLR_M);
1789 return;
1790 }
1791
1792 // flow control?
1793 if (term->m_flowControl != FlowControl::None) {
1794 // send XOFF/XON or set RTS looking at RX FIFO occupation
1795 int count = uartGetRXFIFOCount();
1796 if (count > FABGLIB_TERMINAL_FLOWCONTROL_RXFIFO_MAX_THRESHOLD && !term->m_sentXOFF)
1797 term->flowControl(false); // disable RX
1798 else if (count < FABGLIB_TERMINAL_FLOWCONTROL_RXFIFO_MIN_THRESHOLD && term->m_sentXOFF)
1799 term->flowControl(true); // enable RX
1800 }
1801
1802 // main receive loop
1803 while (uartGetRXFIFOCount() != 0 || uart->mem_rx_status.wr_addr != uart->mem_rx_status.rd_addr) {
1804 // look for enough room in input queue
1805 if (term->m_flowControl != FlowControl::None && xQueueIsQueueFullFromISR(term->m_inputQueue)) {
1806 if (!term->m_sentXOFF)
1807 term->flowControl(false); // disable RX
1808 // block further interrupts
1809 CLEAR_PERI_REG_MASK(UART_INT_ENA_REG(2), UART_RXFIFO_FULL_INT_ENA_M);
1810 break;
1811 }
1812 // add to input queue
1813 uint8_t r = READ_PERI_REG(UART_FIFO_REG(2));
1814 if (term->m_uartRXEnabled)
1815 term->write(r, true);
1816 }
1817
1818 // clear interrupt flag
1819 SET_PERI_REG_MASK(UART_INT_CLR_REG(2), UART_RXFIFO_FULL_INT_CLR_M);
1820}
1821
1822
1823// send a character to m_serialPort or m_outputQueue
1824void Terminal::send(uint8_t c)
1825{
1826 #if FABGLIB_TERMINAL_DEBUG_REPORT_OUT_CODES
1827 logFmt("=> %02X %s%c\n", (int)c, (c <= ASCII_SPC ? CTRLCHAR_TO_STR[(int)c] : ""), (c > ASCII_SPC ? c : ASCII_SPC));
1828 #endif
1829
1830 #ifdef ARDUINO
1831 if (m_serialPort) {
1832 while (m_serialPort->availableForWrite() == 0)
1833 vTaskDelay(1 / portTICK_PERIOD_MS);
1834 m_serialPort->write(c);
1835 }
1836 #endif
1837
1838 if (m_uart) {
1839 //while (!flowControl())
1840 // vTaskDelay(1);
1841 auto uart = (volatile uart_dev_t *)(DR_REG_UART2_BASE);
1842 while (uart->status.txfifo_cnt == 0x7F)
1843 ;
1844 WRITE_PERI_REG(UART_FIFO_AHB_REG(2), c);
1845 }
1846
1847 localWrite(c); // write to m_outputQueue
1848}
1849
1850
1851// send a string to m_serialPort or m_outputQueue
1852void Terminal::send(char const * str)
1853{
1854 #ifdef ARDUINO
1855 if (m_serialPort) {
1856 while (*str) {
1857 while (m_serialPort->availableForWrite() == 0)
1858 vTaskDelay(1 / portTICK_PERIOD_MS);
1859 m_serialPort->write(*str);
1860
1861 #if FABGLIB_TERMINAL_DEBUG_REPORT_OUT_CODES
1862 logFmt("=> %02X %s%c\n", (int)*str, (*str <= ASCII_SPC ? CTRLCHAR_TO_STR[(int)(*str)] : ""), (*str > ASCII_SPC ? *str : ASCII_SPC));
1863 #endif
1864
1865 ++str;
1866 }
1867 }
1868 #endif
1869
1870 if (m_uart) {
1871 auto uart = (volatile uart_dev_t *)(DR_REG_UART2_BASE);
1872 while (*str) {
1873 //while (!flowControl())
1874 // vTaskDelay(1);
1875 while (uart->status.txfifo_cnt == 0x7F)
1876 ;
1877 WRITE_PERI_REG(UART_FIFO_AHB_REG(2), (*str++));
1878 }
1879 }
1880
1881 localWrite(str); // write to m_outputQueue
1882}
1883
1884
1885void Terminal::sendCSI()
1886{
1887 send(m_emuState.ctrlBits == 7 ? CSI_7BIT : CSI_8BIT);
1888}
1889
1890
1891void Terminal::sendDCS()
1892{
1893 send(m_emuState.ctrlBits == 7 ? DCS_7BIT : DCS_8BIT);
1894}
1895
1896
1897void Terminal::sendSS3()
1898{
1899 send(m_emuState.ctrlBits == 7 ? SS3_7BIT : SS3_8BIT);
1900}
1901
1902
1903int Terminal::availableForWrite()
1904{
1905 return uxQueueSpacesAvailable(m_inputQueue);
1906}
1907
1908
1909bool Terminal::addToInputQueue(uint8_t c, bool fromISR)
1910{
1911 if (fromISR)
1912 return xQueueSendToBackFromISR(m_inputQueue, &c, nullptr);
1913 else
1914 return xQueueSendToBack(m_inputQueue, &c, portMAX_DELAY);
1915}
1916
1917
1918bool Terminal::insertToInputQueue(uint8_t c, bool fromISR)
1919{
1920 if (fromISR)
1921 return xQueueSendToFrontFromISR(m_inputQueue, &c, nullptr);
1922 else
1923 return xQueueSendToFront(m_inputQueue, &c, portMAX_DELAY);
1924}
1925
1926
1927void Terminal::write(uint8_t c, bool fromISR)
1928{
1929 if (m_termInfo == nullptr || m_writeDetectedFabGLSeq)
1930 addToInputQueue(c, fromISR); // send unprocessed
1931 else
1932 convHandleTranslation(c, fromISR);
1933
1934 // this is necessary to avoid to call convHandleTranslation() in the middle of FabGL specific sequence (which can have binary data inside)
1935 if (m_writeDetectedFabGLSeq) {
1936 if (c == FABGLEXT_ENDCODE)
1937 m_writeDetectedFabGLSeq = false;
1938 } else if (m_emuState.allowFabGLSequences && m_lastWrittenChar == ASCII_ESC && c == FABGLEXT_STARTCODE) {
1939 m_writeDetectedFabGLSeq = true;
1940 }
1941
1942 m_lastWrittenChar = c;
1943
1944 #if FABGLIB_TERMINAL_DEBUG_REPORT_IN_CODES
1945 if (!fromISR)
1946 logFmt("<= %02X %s%c\n", (int)c, (c <= ASCII_SPC ? CTRLCHAR_TO_STR[(int)c] : ""), (c > ASCII_SPC ? c : ASCII_SPC));
1947 #endif
1948}
1949
1950
1951size_t Terminal::write(uint8_t c)
1952{
1953 write(c, false);
1954 return 1;
1955}
1956
1957
1958size_t Terminal::write(const uint8_t * buffer, size_t size)
1959{
1960 for (int i = 0; i < size; ++i)
1961 write(*(buffer++));
1962 return size;
1963}
1964
1965
1966void Terminal::setTerminalType(TermType value)
1967{
1968 // doesn't set it immediately, serialize into the queue
1970}
1971
1972
1973void Terminal::int_setTerminalType(TermInfo const * value)
1974{
1975 // disable VT52 mode
1976 m_emuState.ANSIMode = true;
1977 m_emuState.conformanceLevel = 4;
1978
1979 m_termInfo = nullptr;
1980
1981 if (value != nullptr) {
1982 // need to "insert" initString in reverse order
1983 auto s = value->initString;
1984 for (int i = strlen(s) - 1; i >= 0; --i)
1985 insertToInputQueue(s[i], false);
1986
1987 m_termInfo = value;
1988 }
1989}
1990
1991
1992void Terminal::int_setTerminalType(TermType value)
1993{
1994 switch (value) {
1995 case TermType::ANSI_VT:
1996 int_setTerminalType(nullptr);
1997 break;
1998 case TermType::ADM3A:
1999 int_setTerminalType(&term_ADM3A);
2000 break;
2001 case TermType::ADM31:
2002 int_setTerminalType(&term_ADM31);
2003 break;
2005 int_setTerminalType(&term_Hazeltine1500);
2006 break;
2007 case TermType::Osborne:
2008 int_setTerminalType(&term_Osborne);
2009 break;
2010 case TermType::Kaypro:
2011 int_setTerminalType(&term_Kaypro);
2012 break;
2013 case TermType::VT52:
2014 int_setTerminalType(&term_VT52);
2015 break;
2017 int_setTerminalType(&term_ANSILegacy);
2018 break;
2019 }
2020}
2021
2022
2023void Terminal::convHandleTranslation(uint8_t c, bool fromISR)
2024{
2025 if (m_convMatchedCount > 0 || c < 32 || c == 0x7f || c == '~') {
2026
2027 m_convMatchedChars[m_convMatchedCount] = c;
2028
2029 if (m_convMatchedItem == nullptr)
2030 m_convMatchedItem = m_termInfo->videoCtrlSet;
2031
2032 for (auto item = m_convMatchedItem; item->termSeq; ++item) {
2033 if (item != m_convMatchedItem) {
2034 // check if this item can be a new candidate
2035 if (m_convMatchedCount == 0 || (item->termSeqLen > m_convMatchedCount && strncmp(item->termSeq, m_convMatchedItem->termSeq, m_convMatchedCount) == 0))
2036 m_convMatchedItem = item;
2037 else
2038 continue;
2039 }
2040 // here (item == m_convMatchedItem) is always true
2041 if (item->termSeq[m_convMatchedCount] == 0xFF || item->termSeq[m_convMatchedCount] == c) {
2042 // are there other chars to process?
2043 ++m_convMatchedCount;
2044 if (item->termSeqLen == m_convMatchedCount) {
2045 // full match, send related ANSI sequences (and resets m_convMatchedCount and m_convMatchedItem)
2046 for (ConvCtrl const * ctrl = item->convCtrl; *ctrl != ConvCtrl::END; ++ctrl)
2047 convSendCtrl(*ctrl, fromISR);
2048 }
2049 return;
2050 }
2051 }
2052
2053 // no match, send received stuff as is
2054 convQueue(nullptr, fromISR);
2055 } else
2056 addToInputQueue(c, fromISR);
2057}
2058
2059
2060void Terminal::convSendCtrl(ConvCtrl ctrl, bool fromISR)
2061{
2062 switch (ctrl) {
2063 case ConvCtrl::CarriageReturn:
2064 convQueue("\x0d", fromISR);
2065 break;
2066 case ConvCtrl::LineFeed:
2067 convQueue("\x0a", fromISR);
2068 break;
2069 case ConvCtrl::CursorLeft:
2070 convQueue("\e[D", fromISR);
2071 break;
2072 case ConvCtrl::CursorUp:
2073 convQueue("\e[A", fromISR);
2074 break;
2075 case ConvCtrl::CursorRight:
2076 convQueue("\e[C", fromISR);
2077 break;
2078 case ConvCtrl::EraseToEndOfScreen:
2079 convQueue("\e[J", fromISR);
2080 break;
2081 case ConvCtrl::EraseToEndOfLine:
2082 convQueue("\e[K", fromISR);
2083 break;
2084 case ConvCtrl::CursorHome:
2085 convQueue("\e[H", fromISR);
2086 break;
2087 case ConvCtrl::AttrNormal:
2088 convQueue("\e[0m", fromISR);
2089 break;
2090 case ConvCtrl::AttrBlank:
2091 convQueue("\e[8m", fromISR);
2092 break;
2093 case ConvCtrl::AttrBlink:
2094 convQueue("\e[5m", fromISR);
2095 break;
2096 case ConvCtrl::AttrBlinkOff:
2097 convQueue("\e[25m", fromISR);
2098 break;
2099 case ConvCtrl::AttrReverse:
2100 convQueue("\e[7m", fromISR);
2101 break;
2102 case ConvCtrl::AttrReverseOff:
2103 convQueue("\e[27m", fromISR);
2104 break;
2105 case ConvCtrl::AttrUnderline:
2106 convQueue("\e[4m", fromISR);
2107 break;
2108 case ConvCtrl::AttrUnderlineOff:
2109 convQueue("\e[24m", fromISR);
2110 break;
2111 case ConvCtrl::AttrReduce:
2112 convQueue("\e[2m", fromISR);
2113 break;
2114 case ConvCtrl::AttrReduceOff:
2115 convQueue("\e[22m", fromISR);
2116 break;
2117 case ConvCtrl::InsertLine:
2118 convQueue("\e[L", fromISR);
2119 break;
2120 case ConvCtrl::InsertChar:
2121 convQueue("\e[@", fromISR);
2122 break;
2123 case ConvCtrl::DeleteLine:
2124 convQueue("\e[M", fromISR);
2125 break;
2126 case ConvCtrl::DeleteCharacter:
2127 convQueue("\e[P", fromISR);
2128 break;
2129 case ConvCtrl::CursorOn:
2130 convQueue("\e[?25h", fromISR);
2131 break;
2132 case ConvCtrl::CursorOff:
2133 convQueue("\e[?25l", fromISR);
2134 break;
2135 case ConvCtrl::SaveCursor:
2136 convQueue("\e[?1048h", fromISR);
2137 break;
2138 case ConvCtrl::RestoreCursor:
2139 convQueue("\e[?1048l", fromISR);
2140 break;
2141 case ConvCtrl::CursorPos:
2142 case ConvCtrl::CursorPos2:
2143 {
2144 char s[11];
2145 int y = (ctrl == ConvCtrl::CursorPos ? m_convMatchedChars[2] - 31 : m_convMatchedChars[3] + 1);
2146 int x = (ctrl == ConvCtrl::CursorPos ? m_convMatchedChars[3] - 31 : m_convMatchedChars[2] + 1);
2147 sprintf(s, "\e[%d;%dH", y, x);
2148 convQueue(s, fromISR);
2149 break;
2150 }
2151
2152 default:
2153 break;
2154 }
2155}
2156
2157
2158// queue m_termMatchedChars[] or specified string
2159void Terminal::convQueue(const char * str, bool fromISR)
2160{
2161 if (str) {
2162 for (; *str; ++str)
2163 addToInputQueue(*str, fromISR);
2164 } else {
2165 for (int i = 0; i <= m_convMatchedCount; ++i) {
2166 addToInputQueue(m_convMatchedChars[i], fromISR);
2167 }
2168 }
2169 m_convMatchedCount = 0;
2170 m_convMatchedItem = nullptr;
2171}
2172
2173
2174uint32_t Terminal::makeGlyphItem(uint8_t c, GlyphOptions * glyphOptions, Color * newForegroundColor)
2175{
2176 *newForegroundColor = m_emuState.foregroundColor;
2177
2178 if (glyphOptions->bold && (m_coloredAttributesMask & (1 << COLORED_ATTRIBUTE_BOLD))) {
2179 *newForegroundColor = m_coloredAttributesColor[COLORED_ATTRIBUTE_BOLD];
2180 if (!m_coloredAttributesMaintainStyle)
2181 glyphOptions->bold = 0;
2182 }
2183
2184 if (glyphOptions->reduceLuminosity && (m_coloredAttributesMask & (1 << COLORED_ATTRIBUTE_FAINT))) {
2185 *newForegroundColor = m_coloredAttributesColor[COLORED_ATTRIBUTE_FAINT];
2186 if (!m_coloredAttributesMaintainStyle)
2187 glyphOptions->reduceLuminosity = 0;
2188 }
2189
2190 if (glyphOptions->italic && (m_coloredAttributesMask & (1 << COLORED_ATTRIBUTE_ITALIC))) {
2191 *newForegroundColor = m_coloredAttributesColor[COLORED_ATTRIBUTE_ITALIC];
2192 if (!m_coloredAttributesMaintainStyle)
2193 glyphOptions->italic = 0;
2194 }
2195
2196 if (glyphOptions->underline && (m_coloredAttributesMask & (1 << COLORED_ATTRIBUTE_UNDERLINE))) {
2197 *newForegroundColor = m_coloredAttributesColor[COLORED_ATTRIBUTE_UNDERLINE];
2198 if (!m_coloredAttributesMaintainStyle)
2199 glyphOptions->underline = 0;
2200 }
2201
2202 return GLYPHMAP_ITEM_MAKE(c, m_emuState.backgroundColor, *newForegroundColor, *glyphOptions);
2203}
2204
2205
2206// set specified character at current cursor position
2207// return true if vertical scroll happened
2208bool Terminal::setChar(uint8_t c)
2209{
2210 bool vscroll = false;
2211
2212 if (m_emuState.cursorPastLastCol) {
2213 if (m_emuState.wraparound) {
2214 setCursorPos(1, m_emuState.cursorY); // this sets m_emuState.cursorPastLastCol = false
2215 if (moveDown()) {
2216 scrollUp();
2217 vscroll = true;
2218 }
2219 }
2220 }
2221
2222 if (m_emuState.insertMode)
2223 insertAt(m_emuState.cursorX, m_emuState.cursorY, 1);
2224
2225 GlyphOptions glyphOptions = m_glyphOptions;
2226
2227 // doubleWidth must be maintained
2228 uint32_t * mapItemPtr = m_glyphsBuffer.map + (m_emuState.cursorX - 1) + (m_emuState.cursorY - 1) * m_columns;
2229 glyphOptions.doubleWidth = glyphMapItem_getOptions(mapItemPtr).doubleWidth;
2230 Color newForegroundColor;
2231 *mapItemPtr = makeGlyphItem(c, &glyphOptions, &newForegroundColor);
2232
2233 if (m_bitmappedDisplayController && isActive()) {
2234
2235 // Instead of this boring stuff we may also just call:
2236 // m_canvas->renderGlyphsBuffer(m_emuState.cursorX - 1, m_emuState.cursorY - 1, &m_glyphsBuffer);
2237 // and then *synch*, but it may be slower. Further tests required.
2238
2239 if (glyphOptions.value != m_glyphOptions.value)
2240 m_canvas->setGlyphOptions(glyphOptions);
2241 if (newForegroundColor != m_emuState.foregroundColor)
2242 m_canvas->setPenColor(newForegroundColor);
2243
2244 int x = (m_emuState.cursorX - 1) * m_font.width * (glyphOptions.doubleWidth ? 2 : 1);
2245 int y = (m_emuState.cursorY - 1) * m_font.height;
2246 m_canvas->drawGlyph(x, y, m_font.width, m_font.height, m_font.data, c);
2247
2248 if (newForegroundColor != m_emuState.foregroundColor)
2249 m_canvas->setPenColor(m_emuState.foregroundColor);
2250 if (glyphOptions.value != m_glyphOptions.value)
2251 m_canvas->setGlyphOptions(m_glyphOptions);
2252 }
2253
2254 // blinking text?
2255 if (m_glyphOptions.userOpt1)
2256 m_prevBlinkingTextEnabled = true; // consumeInputQueue() will set the value
2257
2258 if (m_emuState.cursorX == m_columns) {
2259 m_emuState.cursorPastLastCol = true;
2260 } else {
2261 setCursorPos(m_emuState.cursorX + 1, m_emuState.cursorY);
2262 }
2263
2264 return vscroll;
2265}
2266
2267
2268void Terminal::refresh()
2269{
2270 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
2271 log("refresh()\n");
2272 #endif
2273
2274 refresh(1, 1, m_columns, m_rows);
2275}
2276
2277
2278// do not call waitCompletion!!
2279void Terminal::refresh(int X, int Y)
2280{
2281 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
2282 logFmt("refresh(%d, %d)\n", X, Y);
2283 #endif
2284
2285 if (m_bitmappedDisplayController && isActive())
2286 m_canvas->renderGlyphsBuffer(X - 1, Y - 1, &m_glyphsBuffer);
2287}
2288
2289
2290void Terminal::refresh(int X1, int Y1, int X2, int Y2)
2291{
2292 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
2293 logFmt("refresh(%d, %d, %d, %d)\n", X1, Y1, X2, Y2);
2294 #endif
2295
2296 if (m_bitmappedDisplayController && isActive()) {
2297 for (int y = Y1 - 1; y < Y2; ++y) {
2298 for (int x = X1 - 1; x < X2; ++x)
2299 m_canvas->renderGlyphsBuffer(x, y, &m_glyphsBuffer);
2300 m_canvas->waitCompletion(false);
2301 }
2302 }
2303}
2304
2305
2306// value: 0 = normal, 1 = double width, 2 = double width - double height top, 3 = double width - double height bottom
2307void Terminal::setLineDoubleWidth(int row, int value)
2308{
2309 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCS
2310 logFmt("setLineDoubleWidth(%d, %d)\n", row, value);
2311 #endif
2312
2313 uint32_t * mapItemPtr = m_glyphsBuffer.map + (row - 1) * m_columns;
2314 for (int i = 0; i < m_columns; ++i, ++mapItemPtr) {
2315 GlyphOptions glyphOptions = glyphMapItem_getOptions(mapItemPtr);
2316 glyphOptions.doubleWidth = value;
2317 glyphMapItem_setOptions(mapItemPtr, glyphOptions);
2318 }
2319
2320 refresh(1, row, m_columns, row);
2321}
2322
2323
2324int Terminal::getCharWidthAt(int row)
2325{
2326 return glyphMapItem_getOptions(m_glyphsBuffer.map + (row - 1) * m_columns).doubleWidth ? m_font.width * 2 : m_font.width;
2327}
2328
2329
2330int Terminal::getColumnsAt(int row)
2331{
2332 return glyphMapItem_getOptions(m_glyphsBuffer.map + (row - 1) * m_columns).doubleWidth ? m_columns / 2 : m_columns;
2333}
2334
2335
2336GlyphOptions Terminal::getGlyphOptionsAt(int X, int Y)
2337{
2338 return glyphMapItem_getOptions(m_glyphsBuffer.map + (X - 1) + (Y - 1) * m_columns);
2339}
2340
2341
2342// blocking operation
2343uint8_t Terminal::getNextCode(bool processCtrlCodes)
2344{
2345 while (true) {
2346 uint8_t c;
2347 xQueueReceive(m_inputQueue, &c, portMAX_DELAY);
2348
2349 #if FABGLIB_TERMINAL_DEBUG_REPORT_INQUEUE_CODES
2350 logFmt("<= %02X %s%c\n", (int)c, (c <= ASCII_SPC ? CTRLCHAR_TO_STR[(int)c] : ""), (c > ASCII_SPC ? c : ASCII_SPC));
2351 #endif
2352
2353 if (m_uart)
2354 uartCheckInputQueueForFlowControl();
2355
2356 // inside an ESC sequence we may find control characters!
2357 if (processCtrlCodes && ISCTRLCHAR(c))
2358 execCtrlCode(c);
2359 else
2360 return c;
2361 }
2362}
2363
2364
2365void Terminal::charsConsumerTask(void * pvParameters)
2366{
2367 Terminal * term = (Terminal*) pvParameters;
2368
2369 while (true)
2370 term->consumeInputQueue();
2371}
2372
2373
2374void Terminal::consumeInputQueue()
2375{
2376 uint8_t c = getNextCode(false); // blocking call. false: do not process ctrl chars
2377
2378 xSemaphoreTake(m_mutex, portMAX_DELAY);
2379
2380 m_prevCursorEnabled = int_enableCursor(false);
2381 m_prevBlinkingTextEnabled = enableBlinkingText(false);
2382
2383 // ESC
2384 // Start escape sequence
2385 if (c == ASCII_ESC)
2386 consumeESC();
2387
2388 else if (ISCTRLCHAR(c))
2389 execCtrlCode(c);
2390
2391 else {
2392 if (m_emuState.characterSet[m_emuState.characterSetIndex] == 0 || (!m_emuState.ANSIMode && m_emuState.VT52GraphicsMode))
2393 c = DECGRAPH_TO_CP437[(uint8_t)c];
2394 setChar(c);
2395 }
2396
2397 enableBlinkingText(m_prevBlinkingTextEnabled);
2398 int_enableCursor(m_prevCursorEnabled);
2399
2400 xSemaphoreGive(m_mutex);
2401
2402 if (m_resetRequested)
2403 reset();
2404}
2405
2406
2407void Terminal::execCtrlCode(uint8_t c)
2408{
2409 switch (c) {
2410
2411 // BS
2412 // backspace, move cursor one position to the left (without wrap).
2413 case ASCII_BS:
2414 if (m_emuState.cursorX > 1)
2415 setCursorPos(m_emuState.cursorX - 1, m_emuState.cursorY);
2416 else if (m_emuState.reverseWraparoundMode) {
2417 int newX = m_columns;
2418 int newY = m_emuState.cursorY - 1;
2419 if (newY == 0)
2420 newY = m_rows;
2421 setCursorPos(newX, newY);
2422 }
2423 break;
2424
2425 // HT
2426 // Go to next tabstop or end of line if no tab stop
2427 case ASCII_HT:
2428 nextTabStop();
2429 break;
2430
2431 // LF
2432 // Line feed
2433 case ASCII_LF:
2434 if (!m_emuState.cursorPastLastCol) {
2435 if (m_emuState.newLineMode)
2436 setCursorPos(1, m_emuState.cursorY);
2437 if (moveDown())
2438 scrollUp();
2439 }
2440 break;
2441
2442 // VT (vertical tab)
2443 // FF (form feed)
2444 // Move cursor down
2445 case ASCII_VT:
2446 case ASCII_FF:
2447 if (moveDown())
2448 scrollUp();
2449 break;
2450
2451 // CR
2452 // Carriage return. Move cursor at the beginning of the line.
2453 case ASCII_CR:
2454 setCursorPos(1, m_emuState.cursorY);
2455 break;
2456
2457 // SO
2458 // Shift Out. Switch to Alternate Character Set (G1)
2459 case ASCII_SO:
2460 m_emuState.characterSetIndex = 1;
2461 break;
2462
2463 // SI
2464 // Shift In. Switch to Standard Character Set (G0)
2465 case ASCII_SI:
2466 m_emuState.characterSetIndex = 0;
2467 break;
2468
2469 case ASCII_DEL:
2470 // nothing to do
2471 break;
2472
2473 // BELL
2474 case ASCII_BEL:
2475 sound('1', 800, 250, 100); // square wave, 800 Hz, 250ms, volume 100
2476 break;
2477
2478 // XOFF
2479 case ASCII_XOFF:
2480 //Serial.printf("recv XOFF\n");
2481 m_recvXOFF = true;
2482 break;
2483
2484 // XON
2485 case ASCII_XON:
2486 //Serial.printf("recv XON\n");
2487 m_recvXOFF = false;
2488 break;
2489
2490 default:
2491 break;
2492 }
2493}
2494
2495
2496// consume non-CSI and CSI (ESC is already consumed)
2497void Terminal::consumeESC()
2498{
2499
2500 if (!m_emuState.ANSIMode) {
2501 consumeESCVT52();
2502 return;
2503 }
2504
2505 uint8_t c = getNextCode(true); // true: process ctrl chars
2506
2507 if (c == '[') {
2508 // ESC [ : start of CSI sequence
2509 consumeCSI();
2510 return;
2511 }
2512
2513 if (c == ']') {
2514 // ESC ] : start of OSC sequence
2515 consumeOSC();
2516 return;
2517 }
2518
2519 if (c == FABGLEXT_STARTCODE && m_emuState.allowFabGLSequences > 0) {
2520 // ESC FABGLEXT_STARTCODE : FabGL specific sequence
2521 consumeFabGLSeq();
2522 return;
2523 }
2524
2525 if (c == 'P') {
2526 // ESC P : start of DCS sequence
2527 consumeDCS();
2528 return;
2529 }
2530
2531 #if FABGLIB_TERMINAL_DEBUG_REPORT_ESC
2532 logFmt("ESC%c\n", c);
2533 #endif
2534
2535 switch (c) {
2536
2537 // ESC c : RIS, reset terminal.
2538 case 'c':
2539 m_resetRequested = true; // reset() actually called by consumeInputQueue()
2540 break;
2541
2542 // ESC D : IND, line feed.
2543 case 'D':
2544 if (moveDown())
2545 scrollUp();
2546 break;
2547
2548 // ESC E : NEL, new line.
2549 case 'E':
2550 setCursorPos(1, m_emuState.cursorY);
2551 if (moveDown())
2552 scrollUp();
2553 break;
2554
2555 // ESC H : HTS, set tab stop at current column.
2556 case 'H':
2557 setTabStop(m_emuState.cursorX, true);
2558 break;
2559
2560 // ESC M : RI, move up one line keeping column position.
2561 case 'M':
2562 if (moveUp())
2563 scrollDown();
2564 break;
2565
2566 // ESC Z : DECID, returns TERMID
2567 case 'Z':
2568 //log("DECID\n");
2569 sendCSI();
2570 send(TERMID);
2571 break;
2572
2573 // ESC 7 : DECSC, save current cursor state (position and attributes)
2574 case '7':
2575 saveCursorState();
2576 break;
2577
2578 // ESC 8 : DECRC, restore current cursor state (position and attributes)
2579 case '8':
2580 restoreCursorState();
2581 break;
2582
2583 // ESC #
2584 case '#':
2585 c = getNextCode(true); // true: process ctrl chars
2586 switch (c) {
2587 // ESC # 3 : DECDHL, DEC double-height line, top half
2588 case '3':
2589 setLineDoubleWidth(m_emuState.cursorY, 2);
2590 break;
2591 // ESC # 4 : DECDHL, DEC double-height line, bottom half
2592 case '4':
2593 setLineDoubleWidth(m_emuState.cursorY, 3);
2594 break;
2595 // ESC # 5 : DECSWL, DEC single-width line
2596 case '5':
2597 setLineDoubleWidth(m_emuState.cursorY, 0);
2598 break;
2599 // ESC # 6 : DECDWL, DEC double-width line
2600 case '6':
2601 setLineDoubleWidth(m_emuState.cursorY, 1);
2602 break;
2603 // ESC # 8 :DECALN, DEC screen alignment test - fill screen with E's.
2604 case '8':
2605 erase(1, 1, m_columns, m_rows, 'E', false, false);
2606 break;
2607 }
2608 break;
2609
2610 // Start sequence defining character set:
2611 // ESC character_set_index character_set
2612 // character_set_index: '(' = G0 ')' = G1 '*' = G2 '+' = G3
2613 // character_set: '0' = VT100 graphics mapping '1' = VT100 graphics mapping 'B' = USASCII
2614 case '(':
2615 case ')':
2616 case '*':
2617 case '+':
2618 switch (getNextCode(true)) {
2619 case '0':
2620 case '2':
2621 m_emuState.characterSet[c - '('] = 0; // DEC Special Character and Line Drawing
2622 break;
2623 default: // 'B' and others
2624 m_emuState.characterSet[c - '('] = 1; // United States (USASCII)
2625 break;
2626 }
2627 break;
2628
2629 // ESC = : DECKPAM (Keypad Application Mode)
2630 case '=':
2631 m_emuState.keypadMode = KeypadMode::Application;
2632 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCSALL
2633 log("Keypad Application Mode\n");
2634 #endif
2635 break;
2636
2637 // ESC > : DECKPNM (Keypad Numeric Mode)
2638 case '>':
2639 m_emuState.keypadMode = KeypadMode::Numeric;
2640 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCSALL
2641 log("Keypad Numeric Mode\n");
2642 #endif
2643 break;
2644
2645 case ASCII_SPC:
2646 switch (getNextCode(true)) {
2647
2648 // ESC SPC F : S7C1T, Select 7-Bit C1 Control Characters
2649 case 'F':
2650 m_emuState.ctrlBits = 7;
2651 break;
2652
2653 // ESC SPC G : S8C1T, Select 8-Bit C1 Control Characters
2654 case 'G':
2655 if (m_emuState.conformanceLevel >= 2 && m_emuState.ANSIMode)
2656 m_emuState.ctrlBits = 8;
2657 break;
2658 }
2659 break;
2660
2661 // consume unknow char
2662 default:
2663 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
2664 logFmt("Unknown ESC %c\n", c);
2665 #endif
2666 break;
2667 }
2668}
2669
2670
2671// a parameter is a number. Parameters are separated by ';'. Example: "5;27;3"
2672// first parameter has index 0
2673// params array must have up to FABGLIB_MAX_CSI_PARAMS
2674uint8_t Terminal::consumeParamsAndGetCode(int * params, int * paramsCount, bool * questionMarkFound)
2675{
2676 // get parameters until maximum size reached or a command character has been found
2677 *paramsCount = 1; // one parameter is always assumed (even if not exists)
2678 *questionMarkFound = false;
2679 int * p = params;
2680 *p = 0;
2681 while (true) {
2682 uint8_t c = getNextCode(true); // true: process ctrl chars
2683
2684 #if FABGLIB_TERMINAL_DEBUG_REPORT_ESC
2685 log(c);
2686 #endif
2687
2688 if (c == '?') {
2689 *questionMarkFound = true;
2690 continue;
2691 }
2692
2693 // break the loop if a command has been found
2694 if (!isdigit(c) && c != ';') {
2695
2696 #if FABGLIB_TERMINAL_DEBUG_REPORT_ESC
2697 log('\n');
2698 #endif
2699
2700 // reset non specified parameters
2701 while (p < params + FABGLIB_MAX_CSI_PARAMS)
2702 *(++p) = 0;
2703
2704 return c;
2705 }
2706
2707 if (p < params + FABGLIB_MAX_CSI_PARAMS) {
2708 if (c == ';') {
2709 ++p;
2710 *p = 0;
2711 ++(*paramsCount);
2712 } else {
2713 // this is a digit
2714 *p = *p * 10 + (c - '0');
2715 }
2716 }
2717 }
2718}
2719
2720
2721// consume CSI sequence (ESC + '[' already consumed)
2722void Terminal::consumeCSI()
2723{
2724 #if FABGLIB_TERMINAL_DEBUG_REPORT_ESC
2725 log("ESC[");
2726 #endif
2727
2728 bool questionMarkFound;
2729 int params[FABGLIB_MAX_CSI_PARAMS];
2730 int paramsCount;
2731 uint8_t c = consumeParamsAndGetCode(params, &paramsCount, &questionMarkFound);
2732
2733 // ESC [ ? ... h
2734 // ESC [ ? ... l
2735 if (questionMarkFound && (c == 'h' || c == 'l')) {
2736 consumeDECPrivateModes(params, paramsCount, c);
2737 return;
2738 }
2739
2740 // ESC [ SPC ...
2741 if (c == ASCII_SPC) {
2742 consumeCSISPC(params, paramsCount);
2743 return;
2744 }
2745
2746 // ESC [ " ...
2747 if (c == '"') {
2748 consumeCSIQUOT(params, paramsCount);
2749 return;
2750 }
2751
2752 // process command in "c"
2753 switch (c) {
2754
2755 // ESC [ H : CUP, Move cursor to the indicated row, column
2756 // ESC [ f : HVP, Move cursor to the indicated row, column
2757 case 'H':
2758 case 'f':
2759 setCursorPos(params[1], getAbsoluteRow(params[0]));
2760 break;
2761
2762 // ESC [ g : TBC, Clear one or all tab stops
2763 case 'g':
2764 switch (params[0]) {
2765 case 0:
2766 setTabStop(m_emuState.cursorX, false); // clear tab stop at cursor
2767 break;
2768 case 3:
2769 setTabStop(0, false); // clear all tab stops
2770 break;
2771 }
2772 break;
2773
2774 // ESC [ C : CUF, Move cursor right the indicated # of columns.
2775 case 'C':
2776 setCursorPos(m_emuState.cursorX + tmax(1, params[0]), m_emuState.cursorY);
2777 break;
2778
2779 // ESC [ P : DCH, Delete the indicated # of characters on current line.
2780 case 'P':
2781 deleteAt(m_emuState.cursorX, m_emuState.cursorY, tmax(1, params[0]));
2782 break;
2783
2784 // ESC [ A : CUU, Move cursor up the indicated # of rows.
2785 case 'A':
2786 setCursorPos(m_emuState.cursorX, getAbsoluteRow(m_emuState.cursorY - tmax(1, params[0])));
2787 break;
2788
2789 // ESC [ Ps J : ED, Erase display
2790 // ESC [ ? Ps J : DECSED, Selective Erase in Display
2791 // Ps = 0 : from cursor to end of display (default)
2792 // Ps = 1 : erase from start to cursor
2793 // Ps = 2 : erase whole display
2794 // Erase also doubleWidth attributes
2795 case 'J':
2796 switch (params[0]) {
2797 case 0:
2798 erase(m_emuState.cursorX, m_emuState.cursorY, m_columns, m_emuState.cursorY, ASCII_SPC, false, questionMarkFound);
2799 erase(1, m_emuState.cursorY + 1, m_columns, m_rows, ASCII_SPC, false, questionMarkFound);
2800 break;
2801 case 1:
2802 erase(1, 1, m_columns, m_emuState.cursorY - 1, ASCII_SPC, false, questionMarkFound);
2803 erase(1, m_emuState.cursorY, m_emuState.cursorX, m_emuState.cursorY, ASCII_SPC, false, questionMarkFound);
2804 break;
2805 case 2:
2806 erase(1, 1, m_columns, m_rows, ASCII_SPC, false, questionMarkFound);
2807 break;
2808 }
2809 break;
2810
2811 // ESC [ Ps K : EL, Erase line
2812 // ESC [ ? Ps K : DECSEL, Selective Erase in line
2813 // Ps = 0 : from cursor to end of line (default)
2814 // Ps = 1 : erase from start of line to cursor
2815 // Ps = 2 : erase whole line
2816 // Maintain doubleWidth attributes
2817 case 'K':
2818 switch (params[0]) {
2819 case 0:
2820 erase(m_emuState.cursorX, m_emuState.cursorY, m_columns, m_emuState.cursorY, ASCII_SPC, true, questionMarkFound);
2821 break;
2822 case 1:
2823 erase(1, m_emuState.cursorY, m_emuState.cursorX, m_emuState.cursorY, ASCII_SPC, true, questionMarkFound);
2824 break;
2825 case 2:
2826 erase(1, m_emuState.cursorY, m_columns, m_emuState.cursorY, ASCII_SPC, true, questionMarkFound);
2827 break;
2828 }
2829 break;
2830
2831 // ESC [ Pn X : ECH, Erase Characters
2832 // Pn: number of characters to erase from cursor position (default is 1) up to the end of line
2833 case 'X':
2834 erase(m_emuState.cursorX, m_emuState.cursorY, tmin((int)m_columns, m_emuState.cursorX + tmax(1, params[0]) - 1), m_emuState.cursorY, ASCII_SPC, true, false);
2835 break;
2836
2837 // ESC [ r : DECSTBM, Set scrolling region; parameters are top and bottom row.
2838 case 'r':
2839 setScrollingRegion(tmax(params[0], 1), (params[1] < 1 ? m_rows : params[1]));
2840 break;
2841
2842 // ESC [ d : VPA, Move cursor to the indicated row, current column.
2843 case 'd':
2844 setCursorPos(m_emuState.cursorX, params[0]);
2845 break;
2846
2847 // ESC [ G : CHA, Move cursor to indicated column in current row.
2848 case 'G':
2849 setCursorPos(params[0], m_emuState.cursorY);
2850 break;
2851
2852 // ESC [ S : SU, Scroll up # lines (default = 1)
2853 case 'S':
2854 for (int i = tmax(1, params[0]); i > 0; --i)
2855 scrollUp();
2856 break;
2857
2858 // ESC [ T : SD, Scroll down # lines (default = 1)
2859 case 'T':
2860 for (int i = tmax(1, params[0]); i > 0; --i)
2861 scrollDown();
2862 break;
2863
2864 // ESC [ D : CUB, Cursor left
2865 case 'D':
2866 {
2867 int newX = m_emuState.cursorX - tmax(1, params[0]);
2868 if (m_emuState.reverseWraparoundMode && newX < 1) {
2869 newX = -newX;
2870 int newY = m_emuState.cursorY - newX / m_columns - 1;
2871 if (newY < 1)
2872 newY = m_rows + newY;
2873 newX = m_columns - (newX % m_columns);
2874 setCursorPos(newX, newY);
2875 } else
2876 setCursorPos(tmax(1, newX), m_emuState.cursorY);
2877 break;
2878 }
2879
2880 // ESC [ B : CUD, Cursor down
2881 case 'B':
2882 setCursorPos(m_emuState.cursorX, getAbsoluteRow(m_emuState.cursorY + tmax(1, params[0])));
2883 break;
2884
2885 // ESC [ m : SGR, Character Attributes
2886 case 'm':
2887 execSGRParameters(params, paramsCount);
2888 break;
2889
2890 // ESC [ L : IL, Insert Lines starting at cursor (default 1 line)
2891 case 'L':
2892 for (int i = tmax(1, params[0]); i > 0; --i)
2893 scrollDownAt(m_emuState.cursorY);
2894 break;
2895
2896 // ESC [ M : DL, Delete Lines starting at cursor (default 1 line)
2897 case 'M':
2898 for (int i = tmax(1, params[0]); i > 0; --i)
2899 scrollUpAt(m_emuState.cursorY);
2900 break;
2901
2902 // ESC [ h : SM, Set Mode
2903 // ESC [ l : SM, Unset Mode
2904 case 'h':
2905 case 'l':
2906 switch (params[0]) {
2907
2908 // IRM, Insert Mode
2909 case 4:
2910 m_emuState.insertMode = (c == 'h');
2911 break;
2912
2913 // LNM, Automatic Newline
2914 case 20:
2915 m_emuState.newLineMode = (c == 'h');
2916 break;
2917
2918 default:
2919 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
2920 logFmt("Unknown: ESC [ %d %c\n", params[0], c);
2921 #endif
2922 break;
2923 }
2924 break;
2925
2926 // ESC [ @ : ICH, Insert Character (default 1 character)
2927 case '@':
2928 insertAt(m_emuState.cursorX, m_emuState.cursorY, tmax(1, params[0]));
2929 break;
2930
2931 // ESC [ 0 c : Send Device Attributes
2932 case 'c':
2933 if (params[0] == 0) {
2934 sendCSI();
2935 send(TERMID);
2936 }
2937 break;
2938
2939 // ESC [ Ps q : DECLL, Load LEDs
2940 case 'q':
2941 paramsCount = tmax(1, paramsCount); // default paramater in case no params are provided
2942 for (int i = 0; i < paramsCount; ++i) {
2943 bool numLock, capsLock, scrollLock;
2944 m_keyboard->getLEDs(&numLock, &capsLock, &scrollLock);
2945 switch (params[i]) {
2946 case 0:
2947 numLock = capsLock = scrollLock = false;
2948 break;
2949 case 1:
2950 numLock = true;
2951 break;
2952 case 2:
2953 capsLock = true;
2954 break;
2955 case 3:
2956 scrollLock = true;
2957 break;
2958 case 21:
2959 numLock = false;
2960 break;
2961 case 22:
2962 capsLock = false;
2963 break;
2964 case 23:
2965 scrollLock = false;
2966 break;
2967 }
2968 m_keyboard->setLEDs(numLock, capsLock, scrollLock);
2969 }
2970 break;
2971
2972 // ESC [ Ps n : DSR, Device Status Report
2973 case 'n':
2974 switch (params[0]) {
2975 // Status Report
2976 case 5:
2977 sendCSI();
2978 send("0n");
2979 break;
2980 // Report Cursor Position (CPR)
2981 case 6:
2982 {
2983 sendCSI();
2984 char s[4];
2985 send(itoa(m_emuState.originMode ? m_emuState.cursorY - m_emuState.scrollingRegionTop + 1 : m_emuState.cursorY, s, 10));
2986 send(';');
2987 send(itoa(m_emuState.cursorX, s, 10));
2988 send('R');
2989 break;
2990 }
2991 }
2992 break;
2993
2994 default:
2995 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
2996 log("Unknown: ESC [ ");
2997 if (questionMarkFound)
2998 log("? ");
2999 for (int i = 0; i < paramsCount; ++i)
3000 logFmt("%d %c ", params[i], i < paramsCount - 1 ? ';' : c);
3001 log('\n');
3002 #endif
3003 break;
3004 }
3005}
3006
3007
3008// consume CSI " sequences
3009void Terminal::consumeCSIQUOT(int * params, int paramsCount)
3010{
3011 uint8_t c = getNextCode(true); // true: process ctrl chars
3012
3013 switch (c) {
3014
3015 // ESC [ P1; P2 " p : DECSCL, Select Conformance Level
3016 case 'p':
3017 m_emuState.conformanceLevel = params[0] - 60;
3018 if (params[0] == 61 || (paramsCount == 2 && params[1] == 1))
3019 m_emuState.ctrlBits = 7;
3020 else
3021 m_emuState.ctrlBits = 8;
3022 break;
3023
3024 // ESC [ Ps " q : DECSCA, Select character protection attribute
3025 case 'q':
3026 m_glyphOptions.userOpt2 = (params[0] == 1 ? 1 : 0);
3027 break;
3028
3029 }
3030}
3031
3032
3033// consume CSI SPC sequences
3034void Terminal::consumeCSISPC(int * params, int paramsCount)
3035{
3036 uint8_t c = getNextCode(true); // true: process ctrl chars
3037
3038 switch (c) {
3039
3040 // ESC [ Ps SPC q : DECSCUSR, Set Cursor Style
3041 // Ps: 0..1 blinking block, 2 steady block, 3 blinking underline, 4 steady underline, 5 blinking bar, 6 steady bar
3042 case 'q':
3043 m_emuState.cursorStyle = params[0];
3044 m_emuState.cursorBlinkingEnabled = (params[0] == 0) || (params[0] & 1);
3045 break;
3046
3047 default:
3048 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
3049 log("Unknown: ESC [ ");
3050 for (int i = 0; i < paramsCount; ++i)
3051 logFmt("%d %c ", params[i], i < paramsCount - 1 ? ';' : ASCII_SPC);
3052 logFmt(" %c\n", c);
3053 #endif
3054 break;
3055 }
3056}
3057
3058
3059// consume DEC Private Mode (DECSET/DECRST) sequences
3060// ESC [ ? # h <- set
3061// ESC [ ? # l <- reset
3062// "c" can be "h" or "l"
3063void Terminal::consumeDECPrivateModes(int const * params, int paramsCount, uint8_t c)
3064{
3065 bool set = (c == 'h');
3066 switch (params[0]) {
3067
3068 // ESC [ ? 1 h
3069 // ESC [ ? 1 l
3070 // DECCKM (default off): Cursor Keys Mode
3071 case 1:
3072 m_emuState.cursorKeysMode = set;
3073 break;
3074
3075 // ESC [ ? 2 h
3076 // ESC [ ? 2 l
3077 // DECANM (default on): ANSI Mode (off = VT52 Mode)
3078 case 2:
3079 m_emuState.ANSIMode = set;
3080 break;
3081
3082 // ESC [ ? 3 h
3083 // ESC [ ? 3 l
3084 // DECCOLM (default off): 132 Column Mode
3085 case 3:
3086 if (m_emuState.allow132ColumnMode) {
3087 set132ColumnMode(set);
3088 setCursorPos(1, 1);
3089 }
3090 break;
3091
3092 // ESC [ ? 4 h
3093 // ESC [ ? 4 l
3094 // DECSCLM (default off): Scrolling mode (jump or smooth)
3095 case 4:
3096 m_emuState.smoothScroll = set;
3097 break;
3098
3099
3100 // ESC [ ? 5 h
3101 // ESC [ ? 5 l
3102 // DECSCNM (default off): Reverse Video
3103 case 5:
3104 reverseVideo(set);
3105 break;
3106
3107 // ESC [ ? 6 h
3108 // ESC [ ? 6 l
3109 // DECOM (default off): Origin mode
3110 case 6:
3111 m_emuState.originMode = set;
3112 if (set)
3113 setCursorPos(m_emuState.cursorX, m_emuState.scrollingRegionTop);
3114 break;
3115
3116 // ESC [ ? 7 h
3117 // ESC [ ? 7 l
3118 // DECAWM (default on): Wraparound mode
3119 case 7:
3120 m_emuState.wraparound = set;
3121 break;
3122
3123 // ESC [ ? 8 h
3124 // ESC [ ? 8 l
3125 // DECARM (default on): Autorepeat mode
3126 case 8:
3127 m_emuState.keyAutorepeat = set;
3128 break;
3129
3130 // ESC [ ? 12 h
3131 // ESC [ ? 12 l
3132 // Start Blinking Cursor
3133 case 12:
3134 m_emuState.cursorBlinkingEnabled = set;
3135 break;
3136
3137 // ESC [ ? 25 h
3138 // ESC [ ? 25 l
3139 // DECTECM (default on): Make cursor visible.
3140 case 25:
3141 m_prevCursorEnabled = set; // consumeInputQueue() will set the value
3142 break;
3143
3144 // ESC [ ? 40 h
3145 // ESC [ ? 40 l
3146 // Allow 80 -> 132 column mode (default off)
3147 case 40:
3148 m_emuState.allow132ColumnMode = set;
3149 break;
3150
3151 // ESC [ ? 45 h
3152 // ESC [ ? 45 l
3153 // Reverse-wraparound mode (default off)
3154 case 45:
3155 m_emuState.reverseWraparoundMode = set;
3156 break;
3157
3158 // ESC [ ? 47 h
3159 // ESC [ ? 47 l
3160 // ESC [ ? 1047 h
3161 // ESC [ ? 1047 l
3162 // Use Alternate Screen Buffer
3163 case 47:
3164 case 1047:
3165 useAlternateScreenBuffer(set);
3166 break;
3167
3168 // ESC [ ? 77 h
3169 // ESC [ ? 77 l
3170 // DECBKM (default off): Backarrow key Mode
3171 case 67:
3172 m_emuState.backarrowKeyMode = set;
3173 break;
3174
3175 // ESC [ ? 1048 h : Save cursor as in DECSC
3176 // ESC [ ? 1048 l : Restore cursor as in DECSC
3177 case 1048:
3178 if (set)
3179 saveCursorState();
3180 else
3181 restoreCursorState();
3182 break;
3183
3184 // ESC [ ? 1049 h
3185 // ESC [ ? 1049 l
3186 // Save cursor as in DECSC and use Alternate Screen Buffer
3187 case 1049:
3188 if (set) {
3189 saveCursorState();
3190 useAlternateScreenBuffer(true);
3191 } else {
3192 useAlternateScreenBuffer(false);
3193 restoreCursorState();
3194 }
3195 break;
3196
3197 // ESC [ ? 7999 h
3198 // ESC [ ? 7999 l
3199 // Allows enhanced FabGL sequences (default disabled)
3200 // This set is "incremental". This is actually disabled when the counter reach 0.
3201 case 7999:
3202 enableFabGLSequences(set);
3203 break;
3204
3205 default:
3206 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
3207 logFmt("Unknown DECSET/DECRST: %d %c\n", params[0], c);
3208 #endif
3209 break;
3210 }
3211}
3212
3213
3214// exec SGR: Select Graphic Rendition
3215// ESC [ ...params... m
3216void Terminal::execSGRParameters(int const * params, int paramsCount)
3217{
3218 for (; paramsCount; ++params, --paramsCount) {
3219 switch (*params) {
3220
3221 // Normal
3222 case 0:
3223 m_glyphOptions.bold = 0;
3224 m_glyphOptions.reduceLuminosity = 0; // faint
3225 m_glyphOptions.italic = 0;
3226 m_glyphOptions.underline = 0;
3227 m_glyphOptions.userOpt1 = 0; // blink
3228 m_glyphOptions.blank = 0; // invisible
3229 m_glyphOptions.invert = 0; // inverse
3230 int_setForegroundColor(m_defaultForegroundColor);
3231 int_setBackgroundColor(m_defaultBackgroundColor);
3232 break;
3233
3234 // Bold
3235 case 1:
3236 m_glyphOptions.bold = 1;
3237 break;
3238
3239 // Faint (decreased intensity)
3240 case 2:
3241 m_glyphOptions.reduceLuminosity = 1;
3242 break;
3243
3244 // Select primary font
3245 case 10:
3246 m_emuState.characterSetIndex = 0;
3247 break;
3248
3249 // Disable Bold and Faint
3250 case 22:
3251 m_glyphOptions.bold = m_glyphOptions.reduceLuminosity = 0;
3252 break;
3253
3254 // Italic
3255 case 3:
3256 m_glyphOptions.italic = 1;
3257 break;
3258
3259 // Disable Italic
3260 case 23:
3261 m_glyphOptions.italic = 0;
3262 break;
3263
3264 // Underline
3265 case 4:
3266 m_glyphOptions.underline = 1;
3267 break;
3268
3269 // Disable Underline
3270 case 24:
3271 m_glyphOptions.underline = 0;
3272 break;
3273
3274 // Blink
3275 case 5:
3276 m_glyphOptions.userOpt1 = 1;
3277 break;
3278
3279 // Disable Blink
3280 case 25:
3281 m_glyphOptions.userOpt1 = 0;
3282 break;
3283
3284 // Inverse
3285 case 7:
3286 m_glyphOptions.invert = 1;
3287 break;
3288
3289 // Disable Inverse
3290 case 27:
3291 m_glyphOptions.invert = 0;
3292 break;
3293
3294 // Invisible
3295 case 8:
3296 m_glyphOptions.blank = 1;
3297 break;
3298
3299 // Disable Invisible
3300 case 28:
3301 m_glyphOptions.blank = 0;
3302 break;
3303
3304 // Set Foreground color
3305 case 30 ... 37:
3306 int_setForegroundColor( (Color) (*params - 30) );
3307 break;
3308
3309 // Set Foreground color to Default
3310 case 39:
3311 int_setForegroundColor(m_defaultForegroundColor);
3312 break;
3313
3314 // Set Background color
3315 case 40 ... 47:
3316 int_setBackgroundColor( (Color) (*params - 40) );
3317 break;
3318
3319 // Set Background color to Default
3320 case 49:
3321 int_setBackgroundColor(m_defaultBackgroundColor);
3322 break;
3323
3324 // Set Bright Foreground color
3325 case 90 ... 97:
3326 int_setForegroundColor( (Color) (8 + *params - 90) );
3327 break;
3328
3329 // Set Bright Background color
3330 case 100 ... 107:
3331 int_setBackgroundColor( (Color) (8 + *params - 100) );
3332 break;
3333
3334 default:
3335 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
3336 logFmt("Unknown: ESC [ %d m\n", *params);
3337 #endif
3338 break;
3339
3340 }
3341 }
3342 if (m_bitmappedDisplayController && isActive())
3343 m_canvas->setGlyphOptions(m_glyphOptions);
3344}
3345
3346
3347// "ESC P" (DCS) already consumed
3348// consume from parameters to ST (that is ESC "\")
3349void Terminal::consumeDCS()
3350{
3351 #if FABGLIB_TERMINAL_DEBUG_REPORT_ESC
3352 log("ESC P");
3353 #endif
3354
3355 // get parameters
3356 bool questionMarkFound;
3357 int params[FABGLIB_MAX_CSI_PARAMS];
3358 int paramsCount;
3359 uint8_t c = consumeParamsAndGetCode(params, &paramsCount, &questionMarkFound);
3360
3361 // get DCS content up to ST
3362 uint8_t content[FABGLIB_MAX_DCS_CONTENT];
3363 int contentLength = 0;
3364 content[contentLength++] = c;
3365 while (true) {
3366 uint8_t c = getNextCode(false); // false: do notprocess ctrl chars, ESC needed here
3367 if (c == ASCII_ESC) {
3368 if (getNextCode(false) == '\\')
3369 break; // ST found
3370 else {
3371 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
3372 log("DCS failed, expected ST\n");
3373 #endif
3374 return; // fail
3375 }
3376 } else if (contentLength == FABGLIB_MAX_DCS_CONTENT) {
3377 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
3378 log("DCS failed, content too long\n");
3379 #endif
3380 return; // fail
3381 }
3382 content[contentLength++] = c;
3383 }
3384
3385 // $q : DECRQSS, Request Selection or Setting
3386 if (m_emuState.conformanceLevel >= 3 && contentLength > 2 && content[0] == '$' && content[1] == 'q') {
3387
3388 // "p : request DECSCL setting, reply with: DCS 1 $ r DECSCL " p ST
3389 // where DECSCL is: 6 m_emuState.conformanceLevel ; bits " p
3390 // where bits is 0 = 8 bits, 1 = 7 bits
3391 if (contentLength == 4 && content[2] == '\"' && content[3] == 'p') {
3392 sendDCS();
3393 send("1$r6");
3394 send('0' + m_emuState.conformanceLevel);
3395 send(';');
3396 send(m_emuState.ctrlBits == 7 ? '1' : '0');
3397 send("\"p\e\\");
3398 return; // processed
3399 }
3400
3401 }
3402
3403 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
3404 log("Unknown: ESC P ");
3405 for (int i = 0; i < paramsCount; ++i)
3406 logFmt("%d %c ", params[i], i < paramsCount - 1 ? ';' : ASCII_SPC);
3407 logFmt("%.*s ESC \\\n", contentLength, content);
3408 #endif
3409}
3410
3411
3412void Terminal::consumeESCVT52()
3413{
3414 uint8_t c = getNextCode(false);
3415
3416 #if FABGLIB_TERMINAL_DEBUG_REPORT_ESC
3417 logFmt("ESC%c\n", c);
3418 #endif
3419
3420 // this allows fabgl sequences even in VT52 mode
3421 if (c == FABGLEXT_STARTCODE && m_emuState.allowFabGLSequences > 0) {
3422 // ESC FABGLEXT_STARTCODE : FabGL specific sequence
3423 consumeFabGLSeq();
3424 return;
3425 }
3426
3427 switch (c) {
3428
3429 // ESC < : Exit VT52 mode, goes to VT100 mode
3430 case '<':
3431 m_emuState.ANSIMode = true;
3432 m_emuState.conformanceLevel = 1;
3433 break;
3434
3435 // ESC A : Cursor Up
3436 case 'A':
3437 setCursorPos(m_emuState.cursorX, m_emuState.cursorY - 1);
3438 break;
3439
3440 // ESC B : Cursor Down
3441 case 'B':
3442 setCursorPos(m_emuState.cursorX, m_emuState.cursorY + 1);
3443 break;
3444
3445 // ESC C : Cursor Right
3446 case 'C':
3447 setCursorPos(m_emuState.cursorX + 1, m_emuState.cursorY);
3448 break;
3449
3450 // ESC D : Cursor Left
3451 case 'D':
3452 setCursorPos(m_emuState.cursorX -1, m_emuState.cursorY);
3453 break;
3454
3455 // ESC H : Cursor to Home Position
3456 case 'H':
3457 setCursorPos(1, 1);
3458 break;
3459
3460 // ESC I : Reverse Line Feed
3461 case 'I':
3462 if (moveUp())
3463 scrollDown();
3464 break;
3465
3466 // ESC J : Erase from cursor to end of screen
3467 case 'J':
3468 erase(m_emuState.cursorX, m_emuState.cursorY, m_columns, m_emuState.cursorY, ASCII_SPC, false, false);
3469 erase(1, m_emuState.cursorY + 1, m_columns, m_rows, ASCII_SPC, false, false);
3470 break;
3471
3472 // ESC K : Erase from cursor to end of line
3473 case 'K':
3474 erase(m_emuState.cursorX, m_emuState.cursorY, m_columns, m_emuState.cursorY, ASCII_SPC, true, false);
3475 break;
3476
3477 // ESC Y row col : Direct Cursor Addressing
3478 case 'Y':
3479 {
3480 int row = getNextCode(false) - 31;
3481 int col = getNextCode(false) - 31;
3482 setCursorPos(col, row);
3483 break;
3484 }
3485
3486 // ESC Z : Identify
3487 case 'Z':
3488 send("\e/Z");
3489 break;
3490
3491 // ESC = : Enter Alternate Keypad Mode
3492 case '=':
3493 m_emuState.keypadMode = KeypadMode::Application;
3494 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCSALL
3495 log("Enter Alternate Keypad Mode\n");
3496 #endif
3497 break;
3498
3499 // ESC > : Exit Alternate Keypad Mode
3500 case '>':
3501 m_emuState.keypadMode = KeypadMode::Numeric;
3502 #if FABGLIB_TERMINAL_DEBUG_REPORT_DESCSALL
3503 log("Exit Alternate Keypad Mode\n");
3504 #endif
3505 break;
3506
3507 // ESC F : Enter Graphics Mode
3508 case 'F':
3509 m_emuState.VT52GraphicsMode = true;
3510 break;
3511
3512 // ESC G : Exit Graphics Mode
3513 case 'G':
3514 m_emuState.VT52GraphicsMode = false;
3515 break;
3516
3517 // consume unknow char
3518 default:
3519 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
3520 logFmt("Unknown ESC %c\n", c);
3521 #endif
3522 break;
3523 }
3524
3525}
3526
3527
3528// consume OSC sequence (ESC + ']' already consumed)
3529// OSC is terminated by ASCII_BEL or ST (ESC + '\')
3530void Terminal::consumeOSC()
3531{
3532 #if FABGLIB_TERMINAL_DEBUG_REPORT_ESC
3533 log("ESC]");
3534 #endif
3535
3536 char prevChar = 0;
3537 while (true) {
3538 uint8_t c = getNextCode(false);
3539 #if FABGLIB_TERMINAL_DEBUG_REPORT_OSC_CONTENT
3540 logFmt("OSC: %02X %s%c\n", (int)c, (c <= ASCII_SPC ? CTRLCHAR_TO_STR[(int)(c)] : ""), (c > ASCII_SPC ? c : ASCII_SPC));
3541 #endif
3542 if (c == ASCII_BEL || (c == '\\' && prevChar == ASCII_ESC))
3543 break;
3544 prevChar = c;
3545 }
3546}
3547
3548
3549SoundGenerator * Terminal::soundGenerator()
3550{
3551 if (!m_soundGenerator)
3552 m_soundGenerator = new SoundGenerator;
3553 return m_soundGenerator;
3554}
3555
3556
3557void Terminal::sound(int waveform, int frequency, int duration, int volume)
3558{
3559 auto sg = soundGenerator(); // make sure m_soundGenerator is valid
3560 switch (waveform) {
3561 case '0':
3562 sg->playSound(SineWaveformGenerator(), frequency, duration, volume);
3563 break;
3564 case '1':
3565 sg->playSound(SquareWaveformGenerator(), frequency, duration, volume);
3566 break;
3567 case '2':
3568 sg->playSound(TriangleWaveformGenerator(), frequency, duration, volume);
3569 break;
3570 case '3':
3571 sg->playSound(SawtoothWaveformGenerator(), frequency, duration, volume);
3572 break;
3573 case '4':
3574 sg->playSound(NoiseWaveformGenerator(), frequency, duration, volume);
3575 break;
3576 case '5':
3577 sg->playSound(VICNoiseGenerator(), frequency, duration, volume);
3578 break;
3579 }
3580}
3581
3582
3583// get a single byte from getNextCode() or m_extNextCode
3584uint8_t Terminal::extGetByteParam()
3585{
3586 if (m_extNextCode > -1) {
3587 auto r = m_extNextCode;
3588 m_extNextCode = -1;
3589 return r;
3590 } else
3591 return getNextCode(false);
3592}
3593
3594
3595// get integer parameter terminated by non digit character
3596// the integer may have a single space in place of sign (ie "-10", "+10", " 10" are valid values)
3597int Terminal::extGetIntParam()
3598{
3599 int sign = -2; // -2 = not set
3600 int val = 0;
3601 while (true) {
3602 uint8_t c = extGetByteParam();
3603 if (!isdigit(c)) {
3604 // sign?
3605 if (sign == -2) {
3606 if (c == '-') {
3607 sign = -1;
3608 continue;
3609 } else if (c == '+' || c == ' ') {
3610 sign = 1;
3611 continue;
3612 }
3613 }
3614 // reinsert non digit character and terminate
3615 m_extNextCode = c;
3616 break;
3617 } else if (sign == -2)
3618 sign = 1; // default positive
3619 val = val * 10 + c - '0';
3620 }
3621 return val * sign;
3622}
3623
3624// get a command parameter terminated by non alpha character
3625// str must be already allocated by FABGLEXT_MAXSUBCMDLEN characters
3626// max number of characters is specified by
3627void Terminal::extGetCmdParam(char * cmd)
3628{
3629 int len = 0;
3630 for (; len < FABGLEXT_MAXSUBCMDLEN - 1; ++len) {
3631 uint8_t c = extGetByteParam();
3632 if (!isalpha(c)) {
3633 // reinsert non digit character and terminate
3634 m_extNextCode = c;
3635 break;
3636 }
3637 cmd[len] = c;
3638 }
3639 cmd[len] = 0;
3640}
3641
3642
3643// consume FabGL specific sequence (ESC FABGLEXT_STARTCODE ....)
3644void Terminal::consumeFabGLSeq()
3645{
3646 #if FABGLIB_TERMINAL_DEBUG_REPORT_ESC
3647 log("ESC FABGLEXT_STARTCODE");
3648 #endif
3649
3650 m_extNextCode = -1;
3651
3652 uint8_t c = extGetByteParam();
3653
3654 // process command in "c"
3655 switch (c) {
3656
3657 // Clear terminal area
3658 // Seq:
3659 // ESC FABGLEXT_STARTCODE FABGLEXTX_CLEAR FABGLEXT_ENDCODE
3660 // params:
3661 // none
3662 case FABGLEXTX_CLEAR:
3663 extGetByteParam(); // FABGLEXT_ENDCODE
3664 syncDisplayController();
3665 erase(1, 1, m_columns, m_rows, ASCII_SPC, false, false);
3666 break;
3667
3668 // Enable/disable cursor
3669 // Seq:
3670 // ESC FABGLEXT_STARTCODE FABGLEXTX_ENABLECURSOR STATE FABGLEXT_ENDCODE
3671 // params:
3672 // STATE (byte): '0' = disable (and others), '1' = enable
3673 case FABGLEXTX_ENABLECURSOR:
3674 m_prevCursorEnabled = (extGetByteParam() == '1');
3675 extGetByteParam(); // FABGLEXT_ENDCODE
3676 break;
3677
3678 // Get cursor horizontal position (1 = leftmost pos)
3679 // Seq:
3680 // ESC FABGLEXT_STARTCODE FABGLEXTB_GETCURSORCOL FABGLEXT_ENDCODE
3681 // params:
3682 // none
3683 // return:
3684 // byte: FABGLEXT_REPLYCODE (reply tag)
3685 // byte: column
3686 case FABGLEXTB_GETCURSORCOL:
3687 extGetByteParam(); // FABGLEXT_ENDCODE
3688 send(FABGLEXT_REPLYCODE);
3689 send(m_emuState.cursorX);
3690 break;
3691
3692 // Get cursor vertical position (1 = topmost pos)
3693 // Seq:
3694 // ESC FABGLEXT_STARTCODE FABGLEXTB_GETCURSORROW FABGLEXT_ENDCODE
3695 // params:
3696 // none
3697 // return:
3698 // byte: FABGLEXT_REPLYCODE (reply tag)
3699 // byte: row
3700 case FABGLEXTB_GETCURSORROW:
3701 extGetByteParam(); // FABGLEXT_ENDCODE
3702 send(FABGLEXT_REPLYCODE);
3703 send(m_emuState.cursorY);
3704 break;
3705
3706 // Get cursor position
3707 // Seq:
3708 // ESC FABGLEXT_STARTCODE FABGLEXTB_GETCURSORPOS FABGLEXT_ENDCODE
3709 // params:
3710 // none
3711 // return:
3712 // byte: FABGLEXT_REPLYCODE (reply tag)
3713 // byte: column
3714 // byte: row
3715 case FABGLEXTB_GETCURSORPOS:
3716 extGetByteParam(); // FABGLEXT_ENDCODE
3717 send(FABGLEXT_REPLYCODE);
3718 send(m_emuState.cursorX);
3719 send(m_emuState.cursorY);
3720 break;
3721
3722 // Set cursor position
3723 // Seq:
3724 // ESC FABGLEXT_STARTCODE FABGLEXTB_SETCURSORPOS COL ROW FABGLEXT_ENDCODE
3725 // params:
3726 // COL (byte): column (1 = first column)
3727 // ROW (byte): row (1 = first row)
3728 case FABGLEXTB_SETCURSORPOS:
3729 {
3730 uint8_t col = extGetByteParam();
3731 uint8_t row = extGetByteParam();
3732 extGetByteParam(); // FABGLEXT_ENDCODE
3733 setCursorPos(col, getAbsoluteRow(row));
3734 break;
3735 }
3736
3737 // Set cursor position (textual parameters)
3738 // Seq:
3739 // ESC FABGLEXT_STARTCODE FABGLEXTX_SETCURSORPOS COL ';' ROW FABGLEXT_ENDCODE
3740 // params:
3741 // COL (text): column (1 = first column)
3742 // ROW (text): row (1 = first row)
3743 case FABGLEXTX_SETCURSORPOS:
3744 {
3745 uint8_t col = extGetIntParam();
3746 extGetByteParam(); // ';'
3747 uint8_t row = extGetIntParam();
3748 extGetByteParam(); // FABGLEXT_ENDCODE
3749 setCursorPos(col, getAbsoluteRow(row));
3750 break;
3751 }
3752
3753 // Insert a blank space at current position, moving next CHARSTOMOVE characters to the right (even on multiple lines).
3754 // Advances cursor by one position. Characters after CHARSTOMOVE length are overwritter.
3755 // Vertical scroll may occurs.
3756 // Seq:
3757 // ESC FABGLEXT_STARTCODE FABGLEXTB_INSERTSPACE CHARSTOMOVE_L CHARSTOMOVE_H FABGLEXT_ENDCODE
3758 // params:
3759 // CHARSTOMOVE_L, CHARSTOMOVE_H (byte): number of chars to move to the right by one position
3760 // return:
3761 // byte: FABGLEXT_REPLYCODE (reply tag)
3762 // byte: 0 = vertical scroll not occurred, 1 = vertical scroll occurred
3763 case FABGLEXTB_INSERTSPACE:
3764 {
3765 uint8_t charsToMove_L = extGetByteParam();
3766 uint8_t charsToMove_H = extGetByteParam();
3767 extGetByteParam(); // FABGLEXT_ENDCODE
3768 bool scroll = multilineInsertChar(charsToMove_L | charsToMove_H << 8);
3769 send(FABGLEXT_REPLYCODE);
3770 send(scroll);
3771 break;
3772 }
3773
3774 // Delete character at current position, moving next CHARSTOMOVE characters to the left (even on multiple lines).
3775 // Characters after CHARSTOMOVE are filled with spaces.
3776 // Seq:
3777 // ESC FABGLEXT_STARTCODE FABGLEXTB_DELETECHAR CHARSTOMOVE_L CHARSTOMOVE_H FABGLEXT_ENDCODE
3778 // params:
3779 // CHARSTOMOVE_L, CHARSTOMOVE_H (byte): number of chars to move to the left by one position
3780 case FABGLEXTB_DELETECHAR:
3781 {
3782 uint8_t charsToMove_L = extGetByteParam();
3783 uint8_t charsToMove_H = extGetByteParam();
3784 extGetByteParam(); // FABGLEXT_ENDCODE
3785 multilineDeleteChar(charsToMove_L | charsToMove_H << 8);
3786 break;
3787 }
3788
3789 // Move cursor at left, wrapping lines if necessary
3790 // Seq:
3791 // ESC FABGLEXT_STARTCODE FABGLEXTB_CURSORLEFT COUNT_L COUNT_H FABGLEXT_ENDCODE
3792 // params:
3793 // COUNT_L, COUNT_H (byte): number of positions to move to the left
3794 case FABGLEXTB_CURSORLEFT:
3795 {
3796 uint8_t count_L = extGetByteParam();
3797 uint8_t count_H = extGetByteParam();
3798 extGetByteParam(); // FABGLEXT_ENDCODE
3799 move(-(count_L | count_H << 8));
3800 break;
3801 }
3802
3803 // Move cursor at right, wrapping lines if necessary
3804 // Seq:
3805 // ESC FABGLEXT_STARTCODE FABGLEXTB_CURSORRIGHT COUNT_L COUNT_H FABGLEXT_ENDCODE
3806 // params:
3807 // COUNT (byte): number of positions to move to the right
3808 case FABGLEXTB_CURSORRIGHT:
3809 {
3810 uint8_t count_L = extGetByteParam();
3811 uint8_t count_H = extGetByteParam();
3812 extGetByteParam(); // FABGLEXT_ENDCODE
3813 move(count_L | count_H << 8);
3814 break;
3815 }
3816
3817 // Sets char CHAR at current position and advance one position. Scroll if necessary.
3818 // This do not interpret character as a special code, but just sets it.
3819 // Seq:
3820 // ESC FABGLEXT_STARTCODE FABGLEXTB_SETCHAR CHAR FABGLEXT_ENDCODE
3821 // params:
3822 // CHAR (byte): character to set
3823 // return:
3824 // byte: FABGLEXT_REPLYCODE (reply tag)
3825 // byte: 0 = vertical scroll not occurred, 1 = vertical scroll occurred
3826 case FABGLEXTB_SETCHAR:
3827 {
3828 bool scroll = setChar(extGetByteParam());
3829 extGetByteParam(); // FABGLEXT_ENDCODE
3830 send(FABGLEXT_REPLYCODE);
3831 send(scroll);
3832 break;
3833 }
3834
3835 // Return virtual key state
3836 // Seq:
3837 // ESC FABGLEXT_STARTCODE FABGLEXTB_ISVKDOWN VKCODE FABGLEXT_ENDCODE
3838 // params:
3839 // VKCODE : virtual key code to check
3840 // return:
3841 // byte: FABGLEXT_REPLYCODE (reply tag)
3842 // char: '0' = key is up, '1' = key is down
3843 case FABGLEXTB_ISVKDOWN:
3844 {
3845 VirtualKey vk = (VirtualKey) extGetByteParam();
3846 extGetByteParam(); // FABGLEXT_ENDCODE
3847 send(FABGLEXT_REPLYCODE);
3848 send(keyboard()->isVKDown(vk) ? '1' : '0');
3849 break;
3850 }
3851
3852 // Disable FabGL sequences
3853 // Seq:
3854 // ESC FABGLEXT_STARTCODE FABGLEXTB_DISABLEFABSEQ FABGLEXT_ENDCODE
3855 case FABGLEXTB_DISABLEFABSEQ:
3856 extGetByteParam(); // FABGLEXT_ENDCODE
3857 enableFabGLSequences(false);
3858 break;
3859
3860 // Set terminal type
3861 // Seq:
3862 // ESC FABGLEXT_STARTCODE FABGLEXTB_SETTERMTYPE TERMINDEX FABGLEXT_ENDCODE
3863 // params:
3864 // TERMINDEX : index of terminal to emulate (TermType)
3865 case FABGLEXTB_SETTERMTYPE:
3866 {
3867 auto termType = (TermType) extGetByteParam();
3868 extGetByteParam(); // FABGLEXT_ENDCODE
3869 int_setTerminalType(termType);
3870 break;
3871 }
3872
3873 // Set foreground color
3874 // Seq:
3875 // ESC FABGLEXT_STARTCODE FABGLEXTB_SETFGCOLOR COLORINDEX FABGLEXT_ENDCODE
3876 // params:
3877 // COLORINDEX : 0..15 (index of Color enum)
3878 case FABGLEXTB_SETFGCOLOR:
3879 int_setForegroundColor((Color) extGetByteParam());
3880 extGetByteParam(); // FABGLEXT_ENDCODE
3881 break;
3882
3883 // Set background color
3884 // Seq:
3885 // ESC FABGLEXT_STARTCODE FABGLEXTB_SETBGCOLOR COLORINDEX FABGLEXT_ENDCODE
3886 // params:
3887 // COLORINDEX : 0..15 (index of Color enum)
3888 case FABGLEXTB_SETBGCOLOR:
3889 int_setBackgroundColor((Color) extGetByteParam());
3890 extGetByteParam(); // FABGLEXT_ENDCODE
3891 break;
3892
3893 // Set char style
3894 // Seq:
3895 // ESC FABGLEXT_STARTCODE FABGLEXTB_SETCHARSTYLE STYLEINDEX ENABLE FABGLEXT_ENDCODE
3896 // params:
3897 // STYLEINDEX : 0 = bold, 1 = reduce luminosity, 2 = italic, 3 = underline, 4 = blink, 5 = blank, 6 = inverse
3898 // ENABLE : 0 = disable, 1 = enable
3899 case FABGLEXTB_SETCHARSTYLE:
3900 {
3901 int idx = extGetByteParam();
3902 int val = extGetByteParam();
3903 extGetByteParam(); // FABGLEXT_ENDCODE
3904 switch (idx) {
3905 case 0: // bold
3906 m_glyphOptions.bold = val;
3907 break;
3908 case 1: // reduce luminosity
3909 m_glyphOptions.reduceLuminosity = val;
3910 break;
3911 case 2: // italic
3912 m_glyphOptions.italic = val;
3913 break;
3914 case 3: // underline
3915 m_glyphOptions.underline = val;
3916 break;
3917 case 4: // blink
3918 m_glyphOptions.userOpt1 = val;
3919 break;
3920 case 5: // blank
3921 m_glyphOptions.blank = val;
3922 break;
3923 case 6: // inverse
3924 m_glyphOptions.invert = val;
3925 break;
3926 }
3927 if (m_bitmappedDisplayController && isActive())
3928 m_canvas->setGlyphOptions(m_glyphOptions);
3929 break;
3930 }
3931
3932 // Setup GPIO
3933 // Seq:
3934 // ESC FABGLEXT_STARTCODE FABGLEXTX_SETUPGPIO MODE GPIONUM FABGLEXT_ENDCODE
3935 // params:
3936 // MODE (char) :
3937 // '-' = disable input/output
3938 // 'I' = input only
3939 // 'O' = output only
3940 // 'D' = output only with open-drain
3941 // 'E' = output and input with open-drain
3942 // 'X' = output and input
3943 // GPIONUM (text) : '0'-'39' (not all usable!)
3944 case FABGLEXTX_SETUPGPIO:
3945 {
3946 auto mode = GPIO_MODE_DISABLE;
3947 switch (extGetByteParam()) {
3948 case 'I':
3949 mode = GPIO_MODE_INPUT;
3950 break;
3951 case 'O':
3952 mode = GPIO_MODE_OUTPUT;
3953 break;
3954 case 'D':
3955 mode = GPIO_MODE_OUTPUT_OD;
3956 break;
3957 case 'E':
3958 mode = GPIO_MODE_INPUT_OUTPUT_OD;
3959 break;
3960 case 'X':
3961 mode = GPIO_MODE_INPUT_OUTPUT;
3962 break;
3963 }
3964 auto gpio = (gpio_num_t) extGetIntParam();
3965 extGetByteParam(); // FABGLEXT_ENDCODE
3966 configureGPIO(gpio, mode);
3967 break;
3968 }
3969
3970 // Set GPIO
3971 // Seq:
3972 // ESC FABGLEXT_STARTCODE FABGLEXTX_SETGPIO VALUE GPIONUM FABGLEXT_ENDCODE
3973 // params:
3974 // VALUE (char) : 0 or '0' or 'L' = low (and others), 1 or '1' or 'H' = high
3975 // GPIONUM (text) : '0'-'39' (not all usable!)
3976 case FABGLEXTX_SETGPIO:
3977 {
3978 auto l = extGetByteParam();
3979 auto level = (l == 1 || l == '1' || l == 'H') ? 1 : 0;
3980 auto gpio = (gpio_num_t) extGetIntParam();
3981 extGetByteParam(); // FABGLEXT_ENDCODE
3982 gpio_set_level(gpio, level);
3983 break;
3984 }
3985
3986 // Get GPIO
3987 // Seq:
3988 // ESC FABGLEXT_STARTCODE FABGLEXTX_GETGPIO GPIONUM FABGLEXT_ENDCODE
3989 // params:
3990 // GPIONUM (text) : '0'-'39' (not all usable!)
3991 // return:
3992 // byte: FABGLEXT_REPLYCODE (reply tag)
3993 // char: '0' = low, '1' = high
3994 case FABGLEXTX_GETGPIO:
3995 {
3996 auto gpio = (gpio_num_t) extGetIntParam();
3997 extGetByteParam(); // FABGLEXT_ENDCODE
3998 send(FABGLEXT_REPLYCODE);
3999 send(gpio_get_level(gpio) ? '1' : '0');
4000 break;
4001 }
4002
4003 // Setup ADC
4004 // Seq:
4005 // ESC FABGLEXT_STARTCODE FABGLEXTX_SETUPADC RESOLUTION ';' ATTENUATION ';' GPIONUM FABGLEXT_ENDCODE
4006 // params:
4007 // RESOLUTION (text) : '9', '10', '11', '12'
4008 // ATTENUATION (text) :
4009 // '0' = 0dB (reduced to 1/1), full-scale voltage 1.1 V, accurate between 100 and 950 mV
4010 // '1' = 2.5dB (reduced to 1/1.34), full-scale voltage 1.5 V, accurate between 100 and 1250 mV
4011 // '2' = 6dB (reduced to 1/2), full-scale voltage 2.2 V, accurate between 150 to 1750 mV
4012 // '3' = 11dB (reduced to 1/3.6), full-scale voltage 3.9 V (maximum volatage is still 3.3V!!), accurate between 150 to 2450 mV
4013 // GPIONUM (text) : '32'...'39'
4014 case FABGLEXTX_SETUPADC:
4015 {
4016 auto width = (adc_bits_width_t) (extGetIntParam() - 9);
4017 extGetByteParam(); // ';'
4018 auto atten = (adc_atten_t) extGetIntParam();
4019 extGetByteParam(); // ';'
4020 auto channel = ADC1_GPIO2Channel((gpio_num_t)extGetIntParam());
4021 extGetByteParam(); // FABGLEXT_ENDCODE
4022 adc1_config_width(width);
4023 adc1_config_channel_atten(channel, atten);
4024 break;
4025 }
4026
4027 // Read ADC
4028 // Seq:
4029 // ESC FABGLEXT_STARTCODE FABGLEXTX_READADC GPIONUM FABGLEXT_ENDCODE
4030 // params:
4031 // GPIONUM (text) : '32'...'39'
4032 // return:
4033 // byte: FABGLEXT_REPLYCODE (reply tag)
4034 // char: 1 hex digit of 16 bit value (most significant nibble)
4035 // char: 2 hex digit of 16 bit value
4036 // char: 3 hex digit of 16 bit value (least significant nibble)
4037 //
4038 // Example of return value if read value is 160 (0x0A0):
4039 // '0'
4040 // 'A'
4041 // '0'
4042 case FABGLEXTX_READADC:
4043 {
4044 auto val = adc1_get_raw(ADC1_GPIO2Channel((gpio_num_t)extGetIntParam()));
4045 extGetByteParam(); // FABGLEXT_ENDCODE
4046 send(FABGLEXT_REPLYCODE);
4047 send(toupper(digit2hex((val & 0xF00) >> 8)));
4048 send(toupper(digit2hex((val & 0x0F0) >> 4)));
4049 send(toupper(digit2hex(val & 0x00F)));
4050 break;
4051 }
4052
4053 // Sound
4054 // Seq:
4055 // ESC FABGLEXT_STARTCODE FABGLEXTX_SOUND WAVEFORM ';' FREQUENCY ';' DURATION ';' VOLUME FABGLEXT_ENDCODE
4056 // params:
4057 // WAVEFORM (char) : '0' = SINE, '1' = SQUARE, '2' = TRIANGLE, '3' = SAWTOOTH, '4' = NOISE, '5' = VIC NOISE
4058 // FREQUENCY (text) : frequency in Hertz
4059 // DURATION (text) : duration in milliseconds
4060 // VOLUME (text) : volume (max is 127)
4061 case FABGLEXTX_SOUND:
4062 {
4063 char waveform = extGetByteParam();
4064 extGetByteParam(); // ';'
4065 uint16_t frequency = extGetIntParam();
4066 extGetByteParam(); // ';'
4067 uint16_t duration = extGetIntParam();
4068 extGetByteParam(); // ';'
4069 uint8_t volume = extGetIntParam() & 0x7f;
4070 extGetByteParam(); // FABGLEXT_ENDCODE
4071 sound(waveform, frequency, duration, volume);
4072 break;
4073 }
4074
4075 // Begin of a graphics command
4076 // Seq:
4077 // ESC FABGLEXT_STARTCODE FABGLEXTX_GRAPHICSCMD ...
4078 case FABGLEXTX_GRAPHICSCMD:
4079 consumeFabGLGraphicsSeq();
4080 break;
4081
4082 // Show or hide mouse pointer
4083 // Seq:
4084 // ESC FABGLEXT_STARTCODE FABGLEXTX_SHOWMOUSE VALUE FABGLEXT_ENDCODE
4085 // params:
4086 // VALUE (char) : '1' show mouse, '0' (and others) hide mouse
4087 case FABGLEXTX_SHOWMOUSE:
4088 {
4089 bool value = (extGetByteParam() == '1');
4090 if (m_bitmappedDisplayController) {
4091 auto dispctrl = static_cast<BitmappedDisplayController*>(m_displayController);
4092 auto mouse = PS2Controller::mouse();
4093 if (mouse && mouse->isMouseAvailable()) {
4094 if (value) {
4095 mouse->setupAbsolutePositioner(m_canvas->getWidth(), m_canvas->getHeight(), false, dispctrl);
4096 dispctrl->setMouseCursor(CursorName::CursorPointerSimpleReduced);
4097 } else {
4098 dispctrl->setMouseCursor(nullptr);
4099 mouse->terminateAbsolutePositioner();
4100 }
4101 }
4102 }
4103 extGetByteParam(); // FABGLEXT_ENDCODE
4104 break;
4105 }
4106
4107 // Get mouse position
4108 // Seq:
4109 // ESC FABGLEXT_STARTCODE FABGLEXTX_GETMOUSEPOS FABGLEXT_ENDCODE
4110 // params:
4111 // none
4112 // return:
4113 // byte: FABGLEXT_REPLYCODE (reply tag)
4114 // 3 hex digits : x position
4115 // char: ';'
4116 // 3 hex digits : y position
4117 // char: ';'
4118 // 1 hex digit : scroll wheel delta (0..15)
4119 // char: ';'
4120 // 1 hex digit : pressed button (bit 1 = left button, bit 2 = middle button, bit 3 = right button)
4121 case FABGLEXTX_GETMOUSEPOS:
4122 {
4123 extGetByteParam(); // FABGLEXT_ENDCODE
4124 if (m_bitmappedDisplayController) {
4125 auto mouse = PS2Controller::mouse();
4126 auto x = mouse->status().X;
4127 auto y = mouse->status().Y;
4128 send(FABGLEXT_REPLYCODE);
4129 // x
4130 send(toupper(digit2hex((x & 0xF00) >> 8)));
4131 send(toupper(digit2hex((x & 0x0F0) >> 4)));
4132 send(toupper(digit2hex((x & 0x00F) )));
4133 send(';');
4134 // y
4135 send(toupper(digit2hex((y & 0xF00) >> 8)));
4136 send(toupper(digit2hex((y & 0x0F0) >> 4)));
4137 send(toupper(digit2hex((y & 0x00F) )));
4138 send(';');
4139 // scroll wheel
4140 send(toupper(digit2hex(mouse->status().wheelDelta & 0xf)));
4141 send(';');
4142 // button
4143 auto b = mouse->status().buttons;
4144 send(toupper(digit2hex( b.left | (b.middle << 1) | (b.right << 2) )));
4145 }
4146 break;
4147 }
4148
4149 // Delay for milliseconds (return FABGLEXT_REPLYCODE when time is elapsed)
4150 // Seq:
4151 // ESC FABGLEXT_STARTCODE FABGLEXTX_DELAY VALUE FABGLEXT_ENDCODE
4152 // params:
4153 // VALUE (text) : number (milliseconds)
4154 // return:
4155 // byte: FABGLEXT_REPLYCODE (reply tag)
4156 case FABGLEXTX_DELAY:
4157 {
4158 auto value = extGetIntParam();
4159 extGetByteParam(); // FABGLEXT_ENDCODE
4160 vTaskDelay(value / portTICK_PERIOD_MS);
4161 send(FABGLEXT_REPLYCODE);
4162 break;
4163 }
4164
4165 // User sequence
4166 // Seq:
4167 // ESC FABGLEXT_STARTCODE FABGLEXT_USERSEQ ... FABGLEXT_ENDCODE
4168 // params:
4169 // ... any character different than FABGLEXT_ENDCODE, and up to FABGLEXT_MAXSUBCMDLEN characters
4170 case FABGLEXT_USERSEQ:
4171 {
4172 char usrseq[FABGLEXT_MAXSUBCMDLEN];
4173 int count = 0;
4174 while (count < FABGLEXT_MAXSUBCMDLEN) {
4175 char c = extGetByteParam();
4176 if (c == FABGLEXT_ENDCODE)
4177 break;
4178 usrseq[count++] = c;
4179 }
4180 usrseq[count] = 0;
4181 onUserSequence(usrseq);
4182 break;
4183 }
4184
4185 default:
4186 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
4187 logFmt("Unknown: ESC FABGLEXT_STARTCODE %02x\n", c);
4188 #endif
4189 break;
4190 }
4191}
4192
4193
4194void Terminal::freeSprites()
4195{
4196 for (int i = 0; i < m_spritesCount; ++i) {
4197 for (int j = 0; j < m_sprites[i].framesCount; ++j) {
4198 free(m_sprites[i].frames[j]->data); // free bitmap data
4199 delete m_sprites[i].frames[j]; // free bitmap struct
4200 }
4201 }
4202 delete [] m_sprites;
4203 m_sprites = nullptr;
4204 m_spritesCount = 0;
4205}
4206
4207
4208
4209// already received: ESC FABGLEXT_STARTCODE FABGLEXTX_GRAPHICSCMD
4210void Terminal::consumeFabGLGraphicsSeq()
4211{
4212 char cmd[FABGLEXT_MAXSUBCMDLEN];
4213 extGetCmdParam(cmd);
4214
4215 if (strcmp(cmd, FABGLEXT_GCLEAR) == 0) {
4216
4217 // Graphics clear (fill entire screen with canvas brush) and reset scrolling region
4218 // Seq:
4219 // FABGLEXT_GCLEAR FABGLEXT_ENDCODE
4220 extGetByteParam(); // FABGLEXT_ENDCODE
4221 if (m_canvas) {
4222 m_canvas->reset();
4223 m_canvas->clear();
4224 }
4225
4226 } else if (strcmp(cmd, FABGLEXT_GSETBRUSHCOLOR) == 0) {
4227
4228 // Graphics set brush color
4229 // Seq:
4230 // FABGLEXT_GSETBRUSHCOLOR RED ';' GREEN ';' BLUE FABGLEXT_ENDCODE
4231 // params:
4232 // RED (text) : '0'..'255'
4233 // GREEN (text) : '0'..'255'
4234 // BLUE (text) : '0'..'255'
4235 int r = extGetIntParam();
4236 extGetByteParam(); // ';'
4237 int g = extGetIntParam();
4238 extGetByteParam(); // ';'
4239 int b = extGetIntParam();
4240 extGetByteParam(); // FABGLEXT_ENDCODE
4241 if (m_canvas)
4242 m_canvas->setBrushColor(r, g, b);
4243
4244 } else if (strcmp(cmd, FABGLEXT_GSETPENCOLOR) == 0) {
4245
4246 // Graphics set pen color
4247 // Seq:
4248 // FABGLEXT_GSETPENCOLOR RED ';' GREEN ';' BLUE FABGLEXT_ENDCODE
4249 // params:
4250 // RED (text) : '0'..'255'
4251 // GREEN (text) : '0'..'255'
4252 // BLUE (text) : '0'..'255'
4253 int r = extGetIntParam();
4254 extGetByteParam(); // ';'
4255 int g = extGetIntParam();
4256 extGetByteParam(); // ';'
4257 int b = extGetIntParam();
4258 extGetByteParam(); // FABGLEXT_ENDCODE
4259 if (m_canvas)
4260 m_canvas->setPenColor(r, g, b);
4261
4262 } else if (strcmp(cmd, FABGLEXT_GSETPIXEL) == 0) {
4263
4264 // Graphics set pixel
4265 // Seq:
4266 // FABGLEXT_GSETPIXEL X ';' Y FABGLEXT_ENDCODE
4267 // params:
4268 // X (text) : number
4269 // Y (text) : number
4270 int x = extGetIntParam();
4271 extGetByteParam(); // ';'
4272 int y = extGetIntParam();
4273 extGetByteParam(); // FABGLEXT_ENDCODE
4274 if (m_canvas)
4275 m_canvas->setPixel(x, y);
4276
4277 } else if (strcmp(cmd, FABGLEXT_GSCROLL) == 0) {
4278
4279 // Graphics scroll
4280 // Seq:
4281 // FABGLEXT_GSCROLL OFFSETX ';' OFFSETY FABGLEXT_ENDCODE
4282 // params:
4283 // OFFSETX (text) : number
4284 // OFFSETY (text) : number
4285 int ox = extGetIntParam();
4286 extGetByteParam(); // ';'
4287 int oy = extGetIntParam();
4288 extGetByteParam(); // FABGLEXT_ENDCODE
4289 if (m_canvas)
4290 m_canvas->scroll(ox, oy);
4291
4292 } else if (strcmp(cmd, FABGLEXT_GPENWIDTH) == 0) {
4293
4294 // Graphics set pen width
4295 // Seq:
4296 // FABGLEXT_GPENWIDTH WIDTH FABGLEXT_ENDCODE
4297 // params:
4298 // WIDTH (text) : number
4299 int w = extGetIntParam();
4300 extGetByteParam(); // FABGLEXT_ENDCODE
4301 if (m_canvas)
4302 m_canvas->setPenWidth(w);
4303
4304 } else if (strcmp(cmd, FABGLEXT_GLINE) == 0) {
4305
4306 // Graphics draw a line
4307 // Seq:
4308 // FABGLEXT_GLINE X1 ';' Y1 ';' X2 ';' Y2 FABGLEXT_ENDCODE
4309 // params:
4310 // X1 (text) : number
4311 // Y1 (text) : number
4312 // X2 (text) : number
4313 // Y2 (text) : number
4314 int x1 = extGetIntParam();
4315 extGetByteParam(); // ';'
4316 int y1 = extGetIntParam();
4317 extGetByteParam(); // ';'
4318 int x2 = extGetIntParam();
4319 extGetByteParam(); // ';'
4320 int y2 = extGetIntParam();
4321 extGetByteParam(); // FABGLEXT_ENDCODE
4322 if (m_canvas)
4323 m_canvas->drawLine(x1, y1, x2, y2);
4324
4325 } else if (strcmp(cmd, FABGLEXT_GRECT) == 0) {
4326
4327 // Graphics draw a rectangle
4328 // Seq:
4329 // FABGLEXT_GRECT X1 ';' Y1 ';' X2 ';' Y2 FABGLEXT_ENDCODE
4330 // params:
4331 // X1 (text) : number
4332 // Y1 (text) : number
4333 // X2 (text) : number
4334 // Y2 (text) : number
4335 int x1 = extGetIntParam();
4336 extGetByteParam(); // ';'
4337 int y1 = extGetIntParam();
4338 extGetByteParam(); // ';'
4339 int x2 = extGetIntParam();
4340 extGetByteParam(); // ';'
4341 int y2 = extGetIntParam();
4342 extGetByteParam(); // FABGLEXT_ENDCODE
4343 if (m_canvas)
4344 m_canvas->drawRectangle(x1, y1, x2, y2);
4345
4346 } else if (strcmp(cmd, FABGLEXT_GFILLRECT) == 0) {
4347
4348 // Graphics fill a rectangle
4349 // Seq:
4350 // FABGLEXT_GFILLRECT X1 ';' Y1 ';' X2 ';' Y2 FABGLEXT_ENDCODE
4351 // params:
4352 // X1 (text) : number
4353 // Y1 (text) : number
4354 // X2 (text) : number
4355 // Y2 (text) : number
4356 int x1 = extGetIntParam();
4357 extGetByteParam(); // ';'
4358 int y1 = extGetIntParam();
4359 extGetByteParam(); // ';'
4360 int x2 = extGetIntParam();
4361 extGetByteParam(); // ';'
4362 int y2 = extGetIntParam();
4363 extGetByteParam(); // FABGLEXT_ENDCODE
4364 if (m_canvas)
4365 m_canvas->fillRectangle(x1, y1, x2, y2);
4366
4367 } else if (strcmp(cmd, FABGLEXT_GELLIPSE) == 0) {
4368
4369 // Graphics draw an ellipse
4370 // Seq:
4371 // FABGLEXT_GELLIPSE X ';' Y ';' WIDTH ';' HEIGHT FABGLEXT_ENDCODE
4372 // params:
4373 // X (text) : number
4374 // Y (text) : number
4375 // WIDTH (text) : number
4376 // HEIGHT (text) : number
4377 int x = extGetIntParam();
4378 extGetByteParam(); // ';'
4379 int y = extGetIntParam();
4380 extGetByteParam(); // ';'
4381 int w = extGetIntParam();
4382 extGetByteParam(); // ';'
4383 int h = extGetIntParam();
4384 extGetByteParam(); // FABGLEXT_ENDCODE
4385 if (m_canvas)
4386 m_canvas->drawEllipse(x, y, w, h);
4387
4388 } else if (strcmp(cmd, FABGLEXT_GFILLELLIPSE) == 0) {
4389
4390 // Graphics fill an ellipse
4391 // Seq:
4392 // FABGLEXT_GFILLELLIPSE X ';' Y ';' WIDTH ';' HEIGHT FABGLEXT_ENDCODE
4393 // params:
4394 // X (text) : number
4395 // Y (text) : number
4396 // WIDTH (text) : number
4397 // HEIGHT (text) : number
4398 int x = extGetIntParam();
4399 extGetByteParam(); // ';'
4400 int y = extGetIntParam();
4401 extGetByteParam(); // ';'
4402 int w = extGetIntParam();
4403 extGetByteParam(); // ';'
4404 int h = extGetIntParam();
4405 extGetByteParam(); // FABGLEXT_ENDCODE
4406 if (m_canvas)
4407 m_canvas->fillEllipse(x, y, w, h);
4408
4409 } else if (strcmp(cmd, FABGLEXT_GPATH) == 0) {
4410
4411 // Graphics draw path
4412 // Seq:
4413 // FABGLEXT_GPATH X1 ';' Y1 ';' X2 ';' Y2 [';' Xn ';' Yn...] FABGLEXT_ENDCODE
4414 // params:
4415 // X (text) : number
4416 // Y (text) : number
4417 // notes:
4418 // max 32 points
4419 constexpr int MAXPOINTS = 32;
4420 Point pts[MAXPOINTS];
4421 int count = 0;
4422 while (count < MAXPOINTS) { // @TODO: what happens if count ends before FABGLEXT_ENDCODE?
4423 pts[count].X = extGetIntParam();
4424 extGetByteParam();
4425 pts[count].Y = extGetIntParam();
4426 ++count;
4427 if (extGetByteParam() == FABGLEXT_ENDCODE)
4428 break;
4429 }
4430 if (m_canvas)
4431 m_canvas->drawPath(pts, count);
4432
4433 } else if (strcmp(cmd, FABGLEXT_GFILLPATH) == 0) {
4434
4435 // Graphics fill path
4436 // Seq:
4437 // FABGLEXT_GFILLPATH X1 ';' Y1 ; X2 ';' Y2 [';' Xn ';' Yn...] FABGLEXT_ENDCODE
4438 // params:
4439 // X (text) : number
4440 // Y (text) : number
4441 // notes:
4442 // max 32 points
4443 constexpr int MAXPOINTS = 32;
4444 Point pts[MAXPOINTS];
4445 int count = 0;
4446 while (count < MAXPOINTS) { // @TODO: what happens if count ends before FABGLEXT_ENDCODE?
4447 pts[count].X = extGetIntParam();
4448 extGetByteParam();
4449 pts[count].Y = extGetIntParam();
4450 ++count;
4451 if (extGetByteParam() == FABGLEXT_ENDCODE)
4452 break;
4453 }
4454 if (m_canvas)
4455 m_canvas->fillPath(pts, count);
4456
4457 } else if (strcmp(cmd, FABGLEXT_GSPRITECOUNT) == 0) {
4458
4459 // Determines number of sprites to define
4460 // Seq:
4461 // FABGLEXT_GSPRITECOUNT COUNT FABGLEXT_ENDCODE
4462 // params:
4463 // COUNT (text) : number of sprites that will be defined by FABGLEXT_GSPRITEDEF (0 = free memory)
4464 int count = extGetIntParam();
4465 extGetByteParam();
4466 if (m_bitmappedDisplayController) {
4467 static_cast<BitmappedDisplayController*>(m_displayController)->setSprites<Sprite>(nullptr, 0);
4468 freeSprites();
4469 if (count > 0) {
4470 m_spritesCount = count;
4471 m_sprites = new Sprite[count];
4472 }
4473 }
4474
4475 } else if (strcmp(cmd, FABGLEXT_GSPRITEDEF) == 0) {
4476
4477 // Add a bitmap to a sprite
4478 // Seq:
4479 // FABGLEXT_GSPRITEDEF SPRITEINDEX ';' WIDTH ';' HEIGHT ';' FORMAT ';' [R ';' G ';' B ';'] DATA... FABGLEXT_ENDCODE
4480 // params:
4481 // SPRITEINDEX (text) : sprite index (0...)
4482 // WIDTH (text) : sprite width
4483 // HEIGHT (text) : sprite height
4484 // FORMAT (char) : 'M' = PixelFormat::Mask, '2' = PixelFormat::RGBA2222, '8' = PixelFormat::RGBA8888
4485 // R (text) : red (0..255) when FORMAT is "MASK"
4486 // G (text) : green (0..255) when FORMAT is "MASK"
4487 // B (text) : blue (0..255) when FORMAT is "MASK"
4488 // DATA (text) : 2 digits hex number
4489 int sprite = extGetIntParam();
4490 extGetByteParam();
4491 int width = extGetIntParam();
4492 extGetByteParam();
4493 int height = extGetIntParam();
4494 extGetByteParam();
4495 char cformat = extGetByteParam();
4496 extGetByteParam();
4497 int r = 0, g = 0, b = 0;
4498 int bytes = 0;
4499 PixelFormat format = PixelFormat::Undefined;
4500 switch (cformat) {
4501 case 'M':
4502 r = extGetIntParam();
4503 extGetByteParam();
4504 g = extGetIntParam();
4505 extGetByteParam();
4506 b = extGetIntParam();
4507 extGetByteParam();
4508 bytes = (width + 7) / 8 * height;
4509 format = PixelFormat::Mask;
4510 break;
4511 case '2':
4512 bytes = width * height;
4513 format = PixelFormat::RGBA2222;
4514 break;
4515 case '8':
4516 bytes = width * height * 4;
4517 format = PixelFormat::RGBA8888;
4518 break;
4519 }
4520 auto data = (uint8_t*) malloc(bytes);
4521 for (int i = 0; i < bytes + 1; ++i) { // +1 to include ending code
4522 auto c = extGetByteParam();
4523 if (c == FABGLEXT_ENDCODE)
4524 break;
4525 data[i] = hex2digit(tolower(c)) << 4;
4526 c = extGetByteParam();
4527 if (c == FABGLEXT_ENDCODE)
4528 break;
4529 data[i] |= hex2digit(tolower(c));
4530 }
4531 if (m_bitmappedDisplayController && sprite < m_spritesCount) {
4532 auto bitmap = new Bitmap(width, height, data, format, RGB888(r, g, b), false);
4533 m_sprites[sprite].addBitmap(bitmap);
4534 static_cast<BitmappedDisplayController*>(m_displayController)->setSprites(m_sprites, m_spritesCount);
4535 } else {
4536 // error
4537 free(data);
4538 }
4539
4540 } else if (strcmp(cmd, FABGLEXT_GSPRITESET) == 0) {
4541
4542 // Set sprite visibility, position and frame
4543 // Seq:
4544 // FABGLEXT_GSPRITESET SPRITEINDEX ';' VISIBLE ';' FRAME ';' POSX ';' POSY FABGLEXT_ENDCODE
4545 // params:
4546 // SPRITEINDEX (text) : sprite index (0...)
4547 // VISIBLE (char) : 'H' = hidden, 'V' = visible
4548 // FRAME (text) : frame index (0...)
4549 // POSX (text) : x position
4550 // POSY (text) : y position
4551 int sprite = extGetIntParam();
4552 extGetByteParam();
4553 char visible = extGetByteParam();
4554 extGetByteParam();
4555 int frame = extGetIntParam();
4556 extGetByteParam();
4557 int posx = extGetIntParam();
4558 extGetByteParam();
4559 int posy = extGetIntParam();
4560 extGetByteParam();
4561 if (m_bitmappedDisplayController && sprite < m_spritesCount) {
4562 m_sprites[sprite].visible = (visible == 'V');
4563 m_sprites[sprite].setFrame(frame);
4564 m_sprites[sprite].x = posx;
4565 m_sprites[sprite].y = posy;
4566 static_cast<BitmappedDisplayController*>(m_displayController)->refreshSprites();
4567 }
4568
4569 } else {
4570 #if FABGLIB_TERMINAL_DEBUG_REPORT_UNSUPPORT
4571 logFmt("Unknown: ESC FABGLEXT_STARTCODE FABGLEXTX_GRAPHICSCMD %s\n", cmd);
4572 #endif
4573 }
4574}
4575
4576
4577void Terminal::keyboardReaderTask(void * pvParameters)
4578{
4579 Terminal * term = (Terminal*) pvParameters;
4580
4581 while (true) {
4582
4583 if (!term->isActive())
4584 vTaskSuspend(NULL);
4585
4586 VirtualKeyItem item;
4587 if (term->m_keyboard->getNextVirtualKey(&item)) {
4588
4589 if (term->isActive()) {
4590
4591 term->onVirtualKey(&item.vk, item.down);
4592 term->onVirtualKeyItem(&item);
4593
4594 if (term->flowControl()) {
4595
4596 // note: when flow is locked, no key event is reinjected. This to allow onVirtualKey to always work on last pressed char.
4597
4598 if (item.down) {
4599
4600 if (!term->m_emuState.keyAutorepeat && term->m_lastPressedKey == item.vk)
4601 continue; // don't repeat
4602 term->m_lastPressedKey = item.vk;
4603
4604 xSemaphoreTake(term->m_mutex, portMAX_DELAY);
4605
4606 if (term->m_termInfo == nullptr) {
4607 if (term->m_emuState.ANSIMode)
4608 term->ANSIDecodeVirtualKey(item);
4609 else
4610 term->VT52DecodeVirtualKey(item);
4611 } else
4612 term->TermDecodeVirtualKey(item);
4613
4614 xSemaphoreGive(term->m_mutex);
4615
4616 } else {
4617 // !keyDown
4618 term->m_lastPressedKey = VK_NONE;
4619 }
4620
4621 }
4622
4623 } else {
4624 // not active, reinject back
4625 term->m_keyboard->injectVirtualKey(item, true);
4626 }
4627
4628 }
4629
4630 }
4631}
4632
4633
4634void Terminal::sendCursorKeyCode(uint8_t c)
4635{
4636 if (m_emuState.cursorKeysMode)
4637 sendSS3();
4638 else
4639 sendCSI();
4640 send(c);
4641}
4642
4643
4644void Terminal::sendKeypadCursorKeyCode(uint8_t applicationCode, const char * numericCode)
4645{
4646 if (m_emuState.keypadMode == KeypadMode::Application) {
4647 sendSS3();
4648 send(applicationCode);
4649 } else {
4650 sendCSI();
4651 send(numericCode);
4652 }
4653}
4654
4655
4656void Terminal::ANSIDecodeVirtualKey(VirtualKeyItem const & item)
4657{
4658 switch (item.vk) {
4659
4660 // cursor keys
4661
4662 case VK_UP:
4663 sendCursorKeyCode('A');
4664 break;
4665
4666 case VK_DOWN:
4667 sendCursorKeyCode('B');
4668 break;
4669
4670 case VK_RIGHT:
4671 sendCursorKeyCode('C');
4672 break;
4673
4674 case VK_LEFT:
4675 sendCursorKeyCode('D');
4676 break;
4677
4678 // cursor keys - on numeric keypad
4679
4680 case VK_KP_UP:
4681 sendKeypadCursorKeyCode('x', "A");
4682 break;
4683
4684 case VK_KP_DOWN:
4685 sendKeypadCursorKeyCode('r', "B");
4686 break;
4687
4688 case VK_KP_RIGHT:
4689 sendKeypadCursorKeyCode('v', "C");
4690 break;
4691
4692 case VK_KP_LEFT:
4693 sendKeypadCursorKeyCode('t', "D");
4694 break;
4695
4696 // PageUp, PageDown, Insert, Home, Delete, End
4697
4698 case VK_PAGEUP:
4699 sendCSI();
4700 send("5~");
4701 break;
4702
4703 case VK_PAGEDOWN:
4704 sendCSI();
4705 send("6~");
4706 break;
4707
4708 case VK_INSERT:
4709 sendCSI();
4710 send("2~");
4711 break;
4712
4713 case VK_HOME:
4714 sendCSI();
4715 send("1~");
4716 break;
4717
4718 case VK_DELETE:
4719 sendCSI();
4720 send("3~");
4721 break;
4722
4723 case VK_END:
4724 sendCSI();
4725 send("4~");
4726 break;
4727
4728 // PageUp, PageDown, Insert, Home, Delete, End - on numeric keypad
4729
4730 case VK_KP_PAGEUP:
4731 sendKeypadCursorKeyCode('y', "5~");
4732 break;
4733
4734 case VK_KP_PAGEDOWN:
4735 sendKeypadCursorKeyCode('s', "6~");
4736 break;
4737
4738 case VK_KP_INSERT:
4739 sendKeypadCursorKeyCode('p', "2~");
4740 break;
4741
4742 case VK_KP_HOME:
4743 sendKeypadCursorKeyCode('w', "1~");
4744 break;
4745
4746 case VK_KP_DELETE:
4747 sendKeypadCursorKeyCode('n', "3~");
4748 break;
4749
4750 case VK_KP_END:
4751 sendKeypadCursorKeyCode('q', "4~");
4752 break;
4753
4754 // Backspace
4755
4756 case VK_BACKSPACE:
4757 send(m_emuState.backarrowKeyMode ? ASCII_BS : ASCII_DEL);
4758 break;
4759
4760 // Function keys
4761
4762 case VK_F1:
4763 sendSS3();
4764 send('P');
4765 break;
4766
4767 case VK_F2:
4768 sendSS3();
4769 send('Q');
4770 break;
4771
4772 case VK_F3:
4773 sendSS3();
4774 send('R');
4775 break;
4776
4777 case VK_F4:
4778 sendSS3();
4779 send('S');
4780 break;
4781
4782 case VK_F5:
4783 sendCSI();
4784 send("15~");
4785 break;
4786
4787 case VK_F6:
4788 sendCSI();
4789 send("17~");
4790 break;
4791
4792 case VK_F7:
4793 sendCSI();
4794 send("18~");
4795 break;
4796
4797 case VK_F8:
4798 sendCSI();
4799 send("19~");
4800 break;
4801
4802 case VK_F9:
4803 sendCSI();
4804 send("20~");
4805 break;
4806
4807 case VK_F10:
4808 sendCSI();
4809 send("21~");
4810 break;
4811
4812 case VK_F11:
4813 sendCSI();
4814 send("23~");
4815 break;
4816
4817 case VK_F12:
4818 sendCSI();
4819 send("24~");
4820 break;
4821
4822 // Printable keys
4823
4824 default:
4825 {
4826 switch (item.ASCII) {
4827
4828 // RETURN (CR)?
4829 case ASCII_CR:
4830 if (m_emuState.newLineMode)
4831 send("\r\n"); // send CR LF (0x0D 0x0A)
4832 else
4833 send('\r'); // send only CR (0x0D)
4834 break;
4835
4836 default:
4837 if (item.ASCII > 0)
4838 send(item.ASCII);
4839 break;
4840 }
4841 break;
4842 }
4843
4844 }
4845}
4846
4847
4848void Terminal::VT52DecodeVirtualKey(VirtualKeyItem const & item)
4849{
4850 switch (item.vk) {
4851
4852 // cursor keys
4853
4854 case VK_UP:
4855 send("\eA");
4856 break;
4857
4858 case VK_DOWN:
4859 send("\eB");
4860 break;
4861
4862 case VK_RIGHT:
4863 send("\eC");
4864 break;
4865
4866 case VK_LEFT:
4867 send("\eD");
4868 break;
4869
4870 // numeric keypad
4871
4872 case VK_KP_0:
4873 case VK_KP_INSERT:
4874 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?p" : "0");
4875 break;
4876
4877 case VK_KP_1:
4878 case VK_KP_END:
4879 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?q" : "1");
4880 break;
4881
4882 case VK_KP_2:
4883 case VK_KP_DOWN:
4884 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?r" : "2");
4885 break;
4886
4887 case VK_KP_3:
4888 case VK_KP_PAGEDOWN:
4889 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?s" : "3");
4890 break;
4891
4892 case VK_KP_4:
4893 case VK_KP_LEFT:
4894 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?t" : "4");
4895 break;
4896
4897 case VK_KP_5:
4898 case VK_KP_CENTER:
4899 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?u" : "5");
4900 break;
4901
4902 case VK_KP_6:
4903 case VK_KP_RIGHT:
4904 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?v" : "6");
4905 break;
4906
4907 case VK_KP_7:
4908 case VK_KP_HOME:
4909 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?w" : "7");
4910 break;
4911
4912 case VK_KP_8:
4913 case VK_KP_UP:
4914 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?x" : "8");
4915 break;
4916
4917 case VK_KP_9:
4918 case VK_KP_PAGEUP:
4919 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?y" : "9");
4920 break;
4921
4922 case VK_KP_PERIOD:
4923 case VK_KP_DELETE:
4924 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?n" : ".");
4925 break;
4926
4927 case VK_KP_ENTER:
4928 send(m_emuState.keypadMode == KeypadMode::Application ? "\e?M" : "\r");
4929 break;
4930
4931
4932 // Printable keys
4933
4934 default:
4935 {
4936 if (item.ASCII > 0)
4937 send(item.ASCII);
4938 break;
4939 }
4940
4941 }
4942}
4943
4944
4945void Terminal::TermDecodeVirtualKey(VirtualKeyItem const & item)
4946{
4947 for (auto i = m_termInfo->kbdCtrlSet; i->vk != VK_NONE; ++i) {
4948 if (i->vk == item.vk) {
4949 send(i->ANSICtrlCode);
4950 return;
4951 }
4952 }
4953
4954 // default behavior
4955 if (item.ASCII > 0)
4956 send(item.ASCII);
4957}
4958
4959
4960
4963// TerminalController
4964
4965
4966TerminalController::TerminalController(Terminal * terminal)
4967 : m_terminal(terminal)
4968{
4969}
4970
4971
4972TerminalController::~TerminalController()
4973{
4974}
4975
4976
4978{
4979 m_terminal = terminal;
4980}
4981
4982
4983void TerminalController::write(uint8_t c)
4984{
4985 if (m_terminal)
4986 m_terminal->write(c);
4987 else
4988 onWrite(c);
4989}
4990
4991
4992void TerminalController::write(char const * str)
4993{
4994 while (*str)
4995 write(*str++);
4996}
4997
4998
4999int TerminalController::read()
5000{
5001 if (m_terminal)
5002 return m_terminal->read(-1);
5003 else {
5004 int c;
5005 onRead(&c);
5006 return c;
5007 }
5008}
5009
5010
5011void TerminalController::waitFor(int value)
5012{
5013 while (true)
5014 if (read() == value)
5015 return;
5016}
5017
5018
5020{
5021 write(FABGLEXT_CMD);
5022 write(FABGLEXTX_CLEAR);
5023 write(FABGLEXT_ENDCODE);
5024}
5025
5026
5028{
5029 write(FABGLEXT_CMD);
5030 write(FABGLEXTX_ENABLECURSOR);
5031 write(value ? '1' : '0');
5032 write(FABGLEXT_ENDCODE);
5033}
5034
5035
5037{
5038 write(FABGLEXT_CMD);
5039 write(FABGLEXTB_SETCURSORPOS);
5040 write(col);
5041 write(row);
5042 write(FABGLEXT_ENDCODE);
5043}
5044
5045
5047{
5048 write(FABGLEXT_CMD);
5049 write(FABGLEXTB_CURSORLEFT);
5050 write(count & 0xff);
5051 write(count >> 8);
5052 write(FABGLEXT_ENDCODE);
5053}
5054
5055
5057{
5058 write(FABGLEXT_CMD);
5059 write(FABGLEXTB_CURSORRIGHT);
5060 write(count & 0xff);
5061 write(count >> 8);
5062 write(FABGLEXT_ENDCODE);
5063}
5064
5065
5066void TerminalController::getCursorPos(int * col, int * row)
5067{
5068 write(FABGLEXT_CMD);
5069 write(FABGLEXTB_GETCURSORPOS);
5070 write(FABGLEXT_ENDCODE);
5071 waitFor(FABGLEXT_REPLYCODE);
5072 *col = read();
5073 *row = read();
5074}
5075
5076
5078{
5079 write(FABGLEXT_CMD);
5080 write(FABGLEXTB_GETCURSORCOL);
5081 write(FABGLEXT_ENDCODE);
5082 waitFor(FABGLEXT_REPLYCODE);
5083 return read();
5084}
5085
5086
5088{
5089 write(FABGLEXT_CMD);
5090 write(FABGLEXTB_GETCURSORROW);
5091 write(FABGLEXT_ENDCODE);
5092 waitFor(FABGLEXT_REPLYCODE);
5093 return read();
5094}
5095
5096
5098{
5099 write(FABGLEXT_CMD);
5100 write(FABGLEXTB_INSERTSPACE);
5101 write(charsToMove & 0xff);
5102 write(charsToMove >> 8);
5103 write(FABGLEXT_ENDCODE);
5104 waitFor(FABGLEXT_REPLYCODE);
5105 return read();
5106}
5107
5108
5110{
5111 write(FABGLEXT_CMD);
5112 write(FABGLEXTB_DELETECHAR);
5113 write(charsToMove & 0xff);
5114 write(charsToMove >> 8);
5115 write(FABGLEXT_ENDCODE);
5116}
5117
5118
5120{
5121 write(FABGLEXT_CMD);
5122 write(FABGLEXTB_SETCHAR);
5123 write(c);
5124 write(FABGLEXT_ENDCODE);
5125 waitFor(FABGLEXT_REPLYCODE);
5126 return read();
5127}
5128
5129
5131{
5132 write(FABGLEXT_CMD);
5133 write(FABGLEXTB_ISVKDOWN);
5134 write((uint8_t)vk);
5135 write(FABGLEXT_ENDCODE);
5136 waitFor(FABGLEXT_REPLYCODE);
5137 return read() == '1';
5138}
5139
5140
5142{
5143 write(FABGLEXT_CMD);
5144 write(FABGLEXTB_DISABLEFABSEQ);
5145 write(FABGLEXT_ENDCODE);
5146}
5147
5148
5150{
5151 write(FABGLEXT_CMD);
5152 write(FABGLEXTB_SETTERMTYPE);
5153 write((int)value);
5154 write(FABGLEXT_ENDCODE);
5155}
5156
5157
5159{
5160 write(FABGLEXT_CMD);
5161 write(FABGLEXTB_SETFGCOLOR);
5162 write((int)value);
5163 write(FABGLEXT_ENDCODE);
5164}
5165
5166
5168{
5169 write(FABGLEXT_CMD);
5170 write(FABGLEXTB_SETBGCOLOR);
5171 write((int)value);
5172 write(FABGLEXT_ENDCODE);
5173}
5174
5175
5177{
5178 write(FABGLEXT_CMD);
5179 write(FABGLEXTB_SETCHARSTYLE);
5180 write((int)style);
5181 write(enabled ? 1 : 0);
5182 write(FABGLEXT_ENDCODE);
5183}
5184
5185
5186
5189// LineEditor
5190
5191
5193 : m_terminal(terminal),
5194 m_termctrl(terminal),
5195 m_text(nullptr),
5196 m_textLength(0),
5197 m_allocated(0),
5198 m_state(-1),
5199 m_insertMode(true),
5200 m_typeText(nullptr),
5201 m_typingIndex(0)
5202{
5203}
5204
5205
5206LineEditor::~LineEditor()
5207{
5208 if (m_typeText)
5209 free(m_typeText);
5210 free(m_text);
5211}
5212
5213
5214void LineEditor::setLength(int newLength)
5215{
5216 if (m_allocated < newLength || m_allocated == 0) {
5217 int allocated = imax(m_allocated * 2, newLength);
5218 m_text = (char*) realloc(m_text, allocated + 1);
5219 memset(m_text + m_allocated, 0, allocated - m_allocated + 1);
5220 m_allocated = allocated;
5221 }
5222 m_textLength = newLength;
5223}
5224
5225
5226void LineEditor::typeText(char const * text)
5227{
5228 if (m_typeText)
5229 free(m_typeText);
5230 m_typeText = strdup(text);
5231 m_typingIndex = 0;
5232}
5233
5234
5235void LineEditor::setText(char const * text, bool moveCursor)
5236{
5237 setText(text, strlen(text), moveCursor);
5238}
5239
5240
5241void LineEditor::setText(char const * text, int length, bool moveCursor)
5242{
5243 if (m_state > -1) {
5244 // already editing, replace previous text
5245 m_termctrl.setCursorPos(m_homeCol, m_homeRow);
5246 for (int i = 0; i < m_textLength; ++i)
5247 m_termctrl.setChar(' ');
5248 m_termctrl.setCursorPos(m_homeCol, m_homeRow);
5249 for (int i = 0; i < length; ++i)
5250 m_homeRow -= m_termctrl.setChar(text[i]);
5251 }
5252 setLength(length);
5253 memcpy(m_text, text, length);
5254 m_text[length] = 0;
5255 m_inputPos = moveCursor ? length : 0;
5256}
5257
5258
5259void LineEditor::write(uint8_t c)
5260{
5261 if (m_terminal)
5262 m_terminal->write(c);
5263 else
5264 onWrite(c);
5265}
5266
5267
5268int LineEditor::read()
5269{
5270 if (m_terminal)
5271 return m_terminal->read(-1);
5272 else {
5273 int c;
5274 onRead(&c);
5275 return c;
5276 }
5277}
5278
5279
5280void LineEditor::beginInput()
5281{
5282 if (m_terminal == nullptr) {
5283 // in case a terminal has been not specified, we need to use onRead and onWrite delegates
5284 m_termctrl.onRead = [&](int * c) { onRead(c); };
5285 m_termctrl.onWrite = [&](int c) { onWrite(c); };
5286 }
5287 m_homeCol = m_termctrl.getCursorCol();
5288 m_homeRow = m_termctrl.getCursorRow();
5289 if (m_text) {
5290 // m_inputPos already set by setText()
5291 for (int i = 0, len = strlen(m_text); i < len; ++i)
5292 m_homeRow -= m_termctrl.setChar(m_text[i]);
5293 if (m_inputPos == 0)
5294 m_termctrl.setCursorPos(m_homeCol, m_homeRow);
5295 } else {
5296 m_inputPos = 0;
5297 }
5298 m_state = 0;
5299}
5300
5301
5302void LineEditor::endInput()
5303{
5304 m_state = -1;
5305 if (m_text == nullptr) {
5306 m_text = (char*) malloc(1);
5307 m_text[0] = 0;
5308 }
5309}
5310
5311
5312void LineEditor::performCursorUp()
5313{
5315}
5316
5317
5318void LineEditor::performCursorDown()
5319{
5321}
5322
5323
5324void LineEditor::performCursorLeft()
5325{
5326 if (m_inputPos > 0) {
5327 int count = 1;
5328 if (m_termctrl.isVKDown(VK_LCTRL)) {
5329 // CTRL + Cursor Left => start of previous word
5330 while (m_inputPos - count > 0 && (m_text[m_inputPos - count] == ASCII_SPC || m_text[m_inputPos - count - 1] != ASCII_SPC))
5331 ++count;
5332 }
5333 m_termctrl.cursorLeft(count);
5334 m_inputPos -= count;
5335 }
5336}
5337
5338
5339void LineEditor::performCursorRight()
5340{
5341 if (m_inputPos < m_textLength) {
5342 int count = 1;
5343 if (m_termctrl.isVKDown(VK_LCTRL)) {
5344 // CTRL + Cursor Right => start of next word
5345 while (m_text[m_inputPos + count] && (m_text[m_inputPos + count] == ASCII_SPC || m_text[m_inputPos + count - 1] != ASCII_SPC))
5346 ++count;
5347 }
5348 m_termctrl.cursorRight(count);
5349 m_inputPos += count;
5350 }
5351}
5352
5353
5354void LineEditor::performCursorHome()
5355{
5356 m_termctrl.setCursorPos(m_homeCol, m_homeRow);
5357 m_inputPos = 0;
5358}
5359
5360
5361void LineEditor::performCursorEnd()
5362{
5363 m_termctrl.cursorRight(m_textLength - m_inputPos);
5364 m_inputPos = m_textLength;
5365}
5366
5367
5368void LineEditor::performDeleteRight()
5369{
5370 if (m_inputPos < m_textLength) {
5371 memmove(m_text + m_inputPos, m_text + m_inputPos + 1, m_textLength - m_inputPos);
5372 m_termctrl.multilineDeleteChar(m_textLength - m_inputPos - 1);
5373 --m_textLength;
5374 }
5375}
5376
5377
5378void LineEditor::performDeleteLeft()
5379{
5380 if (m_inputPos > 0) {
5381 m_termctrl.cursorLeft(1);
5382 m_termctrl.multilineDeleteChar(m_textLength - m_inputPos);
5383 memmove(m_text + m_inputPos - 1, m_text + m_inputPos, m_textLength - m_inputPos + 1);
5384 --m_inputPos;
5385 --m_textLength;
5386 }
5387}
5388
5389
5390char const * LineEditor::edit(int maxLength)
5391{
5392
5393 // init?
5394 if (m_state == -1)
5395 beginInput();
5396
5397 while (true) {
5398
5399 int c;
5400
5401 if (m_typeText) {
5402 c = m_typeText[m_typingIndex++];
5403 if (c == 0) {
5404 free(m_typeText);
5405 m_typeText = nullptr;
5406 continue;
5407 }
5408 } else {
5409 c = read();
5410 }
5411
5412 onChar(&c);
5413
5414 // timeout?
5415 if (c < 0)
5416 return nullptr;
5417
5418 if (m_state == 1) {
5419
5420 // ESC mode
5421
5422 switch (c) {
5423
5424 // "ESC [" => switch to CSI mode
5425 case '[':
5426 m_state = 31;
5427 break;
5428
5429 default:
5430 m_state = 0;
5431 break;
5432
5433 }
5434
5435 } else if (m_state == 2) {
5436
5437 // CTRL-Q mode
5438
5439 switch (c) {
5440
5441 // CTRL-Q S => WordStar Home
5442 case 'S':
5443 performCursorHome();
5444 break;
5445
5446 // CTRL-Q D => WordStar End
5447 case 'D':
5448 performCursorEnd();
5449 break;
5450
5451 }
5452 m_state = 0;
5453
5454 } else if (m_state >= 31) {
5455
5456 // CSI mode
5457
5458 switch (c) {
5459
5460 // "ESC [ A" : cursor Up
5461 case 'A':
5462 performCursorUp();
5463 m_state = 0;
5464 break;
5465
5466 // "ESC [ B" : cursor Down
5467 case 'B':
5468 performCursorUp();
5469 m_state = 0;
5470 break;
5471
5472 // "ESC [ D" : cursor Left
5473 case 'D':
5474 performCursorLeft();
5475 m_state = 0;
5476 break;
5477
5478 // "ESC [ C" : cursor right
5479 case 'C':
5480 performCursorRight();
5481 m_state = 0;
5482 break;
5483
5484 // '1'...'6' : special chars (PageUp, Insert, Home...)
5485 case '1' ... '6':
5486 // requires ending '~'
5487 m_state = c;
5488 break;
5489
5490 // '~'
5491 case '~':
5492 switch (m_state) {
5493
5494 // Home
5495 case '1':
5496 performCursorHome();
5497 break;
5498
5499 // End
5500 case '4':
5501 performCursorEnd();
5502 break;
5503
5504 // Delete
5505 case '3':
5506 performDeleteRight();
5507 break;
5508
5509 // Insert
5510 case '2':
5511 m_insertMode = !m_insertMode;
5512 break;
5513
5514 }
5515 m_state = 0;
5516 break;
5517
5518 default:
5519 m_state = 0;
5520 break;
5521
5522 }
5523
5524 } else {
5525
5526 // normal mode
5527
5528 switch (c) {
5529
5530 // ESC, switch to ESC mode
5531 case ASCII_ESC:
5532 m_state = 1;
5533 break;
5534
5535 // CTRL-Q, switch to CTRL-Q mode
5536 case ASCII_CTRLQ:
5537 m_state = 2;
5538 break;
5539
5540 // DEL, delete character at left
5541 case ASCII_DEL:
5542 case ASCII_BS: // alias CTRL-H / backspace
5543 performDeleteLeft();
5544 break;
5545
5546 // CTRL-G, delete character at right
5547 case ASCII_CTRLG:
5548 performDeleteRight();
5549 break;
5550
5551 // CR, newline and return the inserted text
5552 case ASCII_CR:
5553 {
5554 int op = 0;
5555 onCarriageReturn(&op);
5556 if (op < 2) {
5557 m_termctrl.cursorRight(m_textLength - m_inputPos);
5558 if (op == 0) {
5559 write('\r');
5560 write('\n');
5561 }
5562 endInput();
5563 return m_text;
5564 } else
5565 break;
5566 }
5567
5568 // CTRL-E, WordStar UP
5569 case ASCII_CTRLE:
5570 performCursorUp();
5571 break;
5572
5573 // CTRL-X, WordStar DOWN
5574 case ASCII_CTRLX:
5575 performCursorDown();
5576 break;
5577
5578 // CTRL-S, WordStar LEFT
5579 case ASCII_CTRLS:
5580 performCursorLeft();
5581 break;
5582
5583 // CTRL-D, WordStar RIGHT
5584 case ASCII_CTRLD:
5585 performCursorRight();
5586 break;
5587
5588 // insert printable chars
5589 case 32 ... 126:
5590 case 128 ... 255:
5591 // TODO: should we stop input when text length reach the full screen (minus home pos)?
5592 if (maxLength == 0 || m_inputPos < maxLength) {
5593 // update internal buffer
5594 if (m_insertMode || m_inputPos == m_textLength) {
5595 setLength(m_textLength + 1);
5596 memmove(m_text + m_inputPos + 1, m_text + m_inputPos, m_textLength - m_inputPos);
5597 }
5598 m_text[m_inputPos++] = c;
5599 // update terminal
5600 if (m_insertMode && m_inputPos < m_textLength) {
5601 if (m_termctrl.multilineInsertChar(m_textLength - m_inputPos))
5602 --m_homeRow; // scrolled
5603 }
5604 if (m_termctrl.setChar(c))
5605 --m_homeRow; // scrolled
5606 }
5607 break;
5608
5609 }
5610 }
5611
5612 }
5613}
5614
5615
5616
5617} // end of namespace
Represents the base abstract class for all display controllers.
Represents the base abstract class for bitmapped display controllers.
A class with a set of drawing methods.
Definition: canvas.h:70
The PS2 Keyboard controller class.
Definition: keyboard.h:77
char const * edit(int maxLength=0)
Reads user input and return the inserted line.
Definition: terminal.cpp:5390
Delegate< LineEditorSpecialChar > onSpecialChar
A delegate called whenever a special character has been pressed.
Definition: terminal.h:2165
void setText(char const *text, bool moveCursor=true)
Sets initial text.
Definition: terminal.cpp:5235
LineEditor(Terminal *terminal)
Object constructor.
Definition: terminal.cpp:5192
Delegate< int * > onChar
A delegate called whenever a character has been received.
Definition: terminal.h:2147
Delegate< int * > onRead
Read character delegate.
Definition: terminal.h:2131
void typeText(char const *text)
Simulates user typing.
Definition: terminal.cpp:5226
Delegate< int > onWrite
Write character delegate.
Definition: terminal.h:2140
Delegate< int * > onCarriageReturn
A delegate called whenever carriage return has been pressed.
Definition: terminal.h:2158
Sawtooth waveform generator.
Definition: soundgen.h:244
Sine waveform generator.
Definition: soundgen.h:185
SoundGenerator handles audio output.
Definition: soundgen.h:350
Square waveform generator.
Definition: soundgen.h:202
static int keyboardReaderTaskStackSize
Stack size of the task that reads keys from keyboard and send ANSI/VT codes to output stream in Termi...
Definition: terminal.h:1528
size_t write(const uint8_t *buffer, size_t size)
Sends specified number of codes to the display.
Definition: terminal.cpp:1958
static int inputQueueSize
Number of characters the terminal can "write" without pause (increase if you have loss of characters ...
Definition: terminal.h:1510
int read()
Reads codes from keyboard.
Definition: terminal.cpp:1703
static int inputConsumerTaskStackSize
Stack size of the task that processes Terminal input stream.
Definition: terminal.h:1519
void flowControl(bool enableRX)
Allows/disallows host to send data.
Definition: terminal.cpp:511
void cursorRight(int count)
Moves cursor to the right.
Definition: terminal.cpp:5056
void disableFabGLSequences()
Disables FabGL specific sequences.
Definition: terminal.cpp:5141
void cursorLeft(int count)
Moves cursor to the left.
Definition: terminal.cpp:5046
void multilineDeleteChar(int charsToMove)
Deletes a character moving specified amount of characters to the left.
Definition: terminal.cpp:5109
bool isVKDown(VirtualKey vk)
Checks if a virtual key is currently down.
Definition: terminal.cpp:5130
bool multilineInsertChar(int charsToMove)
Inserts a blank character and move specified amount of characters to the right.
Definition: terminal.cpp:5097
int getCursorCol()
Gets current cursor column.
Definition: terminal.cpp:5077
bool setChar(uint8_t c)
Sets a raw character at current cursor position.
Definition: terminal.cpp:5119
void setTerminal(Terminal *terminal=nullptr)
Sets destination terminal.
Definition: terminal.cpp:4977
Delegate< int * > onRead
Read character delegate.
Definition: terminal.h:1983
int getCursorRow()
Gets current cursor row.
Definition: terminal.cpp:5087
Delegate< int > onWrite
Write character delegate.
Definition: terminal.h:1992
void getCursorPos(int *col, int *row)
Gets current cursor position.
Definition: terminal.cpp:5066
void enableCursor(bool value)
Enables/disables cursor.
Definition: terminal.cpp:5027
void setBackgroundColor(Color value)
Sets background color.
Definition: terminal.cpp:5167
void clear()
Clears screen.
Definition: terminal.cpp:5019
void setCursorPos(int col, int row)
Sets current cursor position.
Definition: terminal.cpp:5036
void setTerminalType(TermType value)
Sets the terminal type to emulate.
Definition: terminal.cpp:5149
void setForegroundColor(Color value)
Sets foreground color.
Definition: terminal.cpp:5158
void setCharStyle(CharStyle style, bool enabled)
Enables or disables specified character style.
Definition: terminal.cpp:5176
TerminalController allows direct controlling of the Terminal object without using escape sequences.
Definition: terminal.h:1814
An ANSI-VT100 compatible display terminal.
Definition: terminal.h:953
Represents the base abstract class for textual display controllers.
Triangle waveform generator.
Definition: soundgen.h:227
Emulates VIC6561 (VIC20) noise generator.
Definition: soundgen.h:283
uint8_t width
GlyphOptions & Underline(bool value)
Helper method to set or reset underlined.
uint16_t userOpt2
GlyphOptions & Italic(bool value)
Helper method to set or reset italic.
GlyphOptions & Bold(bool value)
Helper method to set or reset bold.
uint8_t const * data
int16_t X
int16_t Y
uint8_t height
#define FABGLIB_TERMINAL_OUTPUT_QUEUE_SIZE
Definition: fabglconf.h:70
#define FABGLIB_DEFAULT_TERMINAL_INPUT_CONSUMER_TASK_STACK_SIZE
Definition: fabglconf.h:78
#define FABGLIB_DEFAULT_TERMINAL_KEYBOARD_READER_TASK_STACK_SIZE
Definition: fabglconf.h:86
#define FABGLIB_DEFAULT_BLINK_PERIOD_MS
Definition: fabglconf.h:49
#define FABGLIB_MAX_DCS_CONTENT
Definition: fabglconf.h:122
#define FABGLIB_MAX_CSI_PARAMS
Definition: fabglconf.h:118
#define FABGLIB_KEYBOARD_READER_TASK_PRIORITY
Definition: fabglconf.h:90
#define FABGLIB_DEFAULT_TERMINAL_INPUT_QUEUE_SIZE
Definition: fabglconf.h:66
#define FABGLIB_CHARS_CONSUMER_TASK_PRIORITY
Definition: fabglconf.h:82
int16_t X1
Definition: fabutils.h:0
int16_t Y2
Definition: fabutils.h:3
int16_t X2
Definition: fabutils.h:2
int16_t Y1
Definition: fabutils.h:1
This file contains some utility classes and functions.
TerminalTransition
This enum defines terminal transition effect.
Definition: terminal.h:751
CharStyle
This enum defines a character style.
Definition: terminal.h:737
@ ReducedLuminosity
Definition: terminal.h:739
TermType
This enum defines supported terminals.
Definition: terminfo.h:110
@ Osborne
Definition: terminfo.h:115
@ ADM3A
Definition: terminfo.h:112
@ VT52
Definition: terminfo.h:117
@ Hazeltine1500
Definition: terminfo.h:114
@ Kaypro
Definition: terminfo.h:116
@ ANSILegacy
Definition: terminfo.h:118
@ ANSI_VT
Definition: terminfo.h:111
@ ADM31
Definition: terminfo.h:113
Color
This enum defines named colors.
@ CursorPointerSimpleReduced
VirtualKey
Represents each possible real or derived (SHIFT + real) key.
Definition: fabutils.h:1097
@ VK_F12
Definition: fabutils.h:1277
@ VK_KP_LEFT
Definition: fabutils.h:1261
@ VK_F8
Definition: fabutils.h:1273
@ VK_KP_RIGHT
Definition: fabutils.h:1263
@ VK_F7
Definition: fabutils.h:1272
@ VK_KP_PERIOD
Definition: fabutils.h:1190
@ VK_KP_6
Definition: fabutils.h:1118
@ VK_KP_5
Definition: fabutils.h:1117
@ VK_PAGEUP
Definition: fabutils.h:1252
@ VK_KP_END
Definition: fabutils.h:1242
@ VK_KP_8
Definition: fabutils.h:1120
@ VK_F11
Definition: fabutils.h:1276
@ VK_F9
Definition: fabutils.h:1274
@ VK_KP_9
Definition: fabutils.h:1121
@ VK_KP_4
Definition: fabutils.h:1116
@ VK_F2
Definition: fabutils.h:1267
@ VK_F1
Definition: fabutils.h:1266
@ VK_KP_PAGEDOWN
Definition: fabutils.h:1255
@ VK_UP
Definition: fabutils.h:1256
@ VK_KP_UP
Definition: fabutils.h:1257
@ VK_KP_1
Definition: fabutils.h:1113
@ VK_END
Definition: fabutils.h:1241
@ VK_PAGEDOWN
Definition: fabutils.h:1254
@ VK_KP_DELETE
Definition: fabutils.h:1237
@ VK_LCTRL
Definition: fabutils.h:1224
@ VK_KP_CENTER
Definition: fabutils.h:1264
@ VK_KP_0
Definition: fabutils.h:1112
@ VK_F5
Definition: fabutils.h:1270
@ VK_DOWN
Definition: fabutils.h:1258
@ VK_F6
Definition: fabutils.h:1271
@ VK_HOME
Definition: fabutils.h:1239
@ VK_F3
Definition: fabutils.h:1268
@ VK_KP_DOWN
Definition: fabutils.h:1259
@ VK_DELETE
Definition: fabutils.h:1236
@ VK_KP_7
Definition: fabutils.h:1119
@ VK_KP_ENTER
Definition: fabutils.h:1250
@ VK_KP_3
Definition: fabutils.h:1115
@ VK_F10
Definition: fabutils.h:1275
@ VK_BACKSPACE
Definition: fabutils.h:1238
@ VK_NONE
Definition: fabutils.h:1098
@ VK_LEFT
Definition: fabutils.h:1260
@ VK_KP_INSERT
Definition: fabutils.h:1235
@ VK_KP_2
Definition: fabutils.h:1114
@ VK_KP_HOME
Definition: fabutils.h:1240
@ VK_RIGHT
Definition: fabutils.h:1262
@ VK_F4
Definition: fabutils.h:1269
@ VK_KP_PAGEUP
Definition: fabutils.h:1253
@ VK_INSERT
Definition: fabutils.h:1234
FlowControl
This enum defines various serial port flow control methods.
Definition: terminal.h:706
PixelFormat
This enum defines a pixel format.
This file contains fabgl::Mouse definition.
This file contains fabgl::Terminal definition.