boot: work around ansi color issues between sd-boot, uefi and terminals

So, UEFI's color texting is a bit weird. It translates everything to
ANSI sequences, but unlike ANSI sequences it has no understanding of a
distinct "default" bg/fg color, it assumes the ansi color "0" is always
equal to white on black, but that's of course not really true, most
terminal emulators at the very least support white background too.

tianocore then also tries to be smart and suppresses ANSI color changes
from a color to itself. But if the understanding of the color is wrong
in the first place, then any color change suppression like this hurts
more than it helps.

Then in addition there are certain terminal tools that will reset the bg
color on every line break ("less" for example) to the default.

Let's deal with that and improve the situation on all fronts:

1. force out color changes by doing two color changes whenever we really
   want it.

2. on every newline force out the color change again.

with this in place, using sd-boot on a terminal emulator is a lot nicer.
This commit is contained in:
Lennart Poettering
2025-09-19 14:54:08 +02:00
parent d88ecd2bca
commit 8d36341cef
3 changed files with 46 additions and 3 deletions

View File

@@ -917,6 +917,37 @@ static bool handle_format_specifier(FormatContext *ctx, SpecifierContext *sp) {
}
}
#if SD_BOOT
static void output_string_safe(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *this, const char16_t *s) {
assert(this);
assert(s);
/* This is a color-conscious version of ST->ConOut->OutputString(). Whenever it encounters a newline
* character, it will reset the color to our default, because some UEFI implementations/terminals
* reset the color in that case, and we want our default color to remain in effect */
int32_t saved_attribute = ST->ConOut->Mode->Attribute;
for (;;) {
const char16_t *nl = strchr16(s, '\n');
if (!nl) /* No further newline */
return (void) ST->ConOut->OutputString(ST->ConOut, (char16_t*) s);
if (nl[1] == 0) { /* Newline is at the end of the string */
(void) ST->ConOut->OutputString(ST->ConOut, (char16_t*) s);
set_attribute_safe(saved_attribute);
return;
}
/* newline is in the middle of the string */
_cleanup_free_ char16_t *x = xstrndup16(s, nl - s + 1);
(void) ST->ConOut->OutputString(ST->ConOut, x);
set_attribute_safe(saved_attribute);
s = nl + 1;
}
}
#endif
/* printf_internal is largely compatible to userspace vasprintf. Any features omitted should trigger asserts.
*
* Supported:
@@ -983,7 +1014,7 @@ _printf_(2, 0) static char16_t *printf_internal(EFI_STATUS status, const char *f
}
#if SD_BOOT
ST->ConOut->OutputString(ST->ConOut, ctx.buf);
output_string_safe(ST->ConOut, ctx.buf);
#endif
return mfree(ctx.dyn_buf);

View File

@@ -195,16 +195,27 @@ EFI_STATUS file_read(
return file_handle_read(handle, offset, size, ret, ret_size);
}
void set_attribute_safe(size_t attr) {
/* Various UEFI implementations suppress color changes from a color to the same color. Often, we want
* to force out the color change though, hence change the color here once, and then back. We simply
* mark the color as bright for a moment, and then revert that. */
attr ^= 0x08;
ST->ConOut->SetAttribute(ST->ConOut, attr);
attr ^= 0x08;
ST->ConOut->SetAttribute(ST->ConOut, attr);
}
void print_at(size_t x, size_t y, size_t attr, const char16_t *str) {
assert(str);
ST->ConOut->SetCursorPosition(ST->ConOut, x, y);
ST->ConOut->SetAttribute(ST->ConOut, attr);
set_attribute_safe(attr);
ST->ConOut->OutputString(ST->ConOut, (char16_t *) str);
}
void clear_screen(size_t attr) {
log_wait();
ST->ConOut->SetAttribute(ST->ConOut, attr);
set_attribute_safe(attr);
ST->ConOut->ClearScreen(ST->ConOut);
}

View File

@@ -152,6 +152,7 @@ static inline void unload_imagep(EFI_HANDLE *image) {
#define GUID_FORMAT_VAL(g) (g).Data1, (g).Data2, (g).Data3, (g).Data4[0], (g).Data4[1], \
(g).Data4[2], (g).Data4[3], (g).Data4[4], (g).Data4[5], (g).Data4[6], (g).Data4[7]
void set_attribute_safe(size_t attr);
void print_at(size_t x, size_t y, size_t attr, const char16_t *str);
void clear_screen(size_t attr);