feat: add support for fractional timeouts.
diff --git a/CONFIG.md b/CONFIG.md
index 4e5182e8..7d6bb473 100644
--- a/CONFIG.md
+++ b/CONFIG.md
@@ -75,8 +75,9 @@ Some options take *paths* as strings; these are described in the next section.
Miscellaneous:
* `timeout` - Specifies the timeout in seconds before the first *entry* is
- automatically booted. If set to `no`, disable automatic boot. If set to `0`,
- boots default entry instantly (see `default_entry` option).
+ automatically booted. Decimal values such as `0.25` are accepted. If set to
+ `no`, disable automatic boot. If set to `0`, boots default entry instantly
+ (see `default_entry` option).
* `quiet` - If set to `yes`, enable quiet mode, where all screen output except
panics and important warnings is suppressed. If `timeout` is not 0, the
`timeout` still occurs, and pressing any key during the timeout will reveal
diff --git a/common/lib/getchar.c b/common/lib/getchar.c
index b3864fb4..a6a10a0e 100644
--- a/common/lib/getchar.c
+++ b/common/lib/getchar.c
@@ -156,12 +156,21 @@ static int input_sequence(void) {
return 0;
}
-int pit_sleep_and_quit_on_keypress(int seconds) {
+int pit_sleep_ms_and_quit_on_keypress(uint64_t milliseconds) {
+ uint64_t ticks64 = milliseconds > (UINT64_MAX - 999) / 18
+ ? UINT64_MAX
+ : (milliseconds * 18 + 999) / 1000;
+ uint32_t ticks = ticks64 > UINT32_MAX ? UINT32_MAX : ticks64;
+
+ if (ticks == 0) {
+ return 0;
+ }
+
if (!serial) {
- return _pit_sleep_and_quit_on_keypress(seconds * 18);
+ return _pit_sleep_and_quit_on_keypress(ticks);
}
- for (int i = 0; i < seconds * 18; i++) {
+ for (uint32_t i = 0; i < ticks; i++) {
int ret = _pit_sleep_and_quit_on_keypress(1);
if (ret != 0) {
@@ -195,6 +204,10 @@ again:
return 0;
}
+
+int pit_sleep_and_quit_on_keypress(int seconds) {
+ return pit_sleep_ms_and_quit_on_keypress((uint64_t)seconds * 1000);
+}
#endif
#if defined (UEFI)
@@ -254,7 +267,7 @@ static int input_sequence(bool ext,
return 0;
}
-int pit_sleep_and_quit_on_keypress(int seconds) {
+int pit_sleep_ms_and_quit_on_keypress(uint64_t milliseconds) {
EFI_KEY_DATA kd;
UINTN which;
@@ -287,7 +300,8 @@ int pit_sleep_and_quit_on_keypress(int seconds) {
restart:
gBS->CreateEvent(EVT_TIMER, TPL_CALLBACK, NULL, NULL, &events[1]);
- gBS->SetTimer(events[1], TimerRelative, (uint64_t)10000000 * seconds);
+ gBS->SetTimer(events[1], TimerRelative,
+ milliseconds > UINT64_MAX / 10000 ? UINT64_MAX : milliseconds * 10000);
again:
memset(&kd, 0, sizeof(EFI_KEY_DATA));
@@ -362,4 +376,8 @@ again:
gBS->CloseEvent(events[1]);
return ret;
}
+
+int pit_sleep_and_quit_on_keypress(int seconds) {
+ return pit_sleep_ms_and_quit_on_keypress((uint64_t)seconds * 1000);
+}
#endif
diff --git a/common/lib/misc.h b/common/lib/misc.h
index c5a20c1d..8ea9d504 100644
--- a/common/lib/misc.h
+++ b/common/lib/misc.h
@@ -56,6 +56,7 @@ uint8_t int_to_bcd(uint8_t val);
noreturn void panic(bool allow_menu, const char *fmt, ...);
int pit_sleep_and_quit_on_keypress(int seconds);
+int pit_sleep_ms_and_quit_on_keypress(uint64_t milliseconds);
uint64_t strtoui(const char *s, const char **end, int base);
diff --git a/common/menu.c b/common/menu.c
index b465e826..2fadeaa4 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -40,6 +40,7 @@ EFI_GUID limine_efi_vendor_guid =
#define TOK_VALUE 2
#define TOK_BADKEY 3
#define TOK_COMMENT 4
+#define TIMEOUT_MAX_MS (UINT64_C(9999) * 1000)
static char interface_help_colour[24] = "\e[38;2;0;170;0m";
static char interface_help_colour_bright[24] = "\e[38;2;85;255;85m";
@@ -47,6 +48,22 @@ static char menu_branding_colour[24] = "\e[38;2;0;170;170m";
static char *menu_branding = NULL;
+static char *append_uint_dec(char *p, uint64_t val) {
+ char buf[20];
+ size_t i = 0;
+
+ do {
+ buf[i++] = '0' + (val % 10);
+ val /= 10;
+ } while (val != 0);
+
+ while (i != 0) {
+ *p++ = buf[--i];
+ }
+ *p = '\0';
+ return p;
+}
+
static char *write_uint8_dec(char *p, uint8_t v) {
if (v >= 100) {
*p++ = '0' + v / 100;
@@ -61,6 +78,73 @@ static char *write_uint8_dec(char *p, uint8_t v) {
return p;
}
+static uint64_t parse_timeout_ms(const char *str) {
+ uint64_t seconds = 0;
+ uint64_t milliseconds = 0;
+ bool any = false;
+
+ while (isdigit(*str)) {
+ any = true;
+ if (seconds <= TIMEOUT_MAX_MS / 1000) {
+ seconds *= 10;
+ seconds += *str - '0';
+ }
+ str++;
+ }
+
+ if (*str == '.') {
+ uint64_t multiplier = 100;
+
+ str++;
+
+ while (isdigit(*str)) {
+ any = true;
+
+ if (multiplier != 0) {
+ milliseconds += (*str - '0') * multiplier;
+ multiplier /= 10;
+ } else if (*str != '0' && milliseconds < 999) {
+ milliseconds++;
+ }
+
+ str++;
+ }
+ }
+
+ if (!any) {
+ return 0;
+ }
+
+ if (seconds > TIMEOUT_MAX_MS / 1000) {
+ return UINT64_MAX;
+ }
+
+ return seconds * 1000 + milliseconds;
+}
+
+static size_t format_timeout_ms(char *buf, uint64_t milliseconds) {
+ char *p = append_uint_dec(buf, milliseconds / 1000);
+ uint64_t subsecond = milliseconds % 1000;
+
+ if (subsecond != 0) {
+ char *last;
+
+ *p++ = '.';
+ *p++ = '0' + subsecond / 100;
+ *p++ = '0' + (subsecond / 10) % 10;
+ *p++ = '0' + subsecond % 10;
+
+ last = p - 1;
+ while (*last == '0') {
+ last--;
+ }
+ p = last + 1;
+ }
+
+ *p = '\0';
+ return p - buf;
+}
+
static void format_fg_rgb_escape(char *buf, uint32_t rgb) {
char *p = buf;
*p++ = '\e'; *p++ = '['; *p++ = '3'; *p++ = '8'; *p++ = ';';
@@ -1113,22 +1197,6 @@ static char *append_string(char *p, const char *s) {
return p;
}
-static char *append_uint_dec(char *p, uint64_t val) {
- char buf[20];
- size_t i = 0;
-
- do {
- buf[i++] = '0' + (val % 10);
- val /= 10;
- } while (val != 0);
-
- while (i != 0) {
- *p++ = buf[--i];
- }
- *p = '\0';
- return p;
-}
-
static const char *uefi_shell_filename(void) {
#if defined (__x86_64__)
return "shellx64.efi";
@@ -1559,11 +1627,15 @@ noreturn void _menu(bool first_run) {
}
size_t timeout = 5;
+ uint64_t timeout_ms = timeout * 1000;
bool has_timeout = false;
#if defined (UEFI)
has_timeout = bli_update_oneshot_timeout(&timeout, &skip_timeout);
+ if (has_timeout) {
+ timeout_ms = (uint64_t)timeout * 1000;
+ }
#endif
if (!has_timeout) {
@@ -1573,18 +1645,19 @@ noreturn void _menu(bool first_run) {
if (!strcmp(timeout_config, "no"))
skip_timeout = true;
else
- timeout = strtoui(timeout_config, NULL, 10);
+ timeout_ms = parse_timeout_ms(timeout_config);
}
}
#if defined (UEFI)
if (!has_timeout) {
has_timeout = bli_update_timeout(&timeout, &skip_timeout);
+ timeout_ms = (uint64_t)timeout * 1000;
}
#endif
- if (timeout > 9999)
- timeout = 9999;
+ if (timeout_ms > TIMEOUT_MAX_MS)
+ timeout_ms = TIMEOUT_MAX_MS;
#if defined(UEFI)
bool reboot_to_firmware_supported = reboot_to_fw_ui_supported();
@@ -1596,7 +1669,7 @@ noreturn void _menu(bool first_run) {
skip_timeout = true;
}
- if (!skip_timeout && !timeout) {
+ if (!skip_timeout && !timeout_ms) {
if (max_entries == 0 || selected_menu_entry == NULL || selected_menu_entry->sub != NULL) {
quiet = false;
print("Default entry is not valid or directory, booting to menu.\n");
@@ -1757,17 +1830,23 @@ refresh:
if (skip_timeout == false) {
print("\n\n");
- for (size_t i = timeout; i; i--) {
- size_t ndigits = 1;
- for (size_t tmp = i / 10; tmp > 0; tmp /= 10) ndigits++;
- size_t msg_len = 28 + ndigits;
+ while (timeout_ms != 0) {
+ char timeout_buf[24];
+ uint64_t sleep_ms = timeout_ms % 1000;
+ size_t timeout_len = format_timeout_ms(timeout_buf, timeout_ms);
+ size_t msg_len = 28 + timeout_len;
set_cursor_pos_helper((terms[0]->cols - msg_len) / 2, terms[0]->rows - 2);
FOR_TERM(TERM->scroll_enabled = false);
- print("\e[2K%sBooting automatically in %s%U%s...\e[0m",
- interface_help_colour, interface_help_colour_bright, (uint64_t)i, interface_help_colour);
+ print("\e[2K%sBooting automatically in %s%s%s...\e[0m",
+ interface_help_colour, interface_help_colour_bright, timeout_buf, interface_help_colour);
FOR_TERM(TERM->scroll_enabled = true);
FOR_TERM(TERM->double_buffer_flush(TERM));
- if ((c = pit_sleep_and_quit_on_keypress(1))) {
+
+ if (sleep_ms == 0) {
+ sleep_ms = 1000;
+ }
+
+ if ((c = pit_sleep_ms_and_quit_on_keypress(sleep_ms))) {
skip_timeout = true;
if (quiet) {
quiet = false;
@@ -1778,6 +1857,7 @@ refresh:
FOR_TERM(TERM->double_buffer_flush(TERM));
goto timeout_aborted;
}
+ timeout_ms -= sleep_ms;
}
goto autoboot;
}
