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