cosmetics for 'P' splash loop
[platform/upstream/gummiboot.git] / src / efi / gummiboot.c
index c4d22f3..90770c8 100644 (file)
@@ -1,3 +1,5 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
 /*
  * Simple UEFI boot loader which executes configured EFI images, where the
  * default entry is selected by a configured pattern (glob) or an on-screen
 #include <efi.h>
 #include <efilib.h>
 
+#include "util.h"
+#include "console.h"
+#include "graphics.h"
+
 #ifndef EFI_OS_INDICATIONS_BOOT_TO_FW_UI
 #define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001ULL
 #endif
 /* magic string to find in the binary image */
 static const char __attribute__((used)) magic[] = "#### LoaderInfo: gummiboot " VERSION " ####";
 
-/*
- * Allocated random UUID, intended to be shared across tools that implement
- * the (ESP)\loader\entries\<vendor>-<revision>.conf convention and the
- * associated EFI variables.
- */
-static const EFI_GUID loader_guid = { 0x4a67b082, 0x0a4c, 0x41cf, {0xb6, 0xc7, 0x44, 0x0b, 0x29, 0xbb, 0x8c, 0x4f} };
-
 static const EFI_GUID global_guid = EFI_GLOBAL_VARIABLE;
 
 enum loader_type {
@@ -62,6 +61,8 @@ typedef struct {
         enum loader_type type;
         CHAR16 *loader;
         CHAR16 *options;
+        CHAR16 *splash;
+        CHAR16 key;
         EFI_STATUS (*call)(void);
         BOOLEAN no_autoselect;
         BOOLEAN non_unique;
@@ -76,234 +77,13 @@ typedef struct {
         UINTN timeout_sec_config;
         INTN timeout_sec_efivar;
         CHAR16 *entry_default_pattern;
+        CHAR16 *splash;
+        EFI_GRAPHICS_OUTPUT_BLT_PIXEL *background;
         CHAR16 *entry_oneshot;
         CHAR16 *options_edit;
         CHAR16 *entries_auto;
 } Config;
 
-static CHAR16 *stra_to_str(CHAR8 *stra);
-
-#ifdef __x86_64__
-static UINT64 ticks_read(void) {
-        UINT64 a, d;
-        __asm__ volatile ("rdtsc" : "=a" (a), "=d" (d));
-        return (d << 32) | a;
-}
-
-static void cpuid_read(UINT32 info, UINT32 *eax, UINT32 *ebx, UINT32 *ecx, UINT32 *edx) {
-        *eax = info;
-        __asm__ volatile (
-                "mov %%ebx, %%edi;"
-                "cpuid;"
-                "mov %%ebx, %%esi;"
-                "mov %%edi, %%ebx;"
-                :"+a" (*eax), "=S" (*ebx), "=c" (*ecx), "=d" (*edx)
-                : :"edi"
-        );
-}
-
-static UINT64 cpufreq_read(void) {
-        UINT32 eax, ebx, ecx, edx;
-        union {
-                UINT32 i[3][4];
-                CHAR8 s[4 * 4 * 3 + 1];
-        } brand;
-        UINTN i;
-        CHAR8 *s;
-        UINT64 scale;
-        CHAR16 *str;
-        static UINT64 usec;
-
-        if (usec > 0)
-                return usec;
-
-        for (i = 0; i < 3; i++) {
-                cpuid_read(0x80000002 + i, &eax, &ebx, &ecx, &edx);
-                brand.i[i][0] = eax;
-                brand.i[i][1] = ebx;
-                brand.i[i][2] = ecx;
-                brand.i[i][3] = edx;
-        }
-        brand.s[4 * 4 * 3] = '\0';
-
-        /*
-         * Extract:
-         *   “x.xxyHz” or “xxxxyHz”, where y=M,G,T
-         * from CPUID brand string:
-         *   Intel(R) Core(TM) i5-2540M CPU @ 2.60GHz
-         *
-         * http://www.intel.com/content/dam/www/public/us/en/documents/
-         *   application-notes/processor-identification-cpuid-instruction-note.pdf
-         */
-        s = NULL;
-        for (i = 4; i < (4 * 4 * 3) - 2; i++) {
-                if (brand.s[i+1] == 'H' && brand.s[i+2] == 'z') {
-                        s = brand.s + i;
-                        break;
-                }
-        }
-        if (!s)
-                return 0;
-
-        scale = 1000;
-        switch(*s){
-        case 'T':
-                scale *= 1000;
-        case 'G':
-                scale *= 1000;
-        case 'M':
-                scale *= 1000;
-                break;
-        default:
-                return 0;
-        }
-
-        s -= 4;
-        s[4] = '\0';
-        if (s[1] == '.') {
-                s[1] = s[0];
-                s++;
-                scale /= 100;
-        }
-
-        str = stra_to_str(s);
-        usec = Atoi(str) * scale;
-        FreePool(str);
-        return usec;
-}
-
-static UINT64 time_usec(void) {
-        UINT64 ticks;
-        UINT64 cpufreq;
-
-        ticks = ticks_read();
-        if (ticks == 0)
-                return 0;
-
-        cpufreq = cpufreq_read();
-        if (cpufreq == 0)
-                return 0;
-
-        return 1000 * 1000 * ticks / cpufreq;
-}
-#else
-static UINT64 time_usec(void) { return 0; }
-#endif
-
-static EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 *buf, UINTN size, BOOLEAN persistent) {
-        UINT32 flags;
-
-        flags = EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS;
-        if (persistent)
-                flags |= EFI_VARIABLE_NON_VOLATILE;
-
-        return uefi_call_wrapper(RT->SetVariable, 5, name, (EFI_GUID *)vendor, flags, size, buf);
-}
-
-static EFI_STATUS efivar_set(CHAR16 *name, CHAR16 *value, BOOLEAN persistent) {
-        return efivar_set_raw(&loader_guid, name, (CHAR8 *)value, value ? (StrLen(value)+1) * sizeof(CHAR16) : 0, persistent);
-}
-
-static EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, CHAR16 *name, CHAR8 **buffer, UINTN *size) {
-        CHAR8 *buf;
-        UINTN l;
-        EFI_STATUS err;
-
-        l = sizeof(CHAR16 *) * EFI_MAXIMUM_VARIABLE_SIZE;
-        buf = AllocatePool(l);
-        if (!buf)
-                return EFI_OUT_OF_RESOURCES;
-
-        err = uefi_call_wrapper(RT->GetVariable, 5, name, (EFI_GUID *)vendor, NULL, &l, buf);
-        if (EFI_ERROR(err) == EFI_SUCCESS) {
-                *buffer = buf;
-                if (size)
-                        *size = l;
-        } else
-                FreePool(buf);
-        return err;
-
-}
-
-static EFI_STATUS efivar_get(CHAR16 *name, CHAR16 **value) {
-        CHAR8 *buf;
-        CHAR16 *val;
-        UINTN size;
-        EFI_STATUS err;
-
-        err = efivar_get_raw(&loader_guid, name, &buf, &size);
-        if (EFI_ERROR(err) != EFI_SUCCESS)
-                return err;
-
-        val = StrDuplicate((CHAR16 *)buf);
-        if (!val) {
-                FreePool(val);
-                return EFI_OUT_OF_RESOURCES;
-        }
-
-        *value = val;
-        return EFI_SUCCESS;
-}
-
-static EFI_STATUS efivar_set_int(CHAR16 *name, UINTN i, BOOLEAN persistent) {
-        CHAR16 str[32];
-
-        SPrint(str, 32, L"%d", i);
-        return efivar_set(name, str, persistent);
-}
-
-static EFI_STATUS efivar_get_int(CHAR16 *name, UINTN *i) {
-        CHAR16 *val;
-        EFI_STATUS err;
-
-        err = efivar_get(name, &val);
-        if (EFI_ERROR(err) == EFI_SUCCESS) {
-                *i = Atoi(val);
-                FreePool(val);
-        }
-        return err;
-}
-
-static VOID efivar_set_time_usec(CHAR16 *name, UINT64 usec) {
-        CHAR16 str[32];
-
-        if (usec == 0)
-                usec = time_usec();
-        if (usec == 0)
-                return;
-
-        SPrint(str, 32, L"%ld", usec);
-        efivar_set(name, str, FALSE);
-}
-
-/* remap EFI unicode and EFI scan code pair */
-#define CHAR_CTRL(c) ((c) - 'a' + 1)
-static void key_remap(EFI_INPUT_KEY *key, const EFI_INPUT_KEY *map) {
-        UINTN i;
-
-        for (i = 0; ; i += 2) {
-                 /* map unicode char to scan code*/
-                 if (map[i].UnicodeChar > 0) {
-                        if (key->UnicodeChar != map[i].UnicodeChar)
-                                continue;
-                        key->ScanCode = map[i+1].ScanCode ;
-                        key->UnicodeChar = 0;
-                        continue;
-                }
-
-                 /* map scan code to unicode char */
-                if (map[i].ScanCode > 0) {
-                        if (key->ScanCode != map[i].ScanCode)
-                                continue;
-                        key->ScanCode = 0 ;
-                        key->UnicodeChar = map[i+1].UnicodeChar;
-                        continue;
-                }
-
-                break;
-        }
-}
-
 static void cursor_left(UINTN *cursor, UINTN *first)
 {
         if ((*cursor) > 0)
@@ -327,14 +107,9 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
         UINTN first;
         CHAR16 *print;
         UINTN cursor;
+        UINTN clear;
         BOOLEAN exit;
         BOOLEAN enter;
-        static const EFI_INPUT_KEY keymap[] = {
-                { 0, CHAR_CTRL('a') }, { SCAN_HOME, 0 },
-                { 0, CHAR_CTRL('e') }, { SCAN_END,  0 },
-                { 0, CHAR_CTRL('f') }, { SCAN_DOWN, 0 },
-                { 0, CHAR_CTRL('b') }, { SCAN_UP,   0 }
-        };
 
         if (!line_in)
                 line_in = L"";
@@ -348,92 +123,165 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
 
         first = 0;
         cursor = 0;
+        clear = 0;
         enter = FALSE;
         exit = FALSE;
         while (!exit) {
-                UINTN index;
                 EFI_STATUS err;
-                EFI_INPUT_KEY key;
+                UINT64 key;
                 UINTN i;
 
                 i = len - first;
                 if (i >= x_max-1)
                         i = x_max-1;
                 CopyMem(print, line + first, i * sizeof(CHAR16));
-                print[i++] = ' ';
+                while (clear > 0 && i < x_max-1) {
+                        clear--;
+                        print[i++] = ' ';
+                }
                 print[i] = '\0';
 
                 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_pos);
                 uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, print);
                 uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
 
-                uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
-                err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
+                err = console_key_read(&key, TRUE);
                 if (EFI_ERROR(err))
                         continue;
 
-                key_remap(&key, keymap);
-
-                switch (key.ScanCode) {
-                case SCAN_ESC:
+                switch (key) {
+                case KEYPRESS(0, SCAN_ESC, 0):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'c'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'g'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('c')):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('g')):
                         exit = TRUE;
                         break;
-                case SCAN_HOME:
+
+                case KEYPRESS(0, SCAN_HOME, 0):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'a'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('a')):
+                        /* beginning-of-line */
                         cursor = 0;
                         first = 0;
                         continue;
-                case SCAN_END:
-                        cursor = len;
+
+                case KEYPRESS(0, SCAN_END, 0):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'e'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('e')):
+                        /* end-of-line */
+                        cursor = len - first;
                         if (cursor+1 >= x_max) {
                                 cursor = x_max-1;
                                 first = len - (x_max-1);
                         }
                         continue;
-                case SCAN_UP:
-                        while((first + cursor) && line[first + cursor] == ' ')
-                                cursor_left(&cursor, &first);
-                        while((first + cursor) && line[first + cursor] != ' ')
-                                cursor_left(&cursor, &first);
-                        while((first + cursor) && line[first + cursor] == ' ')
-                                cursor_left(&cursor, &first);
-                        if (first + cursor != len && first + cursor)
+
+                case KEYPRESS(0, SCAN_DOWN, 0):
+                case KEYPRESS(EFI_ALT_PRESSED, 0, 'f'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_RIGHT, 0):
+                        /* forward-word */
+                        while (line[first + cursor] && line[first + cursor] == ' ')
+                                cursor_right(&cursor, &first, x_max, len);
+                        while (line[first + cursor] && line[first + cursor] != ' ')
                                 cursor_right(&cursor, &first, x_max, len);
                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
                         continue;
-                case SCAN_DOWN:
-                        while(line[first + cursor] && line[first + cursor] == ' ')
-                                cursor_right(&cursor, &first, x_max, len);
-                        while(line[first + cursor] && line[first + cursor] != ' ')
-                                cursor_right(&cursor, &first, x_max, len);
-                        while(line[first + cursor] && line[first + cursor] == ' ')
-                                cursor_right(&cursor, &first, x_max, len);
+
+                case KEYPRESS(0, SCAN_UP, 0):
+                case KEYPRESS(EFI_ALT_PRESSED, 0, 'b'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, SCAN_LEFT, 0):
+                        /* backward-word */
+                        if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
+                                cursor_left(&cursor, &first);
+                                while ((first + cursor) > 0 && line[first + cursor] == ' ')
+                                        cursor_left(&cursor, &first);
+                        }
+                        while ((first + cursor) > 0 && line[first + cursor-1] != ' ')
+                                cursor_left(&cursor, &first);
                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
                         continue;
-                case SCAN_RIGHT:
+
+                case KEYPRESS(0, SCAN_RIGHT, 0):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'f'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('f')):
+                        /* forward-char */
                         if (first + cursor == len)
                                 continue;
                         cursor_right(&cursor, &first, x_max, len);
                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
                         continue;
-                case SCAN_LEFT:
+
+                case KEYPRESS(0, SCAN_LEFT, 0):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'b'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('b')):
+                        /* backward-char */
                         cursor_left(&cursor, &first);
                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
                         continue;
-                case SCAN_DELETE:
+
+                case KEYPRESS(EFI_ALT_PRESSED, 0, 'd'):
+                        /* kill-word */
+                        clear = 0;
+                        for (i = first + cursor; i < len && line[i] == ' '; i++)
+                                clear++;
+                        for (; i < len && line[i] != ' '; i++)
+                                clear++;
+
+                        for (i = first + cursor; i + clear < len; i++)
+                                line[i] = line[i + clear];
+                        len -= clear;
+                        line[len] = '\0';
+                        continue;
+
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'w'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('w')):
+                case KEYPRESS(EFI_ALT_PRESSED, 0, CHAR_BACKSPACE):
+                        /* backward-kill-word */
+                        clear = 0;
+                        if ((first + cursor) > 0 && line[first + cursor-1] == ' ') {
+                                cursor_left(&cursor, &first);
+                                clear++;
+                                while ((first + cursor) > 0 && line[first + cursor] == ' ') {
+                                        cursor_left(&cursor, &first);
+                                        clear++;
+                                }
+                        }
+                        while ((first + cursor) > 0 && line[first + cursor-1] != ' ') {
+                                cursor_left(&cursor, &first);
+                                clear++;
+                        }
+                        uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, cursor, y_pos);
+
+                        for (i = first + cursor; i + clear < len; i++)
+                                line[i] = line[i + clear];
+                        len -= clear;
+                        line[len] = '\0';
+                        continue;
+
+                case KEYPRESS(0, SCAN_DELETE, 0):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'd'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('d')):
                         if (len == 0)
                                 continue;
                         if (first + cursor == len)
                                 continue;
                         for (i = first + cursor; i < len; i++)
                                 line[i] = line[i+1];
-                        line[len-1] = ' ';
+                        clear = 1;
                         len--;
                         continue;
-                }
 
-                switch (key.UnicodeChar) {
-                case CHAR_LINEFEED:
-                case CHAR_CARRIAGE_RETURN:
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'k'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('k')):
+                        /* kill-line */
+                        line[first + cursor] = '\0';
+                        clear = len - (first + cursor);
+                        len = first + cursor;
+                        continue;
+
+                case KEYPRESS(0, 0, CHAR_LINEFEED):
+                case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN):
                         if (StrCmp(line, line_in) != 0) {
                                 *line_out = line;
                                 line = NULL;
@@ -441,13 +289,15 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
                         enter = TRUE;
                         exit = TRUE;
                         break;
-                case CHAR_BACKSPACE:
+
+                case KEYPRESS(0, 0, CHAR_BACKSPACE):
                         if (len == 0)
                                 continue;
                         if (first == 0 && cursor == 0)
                                 continue;
                         for (i = first + cursor-1; i < len; i++)
                                 line[i] = line[i+1];
+                        clear = 1;
                         len--;
                         if (cursor > 0)
                                 cursor--;
@@ -468,14 +318,14 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
                                 first = 0;
                         }
                         continue;
-                case '\t':
-                case ' ' ... '~':
-                case 0x80 ... 0xffff:
+
+                case KEYPRESS(0, 0, ' ') ... KEYPRESS(0, 0, '~'):
+                case KEYPRESS(0, 0, 0x80) ... KEYPRESS(0, 0, 0xffff):
                         if (len+1 == size)
                                 continue;
                         for (i = len; i > first + cursor; i--)
                                 line[i] = line[i-1];
-                        line[first + cursor] = key.UnicodeChar;
+                        line[first + cursor] = KEYCHAR(key);
                         len++;
                         line[len] = '\0';
                         if (cursor+1 < x_max)
@@ -492,17 +342,77 @@ static BOOLEAN line_edit(CHAR16 *line_in, CHAR16 **line_out, UINTN x_max, UINTN
         return enter;
 }
 
-static VOID dump_status(Config *config, CHAR16 *loaded_image_path) {
-        UINTN index;
-        EFI_INPUT_KEY key;
+static UINTN entry_lookup_key(Config *config, UINTN start, CHAR16 key) {
+        UINTN i;
+
+        if (key == 0)
+                return -1;
+
+        /* select entry by number key */
+        if (key >= '1' && key <= '9') {
+                i = key - '0';
+                if (i > config->entry_count)
+                        i = config->entry_count;
+                return i-1;
+        }
+
+        /* find matching key in config entries */
+        for (i = start; i < config->entry_count; i++)
+                if (config->entries[i]->key == key)
+                        return i;
+
+        for (i = 0; i < start; i++)
+                if (config->entries[i]->key == key)
+                        return i;
+
+        return -1;
+}
+
+static VOID print_status(Config *config, EFI_FILE *root_dir, CHAR16 *loaded_image_path) {
+        UINT64 key;
         UINTN i;
         CHAR16 *s;
         CHAR8 *b;
         UINTN size;
+        EFI_STATUS err;
+        UINTN color = 0;
+        const EFI_GRAPHICS_OUTPUT_BLT_PIXEL *pixel = config->background;
 
         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
         uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
 
+        /* show splash and wait for key */
+        for (;;) {
+                static const EFI_GRAPHICS_OUTPUT_BLT_PIXEL colors[] = {
+                        { .Red = 255, .Green = 255, .Blue = 255 },
+                        { .Red = 255, .Green =   0, .Blue =   0 },
+                        { .Red =   0, .Green = 255, .Blue =   0 },
+                        { .Red =   0, .Green =   0, .Blue = 255 },
+                        { .Red =   0, .Green =   0, .Blue =   0 },
+                };
+
+                err = EFI_NOT_FOUND;
+                if (config->splash)
+                        err = graphics_splash(root_dir, config->splash, pixel);
+                if (EFI_ERROR(err))
+                        err = graphics_splash(root_dir, L"\\EFI\\gummiboot\\splash.bmp", pixel);
+                if (EFI_ERROR(err))
+                        break;
+
+                /* 'b' rotates through background colors */
+                console_key_read(&key, TRUE);
+                if (key == KEYPRESS(0, 0, 'b')) {
+                        pixel = &colors[color++];
+                        if (color == ELEMENTSOF(colors))
+                                color = 0;
+
+                        continue;
+                }
+
+                graphics_mode(FALSE);
+                break;
+        }
+
         Print(L"gummiboot version:      " VERSION "\n");
         Print(L"loaded image:           %s\n", loaded_image_path);
         Print(L"UEFI version:           %d.%02d\n", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff);
@@ -530,6 +440,13 @@ static VOID dump_status(Config *config, CHAR16 *loaded_image_path) {
         Print(L"timeout (config):       %d\n", config->timeout_sec_config);
         if (config->entry_default_pattern)
                 Print(L"default pattern:        '%s'\n", config->entry_default_pattern);
+        if (config->splash)
+                Print(L"splash                  '%s'\n", config->splash);
+        if (config->background)
+                Print(L"background              '#%02x%02x%02x'\n",
+                      config->background->Red,
+                      config->background->Green,
+                      config->background->Blue);
         Print(L"\n");
 
         Print(L"config entry count:     %d\n", config->entry_count);
@@ -556,13 +473,24 @@ static VOID dump_status(Config *config, CHAR16 *loaded_image_path) {
         }
 
         Print(L"\n--- press key ---\n\n");
-        uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
-        uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
+        console_key_read(&key, TRUE);
 
         for (i = 0; i < config->entry_count; i++) {
                 ConfigEntry *entry;
 
+                if (key == KEYPRESS(0, SCAN_ESC, 0) || key == KEYPRESS(0, 0, 'q'))
+                        break;
+
                 entry = config->entries[i];
+
+                if (entry->splash) {
+                        err = graphics_splash(root_dir, entry->splash, config->background);
+                        if (!EFI_ERROR(err)) {
+                                console_key_read(&key, TRUE);
+                                graphics_mode(FALSE);
+                        }
+                }
+
                 Print(L"config entry:           %d/%d\n", i+1, config->entry_count);
                 if (entry->file)
                         Print(L"file                    '%s'\n", entry->file);
@@ -588,65 +516,20 @@ static VOID dump_status(Config *config, CHAR16 *loaded_image_path) {
                         Print(L"loader                  '%s'\n", entry->loader);
                 if (entry->options)
                         Print(L"options                 '%s'\n", entry->options);
+                if (entry->splash)
+                        Print(L"splash                  '%s'\n", entry->splash);
                 Print(L"auto-select             %s\n", entry->no_autoselect ? L"no" : L"yes");
                 if (entry->call)
                         Print(L"internal call           yes\n");
+
                 Print(L"\n--- press key ---\n\n");
-                uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
-                uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
-                if (key.ScanCode == SCAN_ESC)
-                        break;
+                console_key_read(&key, TRUE);
         }
 
         uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut);
 }
 
-static EFI_STATUS console_text_mode(VOID) {
-        #define EFI_CONSOLE_CONTROL_PROTOCOL_GUID \
-                { 0xf42f7782, 0x12e, 0x4c12, { 0x99, 0x56, 0x49, 0xf9, 0x43, 0x4, 0xf7, 0x21 }};
-
-        struct _EFI_CONSOLE_CONTROL_PROTOCOL;
-
-        typedef enum {
-                EfiConsoleControlScreenText,
-                EfiConsoleControlScreenGraphics,
-                EfiConsoleControlScreenMaxValue,
-        } EFI_CONSOLE_CONTROL_SCREEN_MODE;
-
-        typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE)(
-                struct _EFI_CONSOLE_CONTROL_PROTOCOL *This,
-                EFI_CONSOLE_CONTROL_SCREEN_MODE *Mode,
-                BOOLEAN *UgaExists,
-                BOOLEAN *StdInLocked
-        );
-
-        typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE)(
-                struct _EFI_CONSOLE_CONTROL_PROTOCOL *This,
-                EFI_CONSOLE_CONTROL_SCREEN_MODE Mode
-        );
-
-        typedef EFI_STATUS (EFIAPI *EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN)(
-                struct _EFI_CONSOLE_CONTROL_PROTOCOL *This,
-                CHAR16 *Password
-        );
-
-        typedef struct _EFI_CONSOLE_CONTROL_PROTOCOL {
-                EFI_CONSOLE_CONTROL_PROTOCOL_GET_MODE GetMode;
-                EFI_CONSOLE_CONTROL_PROTOCOL_SET_MODE SetMode;
-                EFI_CONSOLE_CONTROL_PROTOCOL_LOCK_STD_IN LockStdIn;
-        } EFI_CONSOLE_CONTROL_PROTOCOL;
-
-        EFI_GUID ConsoleControlProtocolGuid = EFI_CONSOLE_CONTROL_PROTOCOL_GUID;
-        EFI_CONSOLE_CONTROL_PROTOCOL *ConsoleControl = NULL;
-        EFI_STATUS err;
-
-        err = LibLocateProtocol(&ConsoleControlProtocolGuid, (VOID **)&ConsoleControl);
-        if (EFI_ERROR(err))
-                return err;
-        return uefi_call_wrapper(ConsoleControl->SetMode, 2, ConsoleControl, EfiConsoleControlScreenText);
-}
-
-static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *loaded_image_path) {
+static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, EFI_FILE *root_dir, CHAR16 *loaded_image_path) {
         EFI_STATUS err;
         UINTN visible_max;
         UINTN idx_highlight;
@@ -665,16 +548,12 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
         CHAR16 *status;
         CHAR16 *clearline;
         INTN timeout_remain;
+        INT16 idx;
         BOOLEAN exit = FALSE;
         BOOLEAN run = TRUE;
-        static const EFI_INPUT_KEY keymap[] = {
-                { 0,      'j' }, { SCAN_DOWN,  0 },
-                { 0,      'k' }, { SCAN_UP,    0 },
-                { SCAN_F1,  0 }, { 0,        'h' },
-                { 0,        0 }, { 0,          0 }
-        };
-
-        console_text_mode();
+        BOOLEAN wait = FALSE;
+
+        graphics_mode(FALSE);
         uefi_call_wrapper(ST->ConIn->Reset, 2, ST->ConIn, FALSE);
         uefi_call_wrapper(ST->ConOut->EnableCursor, 2, ST->ConOut, FALSE);
         uefi_call_wrapper(ST->ConOut->SetAttribute, 2, ST->ConOut, EFI_LIGHTGRAY|EFI_BACKGROUND_BLACK);
@@ -750,7 +629,7 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
         clearline[i] = 0;
 
         while (!exit) {
-                EFI_INPUT_KEY key;
+                UINT64 key;
 
                 if (refresh) {
                         for (i = 0; i < config->entry_count; i++) {
@@ -812,22 +691,26 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1 + x + len);
                 }
 
-                err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
-                if (err != EFI_SUCCESS) {
-                        UINTN index;
-
+                err = console_key_read(&key, wait);
+                if (EFI_ERROR(err)) {
+                        /* timeout reached */
                         if (timeout_remain == 0) {
                                 exit = TRUE;
                                 break;
                         }
+
+                        /* sleep and update status */
                         if (timeout_remain > 0) {
                                 uefi_call_wrapper(BS->Stall, 1, 100 * 1000);
                                 timeout_remain--;
                                 continue;
                         }
-                        uefi_call_wrapper(BS->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);
+
+                        /* timeout disabled, wait for next key */
+                        wait = TRUE;
                         continue;
                 }
+
                 timeout_remain = -1;
 
                 /* clear status after keystroke */
@@ -841,67 +724,65 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
 
                 idx_highlight_prev = idx_highlight;
 
-                key_remap(&key, keymap);
-
-                switch (key.ScanCode) {
-                case SCAN_UP:
+                switch (key) {
+                case KEYPRESS(0, SCAN_UP, 0):
+                case KEYPRESS(0, 0, 'k'):
                         if (idx_highlight > 0)
                                 idx_highlight--;
                         break;
-                case SCAN_DOWN:
+
+                case KEYPRESS(0, SCAN_DOWN, 0):
+                case KEYPRESS(0, 0, 'j'):
                         if (idx_highlight < config->entry_count-1)
                                 idx_highlight++;
                         break;
-                case SCAN_HOME:
+
+                case KEYPRESS(0, SCAN_HOME, 0):
+                case KEYPRESS(EFI_ALT_PRESSED, 0, '<'):
                         if (idx_highlight > 0) {
                                 refresh = TRUE;
                                 idx_highlight = 0;
                         }
                         break;
-                case SCAN_END:
+
+                case KEYPRESS(0, SCAN_END, 0):
+                case KEYPRESS(EFI_ALT_PRESSED, 0, '>'):
                         if (idx_highlight < config->entry_count-1) {
                                 refresh = TRUE;
                                 idx_highlight = config->entry_count-1;
                         }
                         break;
-                case SCAN_PAGE_UP:
+
+                case KEYPRESS(0, SCAN_PAGE_UP, 0):
                         if (idx_highlight > visible_max)
                                 idx_highlight -= visible_max;
                         else
                                 idx_highlight = 0;
                         break;
-                case SCAN_PAGE_DOWN:
+
+                case KEYPRESS(0, SCAN_PAGE_DOWN, 0):
                         idx_highlight += visible_max;
                         if (idx_highlight > config->entry_count-1)
                                 idx_highlight = config->entry_count-1;
                         break;
-                }
 
-                if (idx_highlight > idx_last) {
-                        idx_last = idx_highlight;
-                        idx_first = 1 + idx_highlight - visible_max;
-                        refresh = TRUE;
-                }
-                if (idx_highlight < idx_first) {
-                        idx_first = idx_highlight;
-                        idx_last = idx_highlight + visible_max-1;
-                        refresh = TRUE;
-                }
-                idx_last = idx_first + visible_max-1;
-
-                switch (key.UnicodeChar) {
-                case CHAR_LINEFEED:
-                case CHAR_CARRIAGE_RETURN:
+                case KEYPRESS(0, 0, CHAR_LINEFEED):
+                case KEYPRESS(0, 0, CHAR_CARRIAGE_RETURN):
                         exit = TRUE;
                         break;
-                case 'h':
-                        status = StrDuplicate(L"(d)efault, (+/-)timeout, (e)dit, (v)ersion (q)uit (*)dump (h)elp");
+
+                case KEYPRESS(0, SCAN_F1, 0):
+                case KEYPRESS(0, 0, 'h'):
+                case KEYPRESS(0, 0, '?'):
+                        status = StrDuplicate(L"(d)efault, (t/T)timeout, (e)dit, (v)ersion (Q)uit (P)rint (h)elp");
                         break;
-                case 'q':
+
+                case KEYPRESS(0, 0, 'Q'):
                         exit = TRUE;
                         run = FALSE;
                         break;
-                case 'd':
+
+                case KEYPRESS(0, 0, 'd'):
                         if (config->idx_default_efivar != (INTN)idx_highlight) {
                                 /* store the selected entry in a persistent EFI variable */
                                 efivar_set(L"LoaderEntryDefault", config->entries[idx_highlight]->file, TRUE);
@@ -915,7 +796,9 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
                         }
                         refresh = TRUE;
                         break;
-                case '-':
+
+                case KEYPRESS(0, 0, '-'):
+                case KEYPRESS(0, 0, 'T'):
                         if (config->timeout_sec_efivar > 0) {
                                 config->timeout_sec_efivar--;
                                 efivar_set_int(L"LoaderConfigTimeout", config->timeout_sec_efivar, TRUE);
@@ -933,7 +816,9 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
                                         status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
                         }
                         break;
-                case '+':
+
+                case KEYPRESS(0, 0, '+'):
+                case KEYPRESS(0, 0, 't'):
                         if (config->timeout_sec_efivar == -1 && config->timeout_sec_config == 0)
                                 config->timeout_sec_efivar++;
                         config->timeout_sec_efivar++;
@@ -944,7 +829,8 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
                         else
                                 status = StrDuplicate(L"Menu disabled. Hold down key at bootup to show menu.");
                         break;
-                case 'e':
+
+                case KEYPRESS(0, 0, 'e'):
                         /* only the options of configured entries can be edited */
                         if (config->entries[idx_highlight]->type == LOADER_UNDEFINED)
                                 break;
@@ -956,17 +842,45 @@ static BOOLEAN menu_run(Config *config, ConfigEntry **chosen_entry, CHAR16 *load
                         uefi_call_wrapper(ST->ConOut->SetCursorPosition, 3, ST->ConOut, 0, y_max-1);
                         uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, clearline+1);
                         break;
-                case 'v':
+
+                case KEYPRESS(0, 0, 'v'):
                         status = PoolPrint(L"gummiboot " VERSION ", UEFI %d.%02d, %s %d.%02d",
                                            ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff,
                                            ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff);
                         break;
-                case '*':
-                        dump_status(config, loaded_image_path);
+
+                case KEYPRESS(0, 0, 'P'):
+                        print_status(config, root_dir, loaded_image_path);
+                        refresh = TRUE;
+                        break;
+
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, 'l'):
+                case KEYPRESS(EFI_CONTROL_PRESSED, 0, CHAR_CTRL('l')):
                         refresh = TRUE;
                         break;
+
+                default:
+                        /* jump with a hotkey directly to a matching entry */
+                        idx = entry_lookup_key(config, idx_highlight+1, KEYCHAR(key));
+                        if (idx < 0)
+                                break;
+                        idx_highlight = idx;
+                        refresh = TRUE;
+                }
+
+                if (idx_highlight > idx_last) {
+                        idx_last = idx_highlight;
+                        idx_first = 1 + idx_highlight - visible_max;
+                        refresh = TRUE;
+                }
+                if (idx_highlight < idx_first) {
+                        idx_first = idx_highlight;
+                        idx_last = idx_highlight + visible_max-1;
+                        refresh = TRUE;
                 }
 
+                idx_last = idx_first + visible_max-1;
+
                 if (!refresh && idx_highlight != idx_highlight_prev)
                         highlight = TRUE;
         }
@@ -1065,131 +979,6 @@ static INTN str_verscmp(CHAR16 *s1, CHAR16 *s2)
         return StrCmp(os1, os2);
 }
 
-static INTN utf8_to_16(CHAR8 *stra, CHAR16 *c) {
-        CHAR16 unichar;
-        UINTN len;
-        UINTN i;
-
-        if (stra[0] < 0x80)
-                len = 1;
-        else if ((stra[0] & 0xe0) == 0xc0)
-                len = 2;
-        else if ((stra[0] & 0xf0) == 0xe0)
-                len = 3;
-        else if ((stra[0] & 0xf8) == 0xf0)
-                len = 4;
-        else if ((stra[0] & 0xfc) == 0xf8)
-                len = 5;
-        else if ((stra[0] & 0xfe) == 0xfc)
-                len = 6;
-        else
-                return -1;
-
-        switch (len) {
-        case 1:
-                unichar = stra[0];
-                break;
-        case 2:
-                unichar = stra[0] & 0x1f;
-                break;
-        case 3:
-                unichar = stra[0] & 0x0f;
-                break;
-        case 4:
-                unichar = stra[0] & 0x07;
-                break;
-        case 5:
-                unichar = stra[0] & 0x03;
-                break;
-        case 6:
-                unichar = stra[0] & 0x01;
-                break;
-        }
-
-        for (i = 1; i < len; i++) {
-                if ((stra[i] & 0xc0) != 0x80)
-                        return -1;
-                unichar <<= 6;
-                unichar |= stra[i] & 0x3f;
-        }
-
-        *c = unichar;
-        return len;
-}
-
-static CHAR16 *stra_to_str(CHAR8 *stra) {
-        UINTN strlen;
-        UINTN len;
-        UINTN i;
-        CHAR16 *str;
-
-        len = strlena(stra);
-        str = AllocatePool((len + 1) * sizeof(CHAR16));
-
-        strlen = 0;
-        i = 0;
-        while (i < len) {
-                INTN utf8len;
-
-                utf8len = utf8_to_16(stra + i, str + strlen);
-                if (utf8len <= 0) {
-                        /* invalid utf8 sequence, skip the garbage */
-                        i++;
-                        continue;
-                }
-
-                strlen++;
-                i += utf8len;
-        }
-        str[strlen] = '\0';
-        return str;
-}
-
-static CHAR16 *stra_to_path(CHAR8 *stra) {
-        CHAR16 *str;
-        UINTN strlen;
-        UINTN len;
-        UINTN i;
-
-        len = strlena(stra);
-        str = AllocatePool((len + 2) * sizeof(CHAR16));
-
-        str[0] = '\\';
-        strlen = 1;
-        i = 0;
-        while (i < len) {
-                INTN utf8len;
-
-                utf8len = utf8_to_16(stra + i, str + strlen);
-                if (utf8len <= 0) {
-                        /* invalid utf8 sequence, skip the garbage */
-                        i++;
-                        continue;
-                }
-
-                if (str[strlen] == '/')
-                        str[strlen] = '\\';
-                if (str[strlen] == '\\' && str[strlen-1] == '\\') {
-                        /* skip double slashes */
-                        i += utf8len;
-                        continue;
-                }
-
-                strlen++;
-                i += utf8len;
-        }
-        str[strlen] = '\0';
-        return str;
-}
-
-static CHAR8 *strchra(CHAR8 *s, CHAR8 c) {
-        do {
-                if (*s == c)
-                        return s;
-        } while (*s++);
-        return NULL;
-}
-
 static CHAR8 *line_get_key_value(CHAR8 *content, UINTN *pos, CHAR8 **key_ret, CHAR8 **value_ret) {
         CHAR8 *line;
         UINTN linelen;
@@ -1262,11 +1051,45 @@ static VOID config_defaults_load_from_file(Config *config, CHAR8 *content) {
                         FreePool(s);
                         continue;
                 }
+
                 if (strcmpa((CHAR8 *)"default", key) == 0) {
                         config->entry_default_pattern = stra_to_str(value);
                         StrLwr(config->entry_default_pattern);
                         continue;
                 }
+
+                if (strcmpa((CHAR8 *)"splash", key) == 0) {
+                        config->splash = stra_to_path(value);
+                        continue;
+                }
+
+                if (strcmpa((CHAR8 *)"background", key) == 0) {
+                        CHAR16 c[3];
+
+                        /* accept #RRGGBB hex notation */
+                        if (value[0] != '#')
+                                continue;
+                        if (value[7] != '\0')
+                                continue;
+
+                        config->background = AllocateZeroPool(sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
+                        if (!config->background)
+                                continue;
+
+                        c[0] = value[1];
+                        c[1] = value[2];
+                        c[2] = '\0';
+                        config->background->Red = xtoi(c);
+
+                        c[0] = value[3];
+                        c[1] = value[4];
+                        config->background->Green = xtoi(c);
+
+                        c[0] = value[5];
+                        c[1] = value[6];
+                        config->background->Blue = xtoi(c);
+                        continue;
+                }
         }
 }
 
@@ -1304,6 +1127,7 @@ static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR1
                         FreePool(entry->loader);
                         entry->type = LOADER_LINUX;
                         entry->loader = stra_to_path(value);
+                        entry->key = 'l';
                         continue;
                 }
 
@@ -1353,6 +1177,12 @@ static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR1
                         FreePool(new);
                         continue;
                 }
+
+                if (strcmpa((CHAR8 *)"splash", key) == 0) {
+                        FreePool(entry->splash);
+                        entry->splash = stra_to_path(value);
+                        continue;
+                }
         }
 
         if (entry->type == LOADER_UNDEFINED) {
@@ -1428,36 +1258,6 @@ static VOID config_entry_add_from_file(Config *config, EFI_HANDLE *device, CHAR1
         config_add_entry(config, entry);
 }
 
-static UINTN file_read(EFI_FILE_HANDLE dir, CHAR16 *name, CHAR8 **content) {
-        EFI_FILE_HANDLE handle;
-        EFI_FILE_INFO *info;
-        CHAR8 *buf;
-        UINTN buflen;
-        EFI_STATUS err;
-        UINTN len = 0;
-
-        err = uefi_call_wrapper(dir->Open, 5, dir, &handle, name, EFI_FILE_MODE_READ, 0ULL);
-        if (EFI_ERROR(err))
-                goto out;
-
-        info = LibFileInfo(handle);
-        buflen = info->FileSize+1;
-        buf = AllocatePool(buflen);
-
-        err = uefi_call_wrapper(handle->Read, 3, handle, &buflen, buf);
-        if (EFI_ERROR(err) == EFI_SUCCESS) {
-                buf[buflen] = '\0';
-                *content = buf;
-                len = buflen;
-        } else
-                FreePool(buf);
-
-        FreePool(info);
-        uefi_call_wrapper(handle->Close, 1, handle);
-out:
-        return len;
-}
-
 static VOID config_load(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path) {
         EFI_FILE_HANDLE entries_dir;
         EFI_STATUS err;
@@ -1472,14 +1272,14 @@ static VOID config_load(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir,
         FreePool(content);
 
         err = efivar_get_int(L"LoaderConfigTimeout", &sec);
-        if (EFI_ERROR(err) == EFI_SUCCESS) {
+        if (!EFI_ERROR(err)) {
                 config->timeout_sec_efivar = sec;
                 config->timeout_sec = sec;
         } else
                 config->timeout_sec_efivar = -1;
 
         err = uefi_call_wrapper(root_dir->Open, 5, root_dir, &entries_dir, L"\\loader\\entries", EFI_FILE_MODE_READ, 0ULL);
-        if (EFI_ERROR(err) == EFI_SUCCESS) {
+        if (!EFI_ERROR(err)) {
                 for (;;) {
                         CHAR16 buf[256];
                         UINTN bufsize;
@@ -1542,7 +1342,7 @@ static VOID config_default_entry_select(Config *config) {
          * next reboot. The variable is always cleared directly after it is read.
          */
         err = efivar_get(L"LoaderEntryOneShot", &var);
-        if (EFI_ERROR(err) == EFI_SUCCESS) {
+        if (!EFI_ERROR(err)) {
                 BOOLEAN found = FALSE;
 
                 for (i = 0; i < config->entry_count; i++) {
@@ -1567,7 +1367,7 @@ static VOID config_default_entry_select(Config *config) {
          * an '*'.
          */
         err = efivar_get(L"LoaderEntryDefault", &var);
-        if (EFI_ERROR(err) == EFI_SUCCESS) {
+        if (!EFI_ERROR(err)) {
                 BOOLEAN found = FALSE;
 
                 for (i = 0; i < config->entry_count; i++) {
@@ -1739,7 +1539,7 @@ static BOOLEAN config_entry_add_call(Config *config, CHAR16 *title, EFI_STATUS (
 }
 
 static BOOLEAN config_entry_add_loader(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
-                                       CHAR16 *file, CHAR16 *title, CHAR16 *loader) {
+                                       CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
         EFI_FILE_HANDLE handle;
         EFI_STATUS err;
         ConfigEntry *entry;
@@ -1760,15 +1560,22 @@ static BOOLEAN config_entry_add_loader(Config *config, EFI_HANDLE *device, EFI_F
         entry->loader = StrDuplicate(loader);
         entry->file = StrDuplicate(file);
         StrLwr(entry->file);
-        entry->no_autoselect = TRUE;
+        entry->key = key;
         config_add_entry(config, entry);
+
+        /* do not boot right away into aut-detected entries */
+        entry->no_autoselect = TRUE;
+
+        /* do not show a splash; they do not need one, or they draw their own */
+        entry->splash = StrDuplicate(L"");
+
         return TRUE;
 }
 
-static VOID config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
-                                         CHAR16 *file, CHAR16 *title, CHAR16 *loader) {
-        if (!config_entry_add_loader(config, device, root_dir, loaded_image_path, file, title, loader))
-                return;
+static BOOLEAN config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI_FILE *root_dir, CHAR16 *loaded_image_path,
+                                         CHAR16 *file, CHAR16 key, CHAR16 *title, CHAR16 *loader) {
+        if (!config_entry_add_loader(config, device, root_dir, loaded_image_path, file, key, title, loader))
+                return FALSE;
 
         /* export identifiers of automatically added entries */
         if (config->entries_auto) {
@@ -1779,6 +1586,8 @@ static VOID config_entry_add_loader_auto(Config *config, EFI_HANDLE *device, EFI
                 config->entries_auto = s;
         } else
                 config->entries_auto = StrDuplicate(file);
+
+        return TRUE;
 }
 
 static VOID config_entry_add_osx(Config *config) {
@@ -1787,18 +1596,21 @@ static VOID config_entry_add_osx(Config *config) {
         EFI_HANDLE *handles = NULL;
 
         err = LibLocateHandle(ByProtocol, &FileSystemProtocol, NULL, &handle_count, &handles);
-        if (EFI_ERROR(err) == EFI_SUCCESS) {
+        if (!EFI_ERROR(err)) {
                 UINTN i;
 
                 for (i = 0; i < handle_count; i++) {
                         EFI_FILE *root;
+                        BOOLEAN found;
 
                         root = LibOpenRoot(handles[i]);
                         if (!root)
                                 continue;
-                        config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", L"OS X",
-                                                     L"\\System\\Library\\CoreServices\\boot.efi");
+                        found = config_entry_add_loader_auto(config, handles[i], root, NULL, L"auto-osx", 'a', L"OS X",
+                                                             L"\\System\\Library\\CoreServices\\boot.efi");
                         uefi_call_wrapper(root->Close, 1, root);
+                        if (found)
+                                break;
                 }
 
                 FreePool(handles);
@@ -1863,12 +1675,12 @@ static EFI_STATUS reboot_into_firmware(VOID) {
         osind = EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
 
         err = efivar_get_raw(&global_guid, L"OsIndications", &b, &size);
-        if (err == EFI_SUCCESS)
+        if (!EFI_ERROR(err))
                 osind |= (UINT64)*b;
         FreePool(b);
 
         err = efivar_set_raw(&global_guid, L"OsIndications", (CHAR8 *)&osind, sizeof(UINT64), TRUE);
-        if (err != EFI_SUCCESS)
+        if (EFI_ERROR(err))
                 return err;
 
         err = uefi_call_wrapper(RT->ResetSystem, 4, EfiResetCold, EFI_SUCCESS, 0, NULL);
@@ -1887,6 +1699,8 @@ static VOID config_free(Config *config) {
         FreePool(config->options_edit);
         FreePool(config->entry_oneshot);
         FreePool(config->entries_auto);
+        FreePool(config->splash);
+        FreePool(config->background);
 }
 
 EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
@@ -1968,11 +1782,11 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
 
         /* if we find some well-known loaders, add them to the end of the list */
         config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
-                                     L"auto-windows", L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
+                                     L"auto-windows", 'w', L"Windows Boot Manager", L"\\EFI\\Microsoft\\Boot\\bootmgfw.efi");
         config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
-                                     L"auto-efi-shell", L"EFI Shell", L"\\shellx64.efi");
+                                     L"auto-efi-shell", 's', L"EFI Shell", L"\\shell" MACHINE_TYPE_NAME ".efi");
         config_entry_add_loader_auto(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path,
-                                     L"auto-efi-default", L"EFI Default Loader", L"\\EFI\\BOOT\\BOOTX64.EFI");
+                                     L"auto-efi-default", '\0', L"EFI Default Loader", L"\\EFI\\Boot\\boot" MACHINE_TYPE_NAME ".efi");
         config_entry_add_osx(&config);
         efivar_set(L"LoaderEntriesAuto", config.entries_auto, FALSE);
 
@@ -2002,13 +1816,21 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
                         config.timeout_sec = 10;
         }
 
-        /* show menu when key is pressed or timeout is set */
+        /* select entry or show menu when key is pressed or timeout is set */
         if (config.timeout_sec == 0) {
-                EFI_INPUT_KEY key;
+                UINT64 key;
 
-                err = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &key);
-                if (err == EFI_SUCCESS)
-                        menu = TRUE;
+                err = console_key_read(&key, FALSE);
+                if (!EFI_ERROR(err)) {
+                        INT16 idx;
+
+                        /* find matching key in config entries */
+                        idx = entry_lookup_key(&config, config.idx_default, KEYCHAR(key));
+                        if (idx >= 0)
+                                config.idx_default = idx;
+                        else
+                                menu = TRUE;
+                }
         } else
                 menu = TRUE;
 
@@ -2019,7 +1841,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
                 if (menu) {
                         efivar_set_time_usec(L"LoaderTimeMenuUSec", 0);
                         uefi_call_wrapper(BS->SetWatchdogTimer, 4, 0, 0x10000, 0, NULL);
-                        if (!menu_run(&config, &entry, loaded_image_path))
+                        if (!menu_run(&config, &entry, root_dir, loaded_image_path))
                                 break;
 
                         /* run special entry like "reboot" */
@@ -2027,6 +1849,25 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
                                 entry->call();
                                 continue;
                         }
+                } else {
+                        err = EFI_NOT_FOUND;
+
+                        /* splash from entry file */
+                        if (entry->splash) {
+                                /* some entries disable the splash because they draw their own */
+                                if (entry->splash[0] == '\0')
+                                        err = EFI_SUCCESS;
+                                else
+                                        err = graphics_splash(root_dir, entry->splash, config.background);
+                        }
+
+                        /* splash from config file */
+                        if (EFI_ERROR(err) && config.splash)
+                                err = graphics_splash(root_dir, config.splash, config.background);
+
+                        /* default splash */
+                        if (EFI_ERROR(err))
+                                graphics_splash(root_dir, L"\\EFI\\gummiboot\\splash.bmp", config.background);
                 }
 
                 /* export the selected boot entry to the system */