From 3a7a058aed938446030d58dfcd94b6bc8f841d3d Mon Sep 17 00:00:00 2001 From: martinwhitaker Date: Wed, 8 Oct 2025 11:51:49 +0100 Subject: [PATCH] Recognise VT52/VT100/VT220 key escape sequences on serial console (#547) * Recognise VT52/VT100/VT220 key escape sequences on serial console. Map function keys to '0' to '9' and cursor keys to 'u', 'd', 'l', 'r', as is done for legacy and USB keyboards. Ignore all other special key sequences. * Make serial_echo_print() and tty_goto() static. These are local to serial.c. Making them static saves a few bytes. * Update keyboard.h to document cursor key mapping. * Added some escape codes for terminals & VT100+ Change input order to check TTY first * Replace comments about PF6-10 --------- Co-authored-by: Sam Demeulemeester --- system/keyboard.c | 158 +++++++++++++++++++++++++++++++++++++++++++--- system/keyboard.h | 3 +- system/serial.c | 27 +++++--- system/serial.h | 5 +- 4 files changed, 171 insertions(+), 22 deletions(-) diff --git a/system/keyboard.c b/system/keyboard.c index 2ad896b..7079558 100644 --- a/system/keyboard.c +++ b/system/keyboard.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -// Copyright (C) 2020-2022 Martin Whitaker. +// Copyright (C) 2020-2025 Martin Whitaker. #include @@ -13,6 +13,13 @@ #include "keyboard.h" #include "config.h" +//------------------------------------------------------------------------------ +// Private Constants +//------------------------------------------------------------------------------ + +// The number of frame periods to wait during escape sequence parsing. +#define TTY_SEQ_WAIT_TIME 2 + //------------------------------------------------------------------------------ // Private Variables //------------------------------------------------------------------------------ @@ -216,6 +223,133 @@ static const char usb_hid_keymap[] = { keyboard_types_t keyboard_types = KT_NONE; +//------------------------------------------------------------------------------ +// Private Functions +//------------------------------------------------------------------------------ + +// This function is called when an ESC O prefix has been detected. +static char get_vt220_sequence1(void) +{ + switch (tty_get_char(TTY_SEQ_WAIT_TIME)) { + case 'P': + return '1'; // VT100/VT220 PF1 (F1 in terminal emulators) + case 'Q': + return '2'; // VT100/VT220 PF2 (F2 in terminal emulators) + case 'R': + return '3'; // VT100/VT220 PF3 (F3 in terminal emulators) + case 'S': + return '4'; // VT100/VT220 PF4 (F4 in terminal emulators) + case 'T': + return '5'; // F5 in some terminal emulators + case 'U': + return '6'; // F6 in some terminal emulators + case 'V': + return '7'; // F7 in some terminal emulators + case 'W': + return '8'; // F8 in some terminal emulators + case 'X': + return '9'; // F9 in some terminal emulators + case 'Y': + return '0'; // F10 in some terminal emulators + default: + return '\0'; // unrecognised key + break; + } +} + +// This function is called when an ESC [ prefix has been detected. +static char get_vt220_sequence2(void) +{ + char ch1; + char ch2; + + ch1 = tty_get_char(TTY_SEQ_WAIT_TIME); + switch (ch1) { + case 'A': + return 'u'; // VT100/VT220 cursor up + case 'B': + return 'd'; // VT100/VT220 cursor down + case 'C': + return 'r'; // VT100/VT220 cursor right + case 'D': + return 'l'; // VT100/VT220 cursor left + default: + break; + } + if ((ch1 < '1') || (ch1 > '6')) { + return '\0'; // unrecognised sequence - give up + } + ch2 = tty_get_char(TTY_SEQ_WAIT_TIME); + if (ch2 == '~') { + return '\0'; // VT100/VT220 editing key - ignore it + } + switch (ch1) { + case '1': + switch (ch2) { + case '1': + ch1 = '1'; break; // F1 in terminal emulators + case '2': + ch1 = '2'; break; // F2 in terminal emulators + case '3': + ch1 = '3'; break; // F3 in terminal emulators + case '4': + ch1 = '4'; break; // F4 in terminal emulators + case '5': + ch1 = '5'; break; // F5 in terminal emulators + case '7': + ch1 = '6'; break; // VT220 F6 + case '8': + ch1 = '7'; break; // VT220 F7 + case '9': + ch1 = '8'; break; // VT220 F8 + default: + ch1 = '\0'; break; // unrecognised key + } + break; + case '2': + switch (ch2) { + case '0': + ch1 = '9'; break; // VT220 F9 + case '1': + ch1 = '0'; break; // VT220 F10 + default: + ch1 = '\0'; break; // unrecognised key + } + break; + default: + ch1 = '\0'; // unrecognised key + break; + } + ch2 = tty_get_char(TTY_SEQ_WAIT_TIME); + if (ch2 == '~') { + return ch1; // valid key sequence - return decoded key + } + return '\0'; // invalid key sequence - ignore it +} + +// This function is called when an ESC has been detected. +static char get_tty_special_key(void) +{ + switch (tty_get_char(TTY_SEQ_WAIT_TIME)) { + case 'A': + return 'u'; // VT52 cursor up + case 'B': + return 'd'; // VT52 cursor down + case 'C': + return 'r'; // VT52 cursor right + case 'D': + return 'l'; // VT52 cursor left + case 'O': + return get_vt220_sequence1(); + case '[': + return get_vt220_sequence2(); + case '\0': + return ESC; // timeout - return ESC character + default: + return '\0'; // unrecognised sequence - give up + } +} + //------------------------------------------------------------------------------ // Public Functions //------------------------------------------------------------------------------ @@ -239,6 +373,20 @@ void keyboard_init(void) char get_key(void) { + if (enable_tty) { + char c = tty_get_char(0); + switch (c) { + case '\0': + break; + case '\r': + return '\n'; + case ESC: + return get_tty_special_key(); + default: + return c; + } + } + if (keyboard_types & KT_USB) { uint8_t c = get_usb_keycode(); if (c > 0 && c < sizeof(usb_hid_keymap)) { @@ -274,13 +422,5 @@ char get_key(void) } } - if (enable_tty) { - uint8_t c = tty_get_key(); - if (c != 0xFF) { - if (c == 0x0D) c = '\n'; // Enter - return c; - } - } - return '\0'; } diff --git a/system/keyboard.h b/system/keyboard.h index 068cc85..10eabe7 100644 --- a/system/keyboard.h +++ b/system/keyboard.h @@ -40,7 +40,8 @@ void keyboard_init(void); /** * Checks if a key has been pressed and returns the primary ASCII character * corresponding to that key if so, otherwise returns the null character. - * F1 to F10 are mapped to the corresponding decimal digit (F10 -> 0). All + * F1 to F10 are mapped to the corresponding decimal digit (F10 -> 0). The + * Up, Down, Left, Right cursor keys are mapped to 'u', 'd', 'l', 'r'. All * other keys that don't have a corresponding ASCII character are ignored. * Characters are only returned for key presses, not key releases. A US * keyboard layout is assumed (because we can't easily do anything else). diff --git a/system/serial.c b/system/serial.c index 4f01db7..7682dff 100644 --- a/system/serial.c +++ b/system/serial.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -// Copyright (C) 2004-2023 Sam Demeulemeester +// Copyright (C) 2004-2025 Sam Demeulemeester #include #include @@ -65,7 +65,7 @@ static void serial_wait_for_xmit(struct serial_port *port) } while ((lsr & BOTH_EMPTY) != BOTH_EMPTY); } -void serial_echo_print(const char *p) +static void serial_echo_print(const char *p) { struct serial_port *port = &console_serial; @@ -81,7 +81,7 @@ void serial_echo_print(const char *p) } } -void tty_goto(int y, int x) +static void tty_goto(int y, int x) { static char s[3]; @@ -117,6 +117,9 @@ void tty_init(void) console_serial.parity = SERIAL_DEFAULT_PARITY; console_serial.bits = SERIAL_DEFAULT_BITS; + // Precalculate the frame time (in microseconds). + console_serial.frame_time = (1000000 * (1 + console_serial.bits + console_serial.parity + 1)) / console_serial.baudrate; + // UART MMIO Address is usually above TOLUD and never < 1MB if (console_serial.base_addr > 0xFFFF) { console_serial.is_mmio = true; @@ -242,13 +245,17 @@ void tty_send_region(int start_row, int start_col, int end_row, int end_col) } } -char tty_get_key(void) +char tty_get_char(int max_wait_frames) { - int uart_status = serial_read_reg(&console_serial, UART_LSR); + int wait_time = max_wait_frames * console_serial.frame_time; + do { + int uart_status = serial_read_reg(&console_serial, UART_LSR); + if (uart_status & UART_LSR_DR) { + return serial_read_reg(&console_serial, UART_RX); + } + usleep(10); + wait_time -= 10; + } while (wait_time > 0); - if (uart_status & UART_LSR_DR) { - return serial_read_reg(&console_serial, UART_RX); - } else { - return 0xFF; - } + return '\0'; } diff --git a/system/serial.h b/system/serial.h index f9239aa..9142bb8 100644 --- a/system/serial.h +++ b/system/serial.h @@ -7,7 +7,7 @@ * display via Serial/UART. * *//* - * Copyright (C) 2004-2023 Sam Demeulemeester. + * Copyright (C) 2004-2025 Sam Demeulemeester. */ #define SERIAL_DEFAULT_BITS 8 @@ -26,6 +26,7 @@ struct serial_port { int parity; int bits; int baudrate; + int frame_time; int reg_width; int refclk; uintptr_t base_addr; @@ -179,6 +180,6 @@ void tty_print(int y, int x, const char *p); void tty_send_region(int start_row, int start_col, int end_row, int end_col); -char tty_get_key(void); +char tty_get_char(int max_wait_frames); #endif /* _SERIAL_REG_H */