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 <github@x86-secret.com>
This commit is contained in:
martinwhitaker
2025-10-08 12:51:49 +02:00
committed by GitHub
co-authored by Sam Demeulemeester
parent 6f4ec848c4
commit 3a7a058aed
4 changed files with 171 additions and 22 deletions
+149 -9
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020-2022 Martin Whitaker.
// Copyright (C) 2020-2025 Martin Whitaker.
#include <stdint.h>
@@ -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';
}
+2 -1
View File
@@ -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).
+17 -10
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2004-2023 Sam Demeulemeester
// Copyright (C) 2004-2025 Sam Demeulemeester
#include <stdbool.h>
#include <stddef.h>
@@ -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';
}
+3 -2
View File
@@ -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 */