From e4ce68657698f6b059d6109b9cecad93fff5477a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juhani=20Krekel=C3=A4?= Date: Fri, 16 Jun 2023 11:08:52 +0300 Subject: [PATCH] Use standard termios flags and functions in chvideomode(1). Previously chvideomode(1) used {get,set}termmode(2) as well as tcgetwincurpos(2), which are non-standard and only work in the kernel terminal. This necessitates changing the keybind for exiting the menu from esc to Q, as there is no good standard way to disambiguate between escape character by itself and the start of an escape sequence. --- chvideomode/chvideomode.1 | 6 +- chvideomode/chvideomode.c | 186 ++++++++++++++++++++------------------ 2 files changed, 103 insertions(+), 89 deletions(-) diff --git a/chvideomode/chvideomode.1 b/chvideomode/chvideomode.1 index bf1b3ec5..35fdb820 100644 --- a/chvideomode/chvideomode.1 +++ b/chvideomode/chvideomode.1 @@ -1,4 +1,4 @@ -.Dd February 6, 2017 +.Dd June 16, 2023 .Dt CHVIDEOMODE 1 .Os .Sh NAME @@ -63,7 +63,9 @@ The interactive key commands are as follows: Move the selection up 1 item. .It Down Arrow Move the selection down 1 item. -.It Escape +.It 0 - 9 +Select item of the specified number. +.It q , Q Quit without changing video mode. .It Enter Change the video mode to the current selection. diff --git a/chvideomode/chvideomode.c b/chvideomode/chvideomode.c index 884f0add..2d7bbab1 100644 --- a/chvideomode/chvideomode.c +++ b/chvideomode/chvideomode.c @@ -20,8 +20,6 @@ #include #include -#include -#include #include #include @@ -29,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +37,17 @@ #include #include +struct termios saved; + +static void restore_terminal(int sig) +{ + if ( tcsetattr(0, TCSANOW, &saved) ) + err(1, "tcsetattr"); + // Re-raise the signal. As we set SA_RESETHAND this will not run the handler + // again but rather fall back to default. + raise(sig); +} + static bool set_current_mode(const struct tiocgdisplay* display, struct dispmsg_crtc_mode mode) { @@ -202,18 +212,15 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, int mode_set_error, struct dispmsg_crtc_mode* mode) { + if ( !isatty(0) ) + errx(1, "Interactive menu requires stdin to be a terminal"); + int num_modes_display_length = 1; for ( size_t i = num_modes; 10 <= i; i /= 10 ) num_modes_display_length++; - size_t selection; - bool decided; - bool first_render; - struct wincurpos render_at; - selection = 0; - decided = false; - first_render = true; - memset(&render_at, 0, sizeof(render_at)); + size_t selection = 0; + bool decided = false; while ( !decided ) { fflush(stdout); @@ -225,13 +232,6 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, ws.ws_row = 25; } - struct wincurpos wcp; - if ( tcgetwincurpos(1, &wcp) != 0 ) - { - wcp.wcp_col = 1; - wcp.wcp_row = 1; - } - size_t off = 1; // The "Please select ..." line at the top. if ( mode_set_error ) off++; @@ -245,36 +245,18 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, how_many = how_many_available; size_t lines_on_screen = off + how_many; - if ( first_render ) - { - while ( wcp.wcp_row && - ws.ws_row - (wcp.wcp_row + 1) < lines_on_screen ) - { - printf("\e[S"); - printf("\e[%juH", 1 + (uintmax_t) wcp.wcp_row); - wcp.wcp_row--; - wcp.wcp_col = 1; - } - render_at = wcp; - first_render = false; - } - printf("\e[m"); - printf("\e[%juH", 1 + (uintmax_t) render_at.wcp_row); printf("\e[2K"); if ( mode_set_error ) printf("Error: Could not set desired mode: %s\n", strerror(mode_set_error)); - printf("Please select one of these video modes or press ESC to " - "abort.\n"); + printf("Please select one of these video modes or press Q to abort.\n"); for ( size_t i = 0; i < how_many; i++ ) { size_t index = from + i; - size_t screenline = off + index - from; const char* color = index == selection ? "\e[31m" : "\e[m"; - printf("\e[%zuH", 1 + render_at.wcp_row + screenline); printf("%s", color); printf("\e[2K"); printf(" [%-*zu] ", num_modes_display_length, index); @@ -288,77 +270,108 @@ static bool select_mode(struct dispmsg_crtc_mode* modes, else printf("(unknown video device feature)"); printf("\e[m"); + if ( i + 1 < how_many ) + printf("\n"); } printf("\e[J"); fflush(stdout); - unsigned int oldtermmode; - if ( gettermmode(0, &oldtermmode) < 0 ) - err(1, "gettermmode"); + // Block delivery of SIGTSTP during menu to avoid having to deal with + // complex interactions between signals and terminal settings. + sigset_t sigtstp; + sigemptyset(&sigtstp); + sigaddset(&sigtstp, SIGTSTP); + sigprocmask(SIG_BLOCK, &sigtstp, NULL); - if ( settermmode(0, TERMMODE_KBKEY | TERMMODE_UNICODE | - TERMMODE_SIGNAL) < 0 ) - err(1, "settermmode"); + if ( tcgetattr(0, &saved) ) + err(1, "tcgetattr"); + + // Revert back to normal terminal settings before dying on a signal. + struct sigaction sa = {0}; + sa.sa_handler = restore_terminal; + sa.sa_flags = SA_RESETHAND; // The handler should only run once. + sigaction(SIGINT, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + + struct termios altered = saved; + altered.c_lflag &= ~(ECHO | ICANON); + if ( tcsetattr(0, TCSANOW, &altered) ) + err(1, "tcsetattr"); bool redraw = false; while ( !redraw && !decided ) { - uint32_t codepoint; - ssize_t numbytes = read(0, &codepoint, sizeof(codepoint)); - if ( numbytes < 0 ) - err(1, "read"); + int byte = fgetc(stdin); - int kbkey = KBKEY_DECODE(codepoint); - if ( kbkey ) + if ( byte == '\e' ) { - switch ( kbkey ) + switch ( fgetc(stdin) ) { - case KBKEY_ESC: - if ( settermmode(0, oldtermmode) < 0 ) - err(1, "settermmode"); + case 'O': fgetc(stdin); break; // \eO is followed by one byte + case '[': + { + // Sequence can have numbers separated by a semicolon before + // the final character, so read until a non-digit + // non-semicolon is found. + size_t length = 1; + while ( (byte = fgetc(stdin)) && + (('0' <= byte && byte <= '9') || byte == ';' ) ) + length++; + + if ( length == 1 && byte == 'A' ) // Up key + { + if ( selection ) + selection--; + else + selection = num_modes - 1; + redraw = true; + } + else if ( length == 1 && byte == 'B' ) // Down key + { + if ( selection + 1 == num_modes ) + selection = 0; + else + selection++; + redraw = true; + } + break; + } + } + } + else if ( '0' <= byte && byte <= '9' ) + { + uint32_t requested = byte - '0'; + if ( requested < num_modes ) + { + selection = requested; + redraw = true; + } + } + else + { + switch ( byte ) + { + case 'q': + case 'Q': + if ( tcsetattr(0, TCSANOW, &saved) ) + err(1, "tcsetattr"); printf("\n"); return false; - break; - case KBKEY_UP: - if ( selection ) - selection--; - else - selection = num_modes -1; - redraw = true; - break; - case KBKEY_DOWN: - if ( selection + 1 == num_modes ) - selection = 0; - else - selection++; - redraw = true; - break; - case KBKEY_ENTER: - if ( settermmode(0, oldtermmode) < 0 ) - err(1, "settermmode"); - fgetc(stdin); + case '\n': printf("\n"); decided = true; break; } } - else - { - if ( L'0' <= codepoint && codepoint <= '9' ) - { - uint32_t requested = codepoint - '0'; - if ( requested < num_modes ) - { - selection = requested; - redraw = true; - } - } - } } - if ( settermmode(0, oldtermmode) < 0 ) - err(1, "settermmode"); + if ( redraw ) + printf("\e[%zuF", lines_on_screen - 1); + + if ( tcsetattr(0, TCSANOW, &saved) ) + err(1, "tcsetattr"); } *mode = modes[selection]; @@ -498,8 +511,7 @@ int main(int argc, char* argv[]) } struct tiocgdisplay display; - struct tiocgdisplays gdisplays; - memset(&gdisplays, 0, sizeof(gdisplays)); + struct tiocgdisplays gdisplays = {0}; gdisplays.count = 1; gdisplays.displays = &display; if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) < 0 || gdisplays.count == 0 )