fix(menu): show the boot menu when it is forced

--force-menu and LoaderConfigTimeout=menu-force only stopped --boot and
LoaderEntryOneShot from skipping the menu. The timeout was left as is,
so with a zero timeout the menu booted the default entry without being
drawn, and menu-force counted down and booted the default entry, where
the Boot Loader Interface says it should wait for the user.

Menus now take an optional timeout. menu-force waits for the user, and
--force-menu keeps the configured timeout unless it is zero, in which
case it waits for the user too.
This commit is contained in:
alex authored and Alex Zenla committed 2026-09-26 20:52:15 -07:00
1 parent df0bda04c0
commit e8e3361764
4 files changed
+27 -14

No files matched your search

+12 -2
View File
@@ -291,11 +291,15 @@ fn run() -> Result<()> {
.menu_timeout
.unwrap_or(config.options.menu_timeout);
// Whether the boot menu should wait for the user, instead of counting down.
let mut wait_for_user = false;
// Apply bootloader interface timeout settings.
match bootloader_interface_timeout {
BootloaderInterfaceTimeout::MenuForce => {
// Force the boot menu.
// Force the boot menu, and wait for the user to choose an entry.
force_boot_menu = true;
wait_for_user = true;
}
BootloaderInterfaceTimeout::MenuHidden | BootloaderInterfaceTimeout::MenuDisabled => {
@@ -341,7 +345,13 @@ fn run() -> Result<()> {
}
// Convert the menu timeout to a duration.
let menu_timeout = Duration::from_secs(menu_timeout);
// A zero timeout boots without showing the menu, so a forced menu with a zero timeout
// waits for the user instead.
let menu_timeout = if wait_for_user || (force_boot_menu && menu_timeout == 0) {
None
} else {
Some(Duration::from_secs(menu_timeout))
};
// Determine the menu style based on the options or configuration.
// We prefer the options over the configuration to allow for overriding.
+5 -3
View File
@@ -26,10 +26,11 @@ const MAX_TIMER_DURATION: Duration = Duration::from_secs(u32::MAX as u64);
/// A boot menu that can be shown to select an entry to boot.
pub trait BootMenu {
/// Select an entry from `entries` to boot. If no entry is chosen before `timeout` passes,
/// the default entry is selected.
/// the default entry is selected. If `timeout` is zero, the default entry is selected
/// without showing the menu. If `timeout` is [None], the menu waits for the user.
fn select<'a>(
&self,
timeout: Duration,
timeout: Option<Duration>,
entries: &'a [BootableEntry],
) -> Result<&'a BootableEntry>;
}
@@ -108,9 +109,10 @@ pub fn read_key(input: &mut Input, timeout: Option<Duration>) -> Result<Option<K
}
/// Shows a boot menu of the specified `style` to select a bootable entry to boot.
/// See [BootMenu::select] for how `timeout` is handled.
pub fn select<'live>(
timer: &'live PlatformTimer,
timeout: Duration,
timeout: Option<Duration>,
style: MenuStyle,
entries: &'live [BootableEntry],
) -> Result<&'live BootableEntry> {
+4 -4
View File
@@ -76,16 +76,16 @@ fn default_entry(entries: &[BootableEntry]) -> Result<&BootableEntry> {
/// Selects an entry from the list of entries using the boot menu.
fn select_with_input<'a>(
input: &mut Input,
timeout: Duration,
timeout: Option<Duration>,
entries: &'a [BootableEntry],
) -> Result<&'a BootableEntry> {
// If the timeout is zero, boot the default entry without showing the menu.
if timeout.is_zero() {
if timeout.is_some_and(|timeout| timeout.is_zero()) {
return default_entry(entries);
}
// The time to wait for a key, or None once a key stops the countdown.
let mut countdown = Some(timeout);
let mut countdown = timeout;
// The entry number typed so far, while more digits could still select another entry.
let mut typed: Option<usize> = None;
@@ -176,7 +176,7 @@ impl BootMenu for BasicMenu {
/// within the context of the standard input device.
fn select<'a>(
&self,
timeout: Duration,
timeout: Option<Duration>,
entries: &'a [BootableEntry],
) -> Result<&'a BootableEntry> {
uefi::system::with_stdin(move |input| select_with_input(input, timeout, entries))
+6 -5
View File
@@ -163,11 +163,12 @@ fn render(
/// Run the menu until an entry is chosen, returning the index of that entry.
/// The `selected` entry is booted if no key is pressed before `timeout` passes.
/// Without a timeout, the menu waits for the user.
fn run(
input: &mut Input,
output: &mut Output,
layout: &Layout,
timeout: Duration,
timeout: Option<Duration>,
entries: &[BootableEntry],
mut selected: usize,
) -> Result<usize> {
@@ -175,7 +176,7 @@ fn run(
// The first visible entry, which changes when scrolling through a long list.
let mut offset = 0;
// The time left before booting, or None once a key stops the countdown.
let mut remaining = Some(timeout);
let mut remaining = timeout;
loop {
// Scroll so that the selected entry is visible.
@@ -230,7 +231,7 @@ fn run(
fn select_with_console<'a>(
input: &mut Input,
output: &mut Output,
timeout: Duration,
timeout: Option<Duration>,
entries: &'a [BootableEntry],
) -> Result<&'a BootableEntry> {
let default = entries
@@ -239,7 +240,7 @@ fn select_with_console<'a>(
.context("no default entry available")?;
// If the timeout is zero, boot the default entry without showing the menu.
if timeout.is_zero() {
if timeout.is_some_and(|timeout| timeout.is_zero()) {
return Ok(&entries[default]);
}
@@ -269,7 +270,7 @@ impl BootMenu for SimpleMenu {
/// within the context of the standard input and output devices.
fn select<'a>(
&self,
timeout: Duration,
timeout: Option<Duration>,
entries: &'a [BootableEntry],
) -> Result<&'a BootableEntry> {
uefi::system::with_stdin(|input| {