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) <noreply@anthropic.com>
This commit is contained in:
Red Bear OS
2026-07-25 18:35:09 +09:00
parent 6407d28c26
commit d7d719775b
2 changed files with 52 additions and 31 deletions
+1 -1
View File
@@ -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!("-------------------------------------------");
}
+51 -30
View File
@@ -509,7 +509,7 @@ fn load_to_memory<O: Os>(
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<O: Os>(
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<D: Disk>(fs: &redoxfs::FileSystem<D>) {
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)));
}