From: Lennart Poettering Date: Fri, 29 Aug 2025 21:24:33 +0000 (+0200) Subject: prompt-util: add helpers that paint some "chrome" on top/bottom of screen X-Git-Tag: v259-rc1~451^2~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8191bbd23cb85ed92c7021a03dd6fe370bcfb9ff;p=thirdparty%2Fsystemd.git prompt-util: add helpers that paint some "chrome" on top/bottom of screen We'll soon have three different kind of interactive "wizard"-like console UIs: systemd-firstboot, homectl firstboot and soon systemd-sysinstall. Let's give them a limited, recognizable visual identity, to distinguish them from the usual console output: let's add a bit of "chrome" to the top and bottom of the screen, that we show during ther wizards, but hide again afterwards. This makes use of the DECSTBM sequence that reduces the scrolling area by chopping off blocks from the top or bottom of the screen. The sequence is quite standard, given it has been part of VT100 already. xterm, vte, Linux console all support it just fine. --- diff --git a/man/os-release.xml b/man/os-release.xml index 4bbb87b1bfe..0c9b3de493b 100644 --- a/man/os-release.xml +++ b/man/os-release.xml @@ -439,15 +439,26 @@ ANSI_COLOR= - A suggested presentation color when showing the OS name on the console. This should - be specified as string suitable for inclusion in the ESC [ m ANSI/ECMA-48 escape code for setting - graphical rendition. This field is optional. + A suggested presentation (foreground) text color when showing the OS name on the + console. This should be specified as string suitable for inclusion in the ESC [ m ANSI/ECMA-48 + escape code for setting graphical rendition. This field is optional. Examples: ANSI_COLOR="0;31" for red, ANSI_COLOR="1;34" for light blue, or ANSI_COLOR="0;38;2;60;110;180" for Fedora blue. + + ANSI_COLOR_REVERSE= + + Similar to ANSI_COLOR=, but should encode the desired + presentation color as background color, along with a suitable foreground color. This is may be used + by console applications to set off "chrome" UI elements from the main terminal contents. This field + is optional. + + + + VENDOR_NAME= diff --git a/src/shared/prompt-util.c b/src/shared/prompt-util.c index 42ddf191895..5c3fa98a922 100644 --- a/src/shared/prompt-util.c +++ b/src/shared/prompt-util.c @@ -1,10 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "alloc-util.h" #include "glyph-util.h" #include "log.h" #include "macro.h" +#include "os-util.h" #include "parse-util.h" +#include "pretty-print.h" #include "prompt-util.h" #include "string-util.h" #include "strv.h" @@ -189,3 +193,140 @@ int prompt_loop( } } } + +/* Default: bright white on blue background */ +#define ANSI_COLOR_CHROME "\x1B[0;44;1;37m" + +static unsigned chrome_visible = 0; /* if non-zero chrome is visible and value is saved number of lines */ + +int chrome_show( + const char *top, + const char *bottom) { + int r; + + assert(top); + + /* Shows our "chrome", i.e. a blue bar at top and bottom. Reduces the scrolling area to the area in + * between */ + + if (terminal_is_dumb()) + return 0; + + unsigned n = lines(); + if (n < 12) /* Do not bother with the chrom on tiny screens */ + return 0; + + _cleanup_free_ char *b = NULL, *ansi_color_reverse = NULL; + if (!bottom) { + _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL, *documentation_url = NULL; + + r = parse_os_release( + /* root= */ NULL, + "PRETTY_NAME", &pretty_name, + "NAME", &os_name, + "ANSI_COLOR", &ansi_color, + "ANSI_COLOR_REVERSE", &ansi_color_reverse, + "DOCUMENTATION_URL", &documentation_url); + if (r < 0) + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read os-release file, ignoring: %m"); + + const char *m = os_release_pretty_name(pretty_name, os_name); + const char *c = ansi_color ?: "0"; + + if (ansi_color_reverse) { + _cleanup_free_ char *j = strjoin("\x1B[0;", ansi_color_reverse, "m"); + if (!j) + return log_oom_debug(); + + free_and_replace(ansi_color_reverse, j); + } + + if (asprintf(&b, "\x1B[0;%sm %s %s", c, m, ansi_color_reverse ?: ANSI_COLOR_CHROME) < 0) + return log_oom_debug(); + + if (documentation_url) { + _cleanup_free_ char *u = NULL; + if (terminal_urlify(documentation_url, "documentation", &u) < 0) + return log_oom_debug(); + + if (!strextend(&b, " - See ", u, " for more information.")) + return log_oom_debug(); + } + + bottom = b; + } + + const char *chrome_color = ansi_color_reverse ?: ANSI_COLOR_CHROME; + + WITH_BUFFERED_STDOUT; + + fputs("\033[H" /* move home */ + "\033[2J", /* clear screen */ + stdout); + + /* Blue bar on top (followed by one empty regular one) */ + printf("\x1B[1;1H" /* jump to top left */ + "%1$s" ANSI_ERASE_TO_END_OF_LINE "\n" + "%1$s %2$s" ANSI_ERASE_TO_END_OF_LINE "\n" + "%1$s" ANSI_ERASE_TO_END_OF_LINE "\n" + ANSI_NORMAL ANSI_ERASE_TO_END_OF_LINE, + chrome_color, + top); + + /* Blue bar on bottom (with one empty regular one before) */ + printf("\x1B[%1$u;1H" /* jump to bottom left, above the blue bar */ + ANSI_NORMAL ANSI_ERASE_TO_END_OF_LINE "\n" + "%2$s" ANSI_ERASE_TO_END_OF_LINE "\n" + "%2$s %3$s" ANSI_ERASE_TO_END_OF_LINE "\n" + "%2$s" ANSI_ERASE_TO_END_OF_LINE ANSI_NORMAL, + n - 3, + chrome_color, + bottom); + + /* Reduce scrolling area (DECSTBM), cutting off top and bottom bars */ + printf("\x1B[5;%ur", n - 4); + + /* Position cursor in fifth line */ + fputs("\x1B[5;1H", stdout); + + fflush(stdout); + + chrome_visible = n; + return 1; +} + +void chrome_hide(void) { + int r; + + if (chrome_visible == 0) + return; + + unsigned n = chrome_visible; + chrome_visible = 0; + + unsigned saved_row = 0; + r = terminal_get_cursor_position(STDIN_FILENO, STDOUT_FILENO, &saved_row, /* ret_column= */ NULL); + if (r < 0) + return (void) log_debug_errno(r, "Failed to get terminal cursor position, skipping chrome hiding: %m"); + + WITH_BUFFERED_STDOUT; + + /* Erase Blue bar on bottom */ + assert(n >= 2); + printf("\x1B[%u;1H" + ANSI_NORMAL ANSI_ERASE_TO_END_OF_LINE "\n" + ANSI_NORMAL ANSI_ERASE_TO_END_OF_LINE "\n" + ANSI_NORMAL ANSI_ERASE_TO_END_OF_LINE ANSI_NORMAL, + n - 2); + + /* Reset scrolling area (DECSTBM) */ + fputs("\x1B[r\n", stdout); + + /* Place the cursor where it was again, but not in the former blue bars */ + assert(n >= 9); + unsigned k = CLAMP(saved_row, 5U, n - 4); + printf("\x1B[%u;1H", k); + + fflush(stdout); +} diff --git a/src/shared/prompt-util.h b/src/shared/prompt-util.h index 179f056b53f..06dd2c7f0c8 100644 --- a/src/shared/prompt-util.h +++ b/src/shared/prompt-util.h @@ -26,3 +26,6 @@ int prompt_loop(const char *text, void *userdata, PromptFlags flags, char **ret); + +int chrome_show(const char *top, const char *bottom); +void chrome_hide(void);