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
+41 -26

No files matched your search

+7 -3
View File
@@ -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<Option<Key>> {
pub fn read_key(input: &mut Input, timeout: Option<Duration>) -> Result<Option<Key>> {
// 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<Option<Key>> {
// 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.
+33 -22
View File
@@ -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<MenuOperation> {
/// 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<Duration>) -> Result<MenuOperation> {
// 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<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.
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.
+1 -1
View File
@@ -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;