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.
This commit is contained in:
alex authored and Alex Zenla committed 2026-09-26 20:52:15 -07:00
1 parent 78bcaa79ff
commit 9e595bf8d5
3 files changed
+34 -19

No files matched your search

+5 -1
View File
@@ -35,8 +35,9 @@ pub trait BootMenu {
} }
/// Read a key from `input`, giving up once `timeout` passes. /// 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. /// Returns the key that was pressed, or [None] if the timeout passed.
pub fn read_key(input: &mut Input, timeout: Duration) -> Result<Option<Key>> { pub fn read_key(input: &mut Input, timeout: Option<Duration>) -> Result<Option<Key>> {
// The event to wait for a key press. // The event to wait for a key press.
let key_event = input let key_event = input
.wait_for_key_event() .wait_for_key_event()
@@ -58,8 +59,11 @@ pub fn read_key(input: &mut Input, timeout: Duration) -> Result<Option<Key>> {
// Set a timer to trigger after the specified duration. // Set a timer to trigger after the specified duration.
// The timer is limited to what the firmware can represent, as a longer timeout // The timer is limited to what the firmware can represent, as a longer timeout
// can't be converted into a timer trigger. // can't be converted into a timer trigger.
// 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)); let trigger = TimerTrigger::Relative(timeout.min(MAX_TIMER_DURATION));
uefi::boot::set_timer(&events[0], trigger).context("unable to set timeout timer")?; uefi::boot::set_timer(&events[0], trigger).context("unable to set timeout timer")?;
}
loop { loop {
// Wait for either the timer event or the key event to trigger. // Wait for either the timer event or the key event to trigger.
+28 -17
View File
@@ -26,11 +26,11 @@ enum MenuOperation {
Nop, Nop,
} }
/// Read a key from the input device with a duration, returning the [MenuOperation] that was /// Read a key from the input device with an optional timeout, returning the [MenuOperation]
/// performed. /// that was performed. Without a timeout, this waits for a key indefinitely.
fn read(input: &mut Input, timeout: &Duration) -> Result<MenuOperation> { fn read(input: &mut Input, timeout: Option<Duration>) -> Result<MenuOperation> {
// If the timer triggered, the user did not select a numbered entry. // 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); return Ok(MenuOperation::Timeout);
}; };
@@ -58,34 +58,48 @@ fn read(input: &mut Input, timeout: &Duration) -> Result<MenuOperation> {
} }
} }
/// 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. /// Selects an entry from the list of entries using the boot menu.
fn select_with_input<'a>( fn select_with_input<'a>(
input: &mut Input, input: &mut Input,
timeout: Duration, timeout: Duration,
entries: &'a [BootableEntry], entries: &'a [BootableEntry],
) -> Result<&'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 { 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. // Print all the entries with the number used to select them.
info!("Boot Menu:"); info!("Boot Menu:");
for (index, entry) in entries.iter().enumerate() { for (index, entry) in entries.iter().enumerate() {
let title = entry.context().stamp(&entry.declaration().title); let title = entry.context().stamp(&entry.declaration().title);
info!(" [{}] {}", index, title); info!(" [{}] {}", index, title);
} }
}
// Read from input until a valid operation is selected. // Read from input until a valid operation is selected.
let operation = loop { 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!("Select a boot entry using the number keys.");
info!("Press Escape to exit and enter to display the entries again."); 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 { if operation != MenuOperation::Nop {
break operation; break operation;
} }
@@ -104,10 +118,7 @@ fn select_with_input<'a>(
// When the user exits the boot menu or a timeout occurs, we should // When the user exits the boot menu or a timeout occurs, we should
// boot the default entry, if any. // boot the default entry, if any.
MenuOperation::Exit | MenuOperation::Timeout => { MenuOperation::Exit | MenuOperation::Timeout => {
return entries return default_entry(entries);
.iter()
.find(|item| item.is_default())
.context("no default entry available");
} }
// If the operation is to continue or nop, we can just run the loop again. // If the operation is to continue or nop, we can just run the loop again.
+1 -1
View File
@@ -193,7 +193,7 @@ fn run(
// Wait for a key, waking up every tick to update the countdown. // Wait for a key, waking up every tick to update the countdown.
let tick = remaining.map_or(TICK, |remaining| remaining.min(TICK)); 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. // Without a countdown there's nothing to do until a key is pressed.
let Some(left) = remaining else { let Some(left) = remaining else {
continue; continue;