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-2021 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. Feel free to use FabGL in free software and hardware:
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 
58 namespace fabgl {
59 
60 
61 // Terminal identification ID
62 // 64 = VT420
63 // 1 = support for 132 columns
64 // 6 = selective erase
65 // 22 = color
66 const char TERMID[] = "?64;1;6;22c";
67 
68 // to send 8 bit (S8C1T) or 7 bit control characters
69 const char CSI_7BIT[] = "\e[";
70 const char CSI_8BIT[] = "\x9B";
71 const char DCS_7BIT[] = "\eP";
72 const char DCS_8BIT[] = "\x90";
73 const char SS2_7BIT[] = "\eN";
74 const char SS2_8BIT[] = "\x8E";
75 const char SS3_7BIT[] = "\eO";
76 const char SS3_8BIT[] = "\x8F";
77 const char ST_7BIT[] = "\e\\";
78 const char ST_8BIT[] = "\x9C";
79 const char OSC_7BIT[] = "\e]";
80 const 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
88 static 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 
101 const 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 
178 Terminal * Terminal::s_activeTerminal = nullptr;
179 
180 
182 
184 
186 
187 
188 
189 Terminal::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 
202 Terminal::~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 
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) {
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;
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) {
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;
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 
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
301 void 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 
323 bool 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 
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
441 void 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)
461 inline 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
469 static 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
478 static 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
487 void 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 
500 void 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
510 void 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
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
543 void 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;
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 
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 
651 {
652  if (m_outputQueue)
653  vQueueDelete(m_outputQueue);
654  m_outputQueue = nullptr;
655 }
656 
657 
658 void 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 
676 void Terminal::log(const char * txt)
677 {
678  if (m_logStream)
679  m_logStream->write(txt);
680 }
681 
682 
683 void Terminal::log(char c)
684 {
685  if (m_logStream)
686  m_logStream->write(c);
687 }
688 
689 
690 void 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 
701 void Terminal::freeTabStops()
702 {
703  if (m_emuState.tabStop) {
704  free(m_emuState.tabStop);
705  m_emuState.tabStop = nullptr;
706  }
707 }
708 
709 
710 void 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 
723 void 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 
797 void 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 
878 void 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
893 void 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 
911 void Terminal::setBackgroundColor(Color color, bool setAsDefault)
912 {
913  if (setAsDefault)
914  m_defaultBackgroundColor = color;
916 }
917 
918 
919 void 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 
931 void Terminal::setForegroundColor(Color color, bool setAsDefault)
932 {
933  if (setAsDefault)
934  m_defaultForegroundColor = color;
936 }
937 
938 
939 void 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 
951 int CharStyleToColorAttributeIndex(CharStyle attribute)
952 {
953  switch (attribute) {
954  case CharStyle::Bold:
955  return COLORED_ATTRIBUTE_BOLD;
957  return COLORED_ATTRIBUTE_FAINT;
958  case CharStyle::Italic:
959  return COLORED_ATTRIBUTE_ITALIC;
961  return COLORED_ATTRIBUTE_UNDERLINE;
962  default:
963  return COLORED_ATTRIBUTE_INVALID;
964  }
965 }
966 
967 
968 void 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 
980 {
981  int cindex = CharStyleToColorAttributeIndex(attribute);
982  if (cindex != COLORED_ATTRIBUTE_INVALID)
983  m_coloredAttributesMask &= ~(1 << cindex);
984 }
985 
986 
987 void 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 
1001 void Terminal::clear(bool moveCursor)
1002 {
1003  TerminalController(this).clear();
1004  if (moveCursor)
1005  TerminalController(this).setCursorPos(1, 1);
1006 }
1007 
1008 
1009 void 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 
1021 void 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
1032 bool 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
1046 bool 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
1060 void 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 
1077 void 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 
1092 int 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 
1106 void Terminal::enableCursor(bool value)
1107 {
1108  TerminalController(this).enableCursor(value);
1109 }
1110 
1111 
1112 bool 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 
1139 bool 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
1148 void 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 
1167 void 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 
1191 void 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 
1223 void 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.
1242 void 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
1250 void 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 
1263 void 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
1292 void 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 
1303 void 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 
1342 void 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 
1353 void 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 
1368 void 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")
1378 bool 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.
1414 void 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 
1444 void 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
1476 void 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
1510 void 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 
1545 void 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 
1553 void 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 
1568 void 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 
1599 void 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 
1628 void 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 
1658 void Terminal::localInsert(uint8_t c)
1659 {
1660  if (m_outputQueue)
1661  xQueueSendToFront(m_outputQueue, &c, portMAX_DELAY);
1662 }
1663 
1664 
1665 void Terminal::localWrite(uint8_t c)
1666 {
1667  if (m_outputQueue)
1668  xQueueSendToBack(m_outputQueue, &c, portMAX_DELAY);
1669 }
1670 
1671 
1672 void 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 
1689 {
1690  return m_outputQueue ? uxQueueMessagesWaiting(m_outputQueue) : 0;
1691 }
1692 
1693 
1695 {
1696  return read(-1);
1697 }
1698 
1699 
1700 int 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 
1711 bool 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
1725 {
1726  return -1;
1727 }
1728 
1729 
1731 {
1732  flush(true);
1733 }
1734 
1735 
1736 #ifdef ARDUINO
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 
1769 void 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
1817 void 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
1845 void 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 
1878 void Terminal::sendCSI()
1879 {
1880  send(m_emuState.ctrlBits == 7 ? CSI_7BIT : CSI_8BIT);
1881 }
1882 
1883 
1884 void Terminal::sendDCS()
1885 {
1886  send(m_emuState.ctrlBits == 7 ? DCS_7BIT : DCS_8BIT);
1887 }
1888 
1889 
1890 void Terminal::sendSS3()
1891 {
1892  send(m_emuState.ctrlBits == 7 ? SS3_7BIT : SS3_8BIT);
1893 }
1894 
1895 
1897 {
1898  return uxQueueSpacesAvailable(m_inputQueue);
1899 }
1900 
1901 
1902 bool 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 
1911 bool 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 
1920 void 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 
1943 size_t Terminal::write(uint8_t c)
1944 {
1945  write(c, false);
1946  return 1;
1947 }
1948 
1949 
1950 size_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 
1959 {
1960  // doesn't set it immediately, serialize into the queue
1961  TerminalController(this).setTerminalType(value);
1962 }
1963 
1964 
1965 void 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 
1984 void 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;
2008  case TermType::ANSILegacy:
2009  int_setTerminalType(&term_ANSILegacy);
2010  break;
2011  }
2012 }
2013 
2014 
2015 void 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 
2052 void 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
2151 void 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 
2166 uint32_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
2200 bool 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 
2260 void 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!!
2271 void 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 
2282 void 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
2299 void 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 
2316 int 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 
2322 int 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 
2328 GlyphOptions 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
2335 uint8_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 
2357 void Terminal::charsConsumerTask(void * pvParameters)
2358 {
2359  Terminal * term = (Terminal*) pvParameters;
2360 
2361  while (true)
2362  term->consumeInputQueue();
2363 }
2364 
2365 
2366 void 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 
2399 void 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)
2489 void 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
2666 uint8_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)
2714 void 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
3001 void 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
3026 void 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"
3055 void 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
3208 void 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 "\")
3341 void 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 
3404 void 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 + '\')
3522 void 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 
3542 {
3543  if (!m_soundGenerator)
3544  m_soundGenerator = new SoundGenerator;
3545  return m_soundGenerator;
3546 }
3547 
3548 
3549 void 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
3576 uint8_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)
3589 int 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
3619 void 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 ....)
3636 void 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 
4186 void 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
4202 void 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;
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 
4569 void 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 
4620 void Terminal::sendCursorKeyCode(uint8_t c)
4621 {
4622  if (m_emuState.cursorKeysMode)
4623  sendSS3();
4624  else
4625  sendCSI();
4626  send(c);
4627 }
4628 
4629 
4630 void 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 
4642 void 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 
4834 void 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 
4931 void 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 
4953  : m_terminal(terminal)
4954 {
4955 }
4956 
4957 
4958 TerminalController::~TerminalController()
4959 {
4960 }
4961 
4962 
4964 {
4965  m_terminal = terminal;
4966 }
4967 
4968 
4969 void TerminalController::write(uint8_t c)
4970 {
4971  if (m_terminal)
4972  m_terminal->write(c);
4973  else
4974  onWrite(c);
4975 }
4976 
4977 
4978 void TerminalController::write(char const * str)
4979 {
4980  while (*str)
4981  write(*str++);
4982 }
4983 
4984 
4985 int 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 
4997 void 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 
5022 void TerminalController::setCursorPos(int col, int row)
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 
5052 void 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 
5192 LineEditor::~LineEditor()
5193 {
5194  if (m_typeText)
5195  free(m_typeText);
5196  free(m_text);
5197 }
5198 
5199 
5200 void 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 
5212 void 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 
5221 void LineEditor::setText(char const * text, bool moveCursor)
5222 {
5223  setText(text, strlen(text), moveCursor);
5224 }
5225 
5226 
5227 void 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 
5245 void LineEditor::write(uint8_t c)
5246 {
5247  if (m_terminal)
5248  m_terminal->write(c);
5249  else
5250  onWrite(c);
5251 }
5252 
5253 
5254 int 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 
5266 void 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 
5288 void 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 
5298 void LineEditor::performCursorUp()
5299 {
5301 }
5302 
5303 
5304 void LineEditor::performCursorDown()
5305 {
5307 }
5308 
5309 
5310 void 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 
5325 void 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 
5340 void LineEditor::performCursorHome()
5341 {
5342  m_termctrl.setCursorPos(m_homeCol, m_homeRow);
5343  m_inputPos = 0;
5344 }
5345 
5346 
5347 void LineEditor::performCursorEnd()
5348 {
5349  m_termctrl.cursorRight(m_textLength - m_inputPos);
5350  m_inputPos = m_textLength;
5351 }
5352 
5353 
5354 void 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 
5364 void 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 
5376 char 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
int16_t X2
Definition: fabutils.h:179
Noise generator.
Definition: soundgen.h:253
#define FABGLIB_MAX_DCS_CONTENT
Definition: fabglconf.h:122
Delegate< int > onWrite
Write character delegate.
Definition: terminal.h:1990
GlyphOptions & Bold(bool value)
Helper method to set or reset bold.
void flush()
Waits for all codes sent to the display has been processed.
Definition: terminal.cpp:1730
Delegate< int * > onCarriageReturn
A delegate called whenever carriage return has been pressed.
Definition: terminal.h:2156
void end()
Finalizes the terminal.
Definition: terminal.cpp:411
void disconnectLocally()
Avoids using of terminal locally.
Definition: terminal.cpp:650
A class with a set of drawing methods.
Definition: canvas.h:70
void activate(TerminalTransition transition=TerminalTransition::None)
Activates this terminal for input and output.
Definition: terminal.cpp:215
void setForegroundColor(Color value)
Sets foreground color.
Definition: terminal.cpp:5144
#define FABGLIB_CHARS_CONSUMER_TASK_PRIORITY
Definition: fabglconf.h:82
TerminalController(Terminal *terminal=nullptr)
Object constructor.
Definition: terminal.cpp:4952
bool isKeyboardAvailable()
Checks if keyboard has been detected and correctly initialized.
Definition: keyboard.h:152
char const * edit(int maxLength=0)
Reads user input and return the inserted line.
Definition: terminal.cpp:5376
void connectLocally()
Permits using of terminal locally.
Definition: terminal.cpp:642
#define FABGLIB_KEYBOARD_READER_TASK_PRIORITY
Definition: fabglconf.h:90
bool waitFor(int value, int timeOutMS=-1)
Wait for a specific code from keyboard, discarding all previous codes.
Definition: terminal.cpp:1711
void setCursorPos(int col, int row)
Sets current cursor position.
Definition: terminal.cpp:5022
Delegate< int * > onRead
Read character delegate.
Definition: terminal.h:2129
void typeText(char const *text)
Simulates user typing.
Definition: terminal.cpp:5212
int getHeight()
Determines the canvas height in pixels.
Definition: canvas.h:92
int16_t Y2
Definition: fabutils.h:180
GlyphOptions & Italic(bool value)
Helper method to set or reset italic.
Triangle waveform generator.
Definition: soundgen.h:219
void reset()
Resets paint state and other display controller settings.
Definition: canvas.cpp:118
int16_t Y1
Definition: fabutils.h:178
uint16_t userOpt2
void scroll(int offsetX, int offsetY)
Scrolls pixels horizontally and/or vertically.
Definition: canvas.cpp:129
#define FABGLIB_DEFAULT_TERMINAL_INPUT_CONSUMER_TASK_STACK_SIZE
Definition: fabglconf.h:78
int16_t Y
bool CTSStatus()
Reports current CTS signal status.
Definition: terminal.h:1451
FlowControl
This enum defines various serial port flow control methods.
Definition: terminal.h:706
void setForegroundColor(Color color, bool setAsDefault=true)
Sets the foreground color.
Definition: terminal.cpp:931
static int inputQueueSize
Number of characters the terminal can "write" without pause (increase if you have loss of characters ...
Definition: terminal.h:1508
bool multilineInsertChar(int charsToMove)
Inserts a blank character and move specified amount of characters to the right.
Definition: terminal.cpp:5083
TerminalTransition
This enum defines terminal transition effect.
Definition: terminal.h:751
void setCodePage(CodePage const *codepage)
Sets font codepage for virtual key to ASCII conversion.
Definition: keyboard.h:275
void enableCursor(bool value)
Enables or disables cursor.
Definition: terminal.cpp:1106
uint8_t const * data
Color
This enum defines named colors.
static Mouse * mouse()
Returns the instance of Mouse object automatically created by PS2Controller.
void setRTSStatus(bool value)
Sets RTS signal status.
Definition: terminal.cpp:500
void getLEDs(bool *numLock, bool *capsLock, bool *scrollLock)
Gets keyboard LEDs status.
Definition: keyboard.cpp:164
void flowControl(bool enableRX)
Allows/disallows host to send data.
Definition: terminal.cpp:510
This file contains fabgl::Terminal definition.
The PS2 Keyboard controller class.
Definition: keyboard.h:77
CharStyle
This enum defines a character style.
Definition: terminal.h:737
void getCursorPos(int *col, int *row)
Gets current cursor position.
Definition: terminal.cpp:5052
int getWidth()
Determines the canvas width in pixels.
Definition: canvas.h:83
void setPaintOptions(PaintOptions options)
Sets paint options.
Definition: canvas.cpp:374
VirtualKey
Represents each possible real or derived (SHIFT + real) key.
Definition: fabutils.h:1016
This file contains fabgl::Mouse definition.
#define FABGLIB_TERMINAL_OUTPUT_QUEUE_SIZE
Definition: fabglconf.h:70
int16_t X1
Definition: fabutils.h:177
bool setLEDs(bool numLock, bool capsLock, bool scrollLock)
Sets keyboard LEDs status.
Definition: keyboard.cpp:155
void pollSerialPort()
Pools the serial port for incoming data.
Definition: terminal.cpp:1737
void fillEllipse(int X, int Y, int width, int height)
Fills an ellipse specifying center and size, using current brush color.
Definition: canvas.cpp:320
void swapRectangle(int X1, int Y1, int X2, int Y2)
Swaps pen and brush colors of the specified rectangle.
Definition: canvas.cpp:311
void setBrushColor(uint8_t red, uint8_t green, uint8_t blue)
Sets brush (background) color specifying color components.
Definition: canvas.cpp:214
void setCharStyle(CharStyle style, bool enabled)
Enables or disables specified character style.
Definition: terminal.cpp:5162
size_t write(const uint8_t *buffer, size_t size)
Sends specified number of codes to the display.
Definition: terminal.cpp:1950
PixelFormat
This enum defines a pixel format.
void waitCompletion(bool waitVSync=true)
Waits for drawing queue to become empty.
Definition: canvas.cpp:84
void endUpdate()
Resumes drawings after beginUpdate().
Definition: canvas.cpp:103
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
This file contains some utility classes and functions.
Delegate< char const * > onUserSequence
Delegate called whenever a new user sequence has been received.
Definition: terminal.h:1494
void fillRectangle(int X1, int Y1, int X2, int Y2)
Fills a rectangle using the current brush color.
Definition: canvas.cpp:278
Definition: canvas.cpp:36
int read()
Reads codes from keyboard.
Definition: terminal.cpp:1694
Delegate< int * > onRead
Read character delegate.
Definition: terminal.h:1981
void setColorForAttribute(CharStyle attribute, Color color, bool maintainStyle)
Selects a color for the specified attribute.
Definition: terminal.cpp:968
GlyphOptions & Underline(bool value)
Helper method to set or reset underlined.
TerminalController allows direct controlling of the Terminal object without using escape sequences...
Definition: terminal.h:1812
Sine waveform generator.
Definition: soundgen.h:177
bool begin(BaseDisplayController *displayController, int maxColumns=-1, int maxRows=-1, Keyboard *keyboard=nullptr)
Initializes the terminal.
Definition: terminal.cpp:323
void drawGlyph(int X, int Y, int width, int height, uint8_t const *data, int index=0)
Draws a glyph at specified position.
Definition: canvas.cpp:340
virtual DisplayControllerType controllerType()=0
Determines the display controller type.
void multilineDeleteChar(int charsToMove)
Deletes a character moving specified amount of characters to the left.
Definition: terminal.cpp:5095
int getCursorRow()
Gets current cursor row.
Definition: terminal.cpp:5073
static int inputConsumerTaskStackSize
Stack size of the task that processes Terminal input stream.
Definition: terminal.h:1517
int available()
Gets the number of codes available in the keyboard queue.
Definition: terminal.cpp:1688
LineEditor(Terminal *terminal)
Object constructor.
Definition: terminal.cpp:5178
int peek()
Reads a code from the keyboard without advancing to the next one.
Definition: terminal.cpp:1724
SoundGenerator handles audio output.
Definition: soundgen.h:344
Emulates VIC6561 (VIC20) noise generator.
Definition: soundgen.h:275
SoundGenerator * soundGenerator()
Gets embedded sound generator.
Definition: terminal.cpp:3541
void setBackgroundColor(Color value)
Sets background color.
Definition: terminal.cpp:5153
bool setChar(uint8_t c)
Sets a raw character at current cursor position.
Definition: terminal.cpp:5105
#define FABGLIB_DEFAULT_TERMINAL_INPUT_QUEUE_SIZE
Definition: fabglconf.h:66
int availableForWrite()
Determines number of codes that the display input queue can still accept.
Definition: terminal.cpp:1896
int16_t X
void setText(char const *text, bool moveCursor=true)
Sets initial text.
Definition: terminal.cpp:5221
void cursorRight(int count)
Moves cursor to the right.
Definition: terminal.cpp:5042
void setBackgroundColor(Color color, bool setAsDefault=true)
Sets the background color.
Definition: terminal.cpp:911
#define FABGLIB_DEFAULT_BLINK_PERIOD_MS
Definition: fabglconf.h:49
static Keyboard * keyboard()
Returns the instance of Keyboard object automatically created by PS2Controller.
An ANSI-VT100 compatible display terminal.
Definition: terminal.h:953
void beginUpdate()
Suspends drawings.
Definition: canvas.cpp:97
void setTerminal(Terminal *terminal=nullptr)
Sets destination terminal.
Definition: terminal.cpp:4963
void setScrollingRegion(int X1, int Y1, int X2, int Y2)
Defines the scrolling region.
Definition: canvas.cpp:145
void clear()
Fills the entire canvas with the brush color.
Definition: canvas.cpp:109
int getCursorCol()
Gets current cursor column.
Definition: terminal.cpp:5063
void setGlyphOptions(GlyphOptions options)
Sets drawing options for the next glyphs.
Definition: canvas.cpp:358
void clear(bool moveCursor=true)
Clears the screen.
Definition: terminal.cpp:1001
TermType
This enum defines supported terminals.
Definition: terminfo.h:110
void drawLine(int X1, int Y1, int X2, int Y2)
Draws a line specifying initial and ending coordinates.
Definition: canvas.cpp:256
Sawtooth waveform generator.
Definition: soundgen.h:236
void setTerminalType(TermType value)
Sets the terminal type to emulate.
Definition: terminal.cpp:5135
bool isVKDown(VirtualKey vk)
Checks if a virtual key is currently down.
Definition: terminal.cpp:5116
bool flowControl()
Checks whether host can receive data.
Definition: terminal.cpp:531
#define FABGLIB_DEFAULT_TERMINAL_KEYBOARD_READER_TASK_STACK_SIZE
Definition: fabglconf.h:86
void clear()
Clears screen.
Definition: terminal.cpp:5005
void drawEllipse(int X, int Y, int width, int height)
Draws an ellipse specifying center and size, using current pen color.
Definition: canvas.cpp:330
void setPenColor(uint8_t red, uint8_t green, uint8_t blue)
Sets pen (foreground) color specifying color components.
Definition: canvas.cpp:193
void connectSerialPort(HardwareSerial &serialPort, bool autoXONXOFF=true)
Connects a remote host using the specified serial port.
Definition: terminal.cpp:441
Delegate< int > onWrite
Write character delegate.
Definition: terminal.h:2138
void drawPath(Point const *points, int pointsCount)
Draws a sequence of lines.
Definition: canvas.cpp:521
void cursorLeft(int count)
Moves cursor to the left.
Definition: terminal.cpp:5032
Represents the base abstract class for all display controllers.
Delegate< int * > onChar
A delegate called whenever a character has been received.
Definition: terminal.h:2145
uint8_t height
Represents the base abstract class for textual display controllers.
void setPixel(int X, int Y)
Fills a single pixel with the pen color.
Definition: canvas.cpp:154
void send(uint8_t c)
Like localWrite() but sends also to serial port if connected.
Definition: terminal.cpp:1817
void disableFabGLSequences()
Disables FabGL specific sequences.
Definition: terminal.cpp:5127
void setPenWidth(int value)
Sets pen width for lines, rectangles and paths.
Definition: canvas.cpp:220
void setTerminalType(TermType value)
Sets the terminal type to emulate.
Definition: terminal.cpp:1958
void fillPath(Point const *points, int pointsCount)
Fills the polygon enclosed in a sequence of lines.
Definition: canvas.cpp:532
void loadFont(FontInfo const *font)
Sets the font to use.
Definition: terminal.cpp:797
void deactivate()
Deactivates this terminal.
Definition: terminal.cpp:290
void localInsert(uint8_t c)
Injects keys into the keyboard queue.
Definition: terminal.cpp:1658
void setOrigin(int X, int Y)
Sets the axes origin.
Definition: canvas.cpp:52
Keyboard * keyboard()
Gets associated keyboard object.
Definition: terminal.h:1363
Square waveform generator.
Definition: soundgen.h:194
uint8_t width
void drawRectangle(int X1, int Y1, int X2, int Y2)
Draws a rectangle using the current pen color.
Definition: canvas.cpp:263
Delegate< LineEditorSpecialChar > onSpecialChar
A delegate called whenever a special character has been pressed.
Definition: terminal.h:2163
#define FABGLIB_MAX_CSI_PARAMS
Definition: fabglconf.h:118
bool isActive()
Determines if this terminal is active or not.
Definition: terminal.h:1392
void enableCursor(bool value)
Enables/disables cursor.
Definition: terminal.cpp:5013
void localWrite(uint8_t c)
Injects keys into the keyboard queue.
Definition: terminal.cpp:1665