From 9e595bf8d5fe6fb10b3a0ac9f78911a02f044b4a Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 26 Sep 2026 20:11:24 -0700 Subject: [PATCH] fix(menu): stop the basic menu countdown on a key press Every key that didn't select an entry, like enter or an arrow key, made the basic menu wait for the full timeout again. Stop the countdown on any key instead, like the simple menu, so the menu waits for the user once they start using it. --- crates/boot/src/menu.rs | 10 +++++-- crates/boot/src/menu/basic.rs | 55 ++++++++++++++++++++-------------- crates/boot/src/menu/simple.rs | 2 +- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/crates/boot/src/menu.rs b/crates/boot/src/menu.rs index 5711622..3494a96 100644 --- a/crates/boot/src/menu.rs +++ b/crates/boot/src/menu.rs @@ -35,8 +35,9 @@ pub trait BootMenu { } /// Read a key from `input`, giving up once `timeout` passes. +/// If `timeout` is [None], this waits for a key indefinitely. /// Returns the key that was pressed, or [None] if the timeout passed. -pub fn read_key(input: &mut Input, timeout: Duration) -> Result> { +pub fn read_key(input: &mut Input, timeout: Option) -> Result> { // The event to wait for a key press. let key_event = input .wait_for_key_event() @@ -58,8 +59,11 @@ pub fn read_key(input: &mut Input, timeout: Duration) -> Result> { // Set a timer to trigger after the specified duration. // The timer is limited to what the firmware can represent, as a longer timeout // can't be converted into a timer trigger. - let trigger = TimerTrigger::Relative(timeout.min(MAX_TIMER_DURATION)); - uefi::boot::set_timer(&events[0], trigger).context("unable to set timeout timer")?; + // Without a timeout, the timer is never set, so it never triggers. + if let Some(timeout) = timeout { + let trigger = TimerTrigger::Relative(timeout.min(MAX_TIMER_DURATION)); + uefi::boot::set_timer(&events[0], trigger).context("unable to set timeout timer")?; + } loop { // Wait for either the timer event or the key event to trigger. diff --git a/crates/boot/src/menu/basic.rs b/crates/boot/src/menu/basic.rs index 7626852..d43b2ac 100644 --- a/crates/boot/src/menu/basic.rs +++ b/crates/boot/src/menu/basic.rs @@ -26,11 +26,11 @@ enum MenuOperation { Nop, } -/// Read a key from the input device with a duration, returning the [MenuOperation] that was -/// performed. -fn read(input: &mut Input, timeout: &Duration) -> Result { +/// Read a key from the input device with an optional timeout, returning the [MenuOperation] +/// that was performed. Without a timeout, this waits for a key indefinitely. +fn read(input: &mut Input, timeout: Option) -> Result { // If the timer triggered, the user did not select a numbered entry. - let Some(key) = read_key(input, *timeout)? else { + let Some(key) = read_key(input, timeout)? else { return Ok(MenuOperation::Timeout); }; @@ -58,34 +58,48 @@ fn read(input: &mut Input, timeout: &Duration) -> Result { } } +/// Find the default entry in `entries`. +fn default_entry(entries: &[BootableEntry]) -> Result<&BootableEntry> { + entries + .iter() + .find(|item| item.is_default()) + .context("no default entry available") +} + /// Selects an entry from the list of entries using the boot menu. fn select_with_input<'a>( input: &mut Input, timeout: Duration, entries: &'a [BootableEntry], ) -> Result<&'a BootableEntry> { + // If the timeout is zero, boot the default entry without showing the menu. + if 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); + loop { - // If the timeout is not zero, let's display the boot menu. - if !timeout.is_zero() { - // Print all the entries with the number used to select them. - info!("Boot Menu:"); - for (index, entry) in entries.iter().enumerate() { - let title = entry.context().stamp(&entry.declaration().title); - info!(" [{}] {}", index, title); - } + // Print all the entries with the number used to select them. + info!("Boot Menu:"); + for (index, entry) in entries.iter().enumerate() { + let title = entry.context().stamp(&entry.declaration().title); + info!(" [{}] {}", index, title); } // Read from input until a valid operation is selected. let operation = loop { - // If the timeout is zero, we can exit immediately because there is nothing to do. - if timeout.is_zero() { - break MenuOperation::Exit; - } - info!("Select a boot entry using the number keys."); info!("Press Escape to exit and enter to display the entries again."); - let operation = read(input, &timeout)?; + let operation = read(input, countdown)?; + + // Any key stops the countdown, so the menu waits for the user from now on. + if operation != MenuOperation::Timeout { + countdown = None; + } + if operation != MenuOperation::Nop { break operation; } @@ -104,10 +118,7 @@ fn select_with_input<'a>( // When the user exits the boot menu or a timeout occurs, we should // boot the default entry, if any. MenuOperation::Exit | MenuOperation::Timeout => { - return entries - .iter() - .find(|item| item.is_default()) - .context("no default entry available"); + return default_entry(entries); } // If the operation is to continue or nop, we can just run the loop again. diff --git a/crates/boot/src/menu/simple.rs b/crates/boot/src/menu/simple.rs index ed90feb..9d63095 100644 --- a/crates/boot/src/menu/simple.rs +++ b/crates/boot/src/menu/simple.rs @@ -193,7 +193,7 @@ fn run( // Wait for a key, waking up every tick to update the countdown. let tick = remaining.map_or(TICK, |remaining| remaining.min(TICK)); - let Some(key) = read_key(input, tick)? else { + let Some(key) = read_key(input, Some(tick))? else { // Without a countdown there's nothing to do until a key is pressed. let Some(left) = remaining else { continue;