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