From d7d719775be72885f5b83b57cf95c21d876bfc2b Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Sat, 25 Jul 2026 18:35:09 +0900 Subject: [PATCH] bootloader: clean per-phase screens + Red Bear branding Comprehensive fix for overlapping bootloader text. The loader ran three phases (filesystem, resolution menu, loading) that all wrote to one never-fully-cleared screen at absolute cursor positions, so they overprinted each other. Most visibly, the loading phase began printing "live: .../..." exactly where the menu had left the cursor (the "Autobooting" row), so "live" landed on top of the countdown. clear_text() also only ran for the 2nd+ video output, so on a single display the menu was drawn under the header. Introduce draw_header(os): clear the screen and draw a consistent, branded "Red Bear OS" title + version/platform + separator. Call it at the start of each phase so every screen is clean and no phase overprints another: - Phase 1 (filesystem / password prompt) - Phase 2 (resolution menu, per video output) - Phase 3 (kernel/initfs load + optional live preload) Also: - Rebrand user-facing strings: "Redox OS Bootloader" -> "Red Bear OS", and the env editor title -> "Red Bear OS Boot Environment Editor". (RedoxFS / the RedoxFtw initfs magic are left as-is: those are real format names.) - Drop the raw "Hardware descriptor: {:x?}" Debug dump from the UI. - Indent all loading-phase progress lines (RedoxFS/live/kernel/initfs) to match the header for a consistent layout. Verified: cargo check passes for x86_64-unknown-uefi (--bin) and x86-unknown-none (--lib). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/editor.rs | 2 +- src/main.rs | 81 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/editor.rs b/src/editor.rs index a4ab291007..7dad0f96d9 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -2,7 +2,7 @@ use crate::os::{Os, OsKey}; fn edit_banner(os: &impl Os) { os.clear_text(); - println!("--- Redox Bootloader Environment Editor ---"); + println!("--- Red Bear OS Boot Environment Editor ---"); println!("ENTER twice to boot. UP/DOWN to edit lines."); println!("-------------------------------------------"); } diff --git a/src/main.rs b/src/main.rs index bb693f77b8..ac8d11fc14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -509,7 +509,7 @@ fn load_to_memory( let size = node.data().size(); - print!("{}: 0/{} MiB", path, size / MIBI as u64); + print!(" {}: 0/{} MiB", path, size / MIBI as u64); let ptr = os.alloc_zeroed_page_aligned(size as usize); if ptr.is_null() { @@ -520,13 +520,13 @@ fn load_to_memory( let mut i = 0; for chunk in slice.chunks_mut(MIBI) { - print!("\r{}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64); + print!("\r {}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64); i += tx .read_node_inner(&node, i, chunk) .unwrap_or_else(|err| panic!("Failed to read `{}` file: {}", path, err)) as u64; } - println!("\r{}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64); + println!("\r {}: {}/{} MiB", path, i / MIBI as u64, size / MIBI as u64); if filetype == Filetype::Elf { let magic = &slice[..4]; @@ -586,15 +586,43 @@ fn elf_entry(data: &[u8]) -> (u64, bool) { } } +/// Clear the screen and draw the Red Bear OS bootloader header. +/// +/// Called at the start of each phase (filesystem, resolution menu, loading) so +/// every phase renders on a clean, consistently-branded screen and no phase +/// overprints another. Previously only the second-and-later video outputs were +/// cleared, so on a single display the menu was drawn under the header and the +/// loading progress was drawn on top of the still-visible menu (e.g. "live:" +/// landing on the "Autobooting" line). +fn draw_header(os: &impl Os) { + os.clear_text(); + os.set_text_position(0, 0); + os.set_text_color(TextColor::Red); + println!(" Red Bear OS"); + os.set_text_color(TextColor::Default); + println!(" Bootloader {} ({})", env!("CARGO_PKG_VERSION"), os.name()); + println!(" --------------------------------------------"); + println!(); +} + +/// Print the RedoxFS identity line (uuid + size) below the current cursor. +fn print_fs_line(fs: &redoxfs::FileSystem) { + print!(" RedoxFS "); + for i in 0..fs.header.uuid().len() { + if i == 4 || i == 6 || i == 8 || i == 10 { + print!("-"); + } + print!("{:>02x}", fs.header.uuid()[i]); + } + println!(": {} MiB", fs.header.size() / MIBI as u64); +} + fn main(os: &impl Os) -> (usize, u64, KernelArgs) { - println!( - "Redox OS Bootloader {} on {}", - env!("CARGO_PKG_VERSION"), - os.name() - ); + // Phase 1: open the filesystem. redoxfs() prompts for a password on this + // header screen if the disk is encrypted. + draw_header(os); let hwdesc = os.hwdesc(); - println!("Hardware descriptor: {:x?}", hwdesc); let (acpi_rsdp_base, acpi_rsdp_size) = match hwdesc { OsHwDesc::Acpi(base, size) => (base, size), OsHwDesc::DeviceTree(base, size) => (base, size), @@ -603,24 +631,12 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) { let (mut fs, password_opt) = redoxfs(os); - print!("RedoxFS "); - for i in 0..fs.header.uuid().len() { - if i == 4 || i == 6 || i == 8 || i == 10 { - print!("-"); - } - - print!("{:>02x}", fs.header.uuid()[i]); - } - println!(": {} MiB", fs.header.size() / MIBI as u64); - println!(); - + // Phase 2: resolution selection — a clean, titled screen per video output. let mut mode_opts = Vec::new(); let mut live = cfg!(feature = "live"); let mut edit_env = false; for output_i in 0..os.video_outputs() { - if output_i > 0 { - os.clear_text(); - } + draw_header(os); mode_opts.push(select_mode(os, output_i, &mut live, &mut edit_env)); } @@ -630,15 +646,20 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) { panic!("Failed to allocate memory for stack"); } + // Phase 3: load kernel + initfs (and optionally preload the live image) on a + // fresh titled screen, so loading progress never overprints the menu. + draw_header(os); + print_fs_line(&fs); + let live_opt = if live { let size = fs.header.size(); - print!("live: 0/{} MiB", size / MIBI as u64); + print!(" live: 0/{} MiB", size / MIBI as u64); let live_size = match usize::try_from(size) { Ok(live_size) => live_size, Err(_) => { - println!("\rlive: disabled (image too large for bootloader address space)"); + println!("\r live: disabled (image too large for bootloader address space)"); live = false; 0 } @@ -651,7 +672,7 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) { }; if live && ptr.is_null() { println!( - "\rlive: disabled (unable to allocate {} MiB upfront)", + "\r live: disabled (unable to allocate {} MiB upfront)", size / MIBI as u64 ); live = false; @@ -660,23 +681,23 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) { let live = if live { Some(unsafe { slice::from_raw_parts_mut(ptr, live_size) }) } else { - println!("Continuing without live preload"); + println!(" Continuing without live preload"); None }; if let Some(live) = live { let mut i = 0; for chunk in live.chunks_mut(MIBI) { - print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); + print!("\r live: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); i += unsafe { fs.disk .read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk) .expect("Failed to read live disk") as u64 }; } - println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); + println!("\r live: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); - println!("Switching to live disk"); + println!(" Switching to live disk"); unsafe { LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, live_size))); }