]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
boot: work around ansi color issues between sd-boot, uefi and terminals
authorLennart Poettering <lennart@poettering.net>
Fri, 19 Sep 2025 12:54:08 +0000 (14:54 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 24 Sep 2025 10:33:59 +0000 (12:33 +0200)
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.

src/boot/efi-string.c
src/boot/util.c
src/boot/util.h

index 8e2c73d84d68882fcbd115ca9cc135af6e623edf..f051b5dd8877b93862245f95fa49c87b7d70b32a 100644 (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);
index 5d439430b8de97473b63511ea94c222e06e05467..92da63fc0ff2c969ddbfad9ec2c73fc1b57e2e45 100644 (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);
 }
 
index e7a3ba03033cc59432eee23ff9a29966ef36949e..ef017f45fa03ea193c0152fcf7cbaa20d3178569 100644 (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);