diff --git a/README.md b/README.md index 7949fe9..424ae62 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ $ sprout.efi --boot="Boot Xen" $ sprout.efi --autoconfigure # Use the basic boot menu instead of the simple one. $ sprout.efi --menu-style=basic -# Use the graphical boot menu, which can be used with the mouse. +# Use the graphical boot menu. $ sprout.efi --menu-style=graphical # Show the boot menu for 10 seconds before booting the default entry. $ sprout.efi --menu-timeout=10 @@ -142,6 +142,16 @@ $ sprout.efi --retain-boot-console $ sprout.efi --bls-strict-mode ``` +### Graphical Menu + +The graphical menu selects entries with the keyboard. The mouse is off by default. To use it, enable it in +`sprout.toml`. The cursor is only shown when the mouse is enabled and the firmware has a pointing device. + +```toml +[menu-styles.graphical] +enable-mouse = true +``` + ### Boot Linux from ESP ```toml diff --git a/crates/boot/src/main.rs b/crates/boot/src/main.rs index 8184c3e..e78d18c 100644 --- a/crates/boot/src/main.rs +++ b/crates/boot/src/main.rs @@ -630,6 +630,9 @@ fn run(reboot_on_error: &mut bool) -> Result<()> { .menu_style .unwrap_or(config.options.menu_style); + // The graphical menu only uses the mouse when it is enabled in the configuration. + let graphical_mouse = config.menu_styles.graphical.enable_mouse; + // Find the forced boot entry, unless the boot menu is forced. // The oneshot entry from the bootloader interface is forced by id, and it is used instead of // --boot. If the forced boot entry can't be found, such as when it was removed, @@ -664,8 +667,15 @@ fn run(reboot_on_error: &mut bool) -> Result<()> { let entry = match forced_entry { Some(entry) => entry, // Delegate to the menu to select an entry to boot. - None => menu::select(&timer, menu_timeout, menu_disabled, menu_style, &entries) - .context("unable to select entry via boot menu")?, + None => menu::select( + &timer, + menu_timeout, + menu_disabled, + menu_style, + graphical_mouse, + &entries, + ) + .context("unable to select entry via boot menu")?, }; // Tell the bootloader interface what the selected entry is. diff --git a/crates/boot/src/menu.rs b/crates/boot/src/menu.rs index ab17128..be03e42 100644 --- a/crates/boot/src/menu.rs +++ b/crates/boot/src/menu.rs @@ -22,7 +22,7 @@ pub mod basic; /// font: The bitmap font that the graphical menu draws text with. mod font; -/// graphical: A menu drawn on the graphics output that is used with the keyboard or the mouse. +/// graphical: A menu drawn on the graphics output that is used with the keyboard, or the mouse if enabled. pub mod graphical; /// logo: The Sprout logo that the graphical menu draws in a corner. @@ -177,6 +177,7 @@ pub fn select<'live>( timeout: Option, menu_disabled: bool, style: MenuStyle, + graphical_mouse: bool, entries: &'live [BootableEntry], ) -> Result<&'live BootableEntry> { // A hidden menu gives the user a moment to ask for the menu with a key press. @@ -208,7 +209,11 @@ pub fn select<'live>( // The graphical menu needs a graphics output, which not every machine has. // If it fails, the simple menu is used instead, which falls back to the basic menu. - MenuStyle::Graphical => GraphicalMenu.select(timeout, entries).or_else(|error| { + MenuStyle::Graphical => GraphicalMenu { + enable_mouse: graphical_mouse, + } + .select(timeout, entries) + .or_else(|error| { warn!( "unable to show the graphical boot menu, using the simple menu: {:#}", error diff --git a/crates/boot/src/menu/graphical.rs b/crates/boot/src/menu/graphical.rs index e0e22ba..71a524f 100644 --- a/crates/boot/src/menu/graphical.rs +++ b/crates/boot/src/menu/graphical.rs @@ -29,7 +29,10 @@ const PIXELS_PER_MILLIMETER: i64 = 8; const TITLE: &str = "sprout"; /// The hint shown once the countdown has been stopped. -const HINT: &str = "Pick an entry with the arrow keys or the mouse."; +const HINT: &str = "Pick an entry with the arrow keys."; + +/// The hint shown instead of [HINT] when the mouse is in use. +const HINT_MOUSE: &str = "Pick an entry with the arrow keys or the mouse."; /// The color of the top of the background, which fades into [BACKGROUND_BOTTOM]. const BACKGROUND_TOP: (u8, u8, u8) = (0x3a, 0x1c, 0x4e); @@ -87,8 +90,11 @@ const CURSOR: [&str; 16] = [ " XXXX ", ]; -/// A graphical boot menu that selects entries with the keyboard or the mouse. -pub struct GraphicalMenu; +/// A graphical boot menu that selects entries with the keyboard, or the mouse if enabled. +pub struct GraphicalMenu { + /// Whether the mouse is used when the firmware has a pointing device. + pub enable_mouse: bool, +} /// The pointing devices of the firmware. /// Firmware commonly has both kinds of protocol, even if one has no device behind it. @@ -258,7 +264,7 @@ impl Layout { .map(|entry| entry.title().chars().count()) .max() .unwrap_or(0); - let status = HINT.len().max(STATUS_WIDEST.len()); + let status = HINT.len().max(HINT_MOUSE.len()).max(STATUS_WIDEST.len()); let panel_width = ((longest + 4) * cell + padding * 2) .max(40 * cell) .max((status + 8) * cell) @@ -806,6 +812,7 @@ fn run( loop { scene.status = match remaining { Some(remaining) => format!("Boot in {} s.", remaining.as_millis().div_ceil(1000)), + None if mouse.is_some() => String::from(HINT_MOUSE), None => String::from(HINT), }; for screen in screens.iter_mut() { @@ -901,7 +908,7 @@ impl BootMenu for GraphicalMenu { } let mut screens = Screen::open_all(entries)?; - let mut mouse = Mouse::open(); + let mut mouse = self.enable_mouse.then(Mouse::open).flatten(); let result = uefi::system::with_stdin(|input| { run(input, mouse.take(), &mut screens, timeout, entries, default) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index ee1c180..df7fdbc 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -38,6 +38,9 @@ pub struct RootConfiguration { /// Default options for Sprout. #[serde(default)] pub options: OptionsConfiguration, + /// Settings for each style of boot menu. + #[serde(rename = "menu-styles", default)] + pub menu_styles: MenuStylesConfiguration, /// Values to be inserted into the root sprout context. #[serde(default)] pub values: BTreeMap, @@ -99,12 +102,30 @@ pub struct OptionsConfiguration { pub bls_strict_mode: bool, } +/// Settings for each style of boot menu. +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct MenuStylesConfiguration { + /// Settings for the graphical boot menu. + #[serde(default)] + pub graphical: GraphicalMenuConfiguration, +} + +/// Settings for the graphical boot menu. +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct GraphicalMenuConfiguration { + /// Enables selecting entries with the mouse, and shows the cursor. When disabled, or when + /// the firmware has no pointing device, entries are selected with the keyboard only. + #[serde(rename = "enable-mouse", default)] + pub enable_mouse: bool, +} + /// The default configuration, which matches an empty configuration file. impl Default for RootConfiguration { fn default() -> Self { Self { version: latest_version(), options: Default::default(), + menu_styles: Default::default(), values: Default::default(), drivers: Default::default(), extractors: Default::default(), @@ -171,6 +192,17 @@ mod tests { assert_eq!(default.options.autoconfigure, empty.options.autoconfigure); } + #[test] + fn graphical_mouse_is_off_by_default() { + assert!( + !RootConfiguration::default() + .menu_styles + .graphical + .enable_mouse + ); + assert!(!empty().menu_styles.graphical.enable_mouse); + } + #[test] fn bls_strict_mode_is_off_by_default() { assert!(!RootConfiguration::default().options.bls_strict_mode);