//! tile [N] -- draw a tiling-window-manager mock-up on /dev/fb0. //! //! tile 4 "windows" in a dwindle layout, the text console in the first //! tile 6 1 to 6 windows //! tile -r give the text console the whole screen back //! //! A hint of the compositor planned for later (think Hyprland): a gradient //! wallpaper, windows with gaps between them and a border, the focused one //! cyan. The "windows" are only coloured rectangles, except the first one, //! which is where the kernel's text console moves to (FB_SET_CONSOLE), so //! the shell keeps working inside it. //! //! How it talks to the framebuffer (no mmap yet, so no direct pointer): //! ioctl(FB_GET_INFO) width, height, bytes per row, bits per pixel //! lseek + write raw 0x00RRGGBB pixels (the wallpaper rows) //! ioctl(FB_FILL) fill a rectangle with one colour (fast) //! ioctl(FB_FLUSH) send the changed part to the screen #![no_std] #![no_main] #[macro_use] #[path = "ulib.rs"] mod ulib; extern crate alloc; use alloc::vec; use alloc::vec::Vec; use ulib::*; const GAP: u32 = 10; // space between windows and around the screen edge const BORDER: u32 = 3; const ACTIVE: u32 = 0x33ccff; // focused window border (cyan) const INACTIVE: u32 = 0x595959; // the other borders (gray) /// Window contents: soft pastels (the first window is the console). const COLORS: [u32; 5] = [0x89b4fa, 0xa6e3a1, 0xf9e2af, 0xf38ba8, 0xcba6f7]; #[derive(Clone, Copy)] struct Rect { x: u32, y: u32, w: u32, h: u32, } fn main() -> i64 { let fd = open("/dev/fb0", 0); if fd < 0 { eprintln!("tile: /dev/fb0: {} (no virtio-gpu?)", strerror(fd)); return 1; } let status = match arg(1) { Some("-r") => reset(fd), Some(n) => match n.parse::() { Ok(n @ 1..=6) => draw(fd, n), _ => { eprintln!("usage: tile [1-6] | tile -r"); 2 } }, None => draw(fd, 4), }; close(fd); status } fn reset(fd: i64) -> i64 { let r = ioctl(fd, FB_SET_CONSOLE, 0); if r < 0 { eprintln!("tile: {}", strerror(r)); return 1; } println!("console: full screen, {}x{} characters", r >> 16, r & 0xffff); 0 } fn draw(fd: i64, n: usize) -> i64 { let mut info = [0u32; 4]; if ioctl(fd, FB_GET_INFO, info.as_mut_ptr() as u64) < 0 { eprintln!("tile: FB_GET_INFO failed"); return 1; } let [width, height, stride, _bpp] = info; wallpaper(fd, width, height, stride); let screen = Rect { x: GAP, y: GAP, w: width - 2 * GAP, h: height - 2 * GAP }; let windows = dwindle(screen, n); for (i, &win) in windows.iter().enumerate() { fill(fd, win, if i == 0 { ACTIVE } else { INACTIVE }); let inside = shrink(win, BORDER); if i > 0 { fake_content(fd, inside, COLORS[(i - 1) % COLORS.len()]); } } let flush = [0, 0, width, height]; ioctl(fd, FB_FLUSH, flush.as_ptr() as u64); // Move the console into window 0 (inside its border). let c = shrink(windows[0], BORDER); let r = ioctl(fd, FB_SET_CONSOLE, [c.x, c.y, c.w, c.h].as_ptr() as u64); if r < 0 { eprintln!("tile: FB_SET_CONSOLE: {}", strerror(r)); return 1; } println!("tile: {} windows on {}x{}, console {}x{}", n, width, height, r >> 16, r & 0xffff); 0 } /// Split the screen like a "dwindle" layout: each window takes half of /// what is left, alternating left|right and top/bottom splits. fn dwindle(mut area: Rect, n: usize) -> Vec { let mut out = Vec::new(); for i in 0..n { if i + 1 == n { out.push(area); // the last window gets the rest break; } if i % 2 == 0 { let w = (area.w - GAP) / 2; out.push(Rect { w, ..area }); area = Rect { x: area.x + w + GAP, w: area.w - w - GAP, ..area }; } else { let h = (area.h - GAP) / 2; out.push(Rect { h, ..area }); area = Rect { y: area.y + h + GAP, h: area.h - h - GAP, ..area }; } } out } /// A vertical gradient, written 8 rows (32 KiB) at a time with lseek + /// write. (Programs only have a 64 KiB heap until mmap exists.) fn wallpaper(fd: i64, width: u32, height: u32, stride: u32) { const ROWS: u32 = 8; let mut buf = vec![0u8; (stride * ROWS) as usize]; for y0 in (0..height).step_by(ROWS as usize) { let rows = ROWS.min(height - y0); for dy in 0..rows { let color = gradient(y0 + dy, height).to_le_bytes(); let row = &mut buf[(dy * stride) as usize..][..(width * 4) as usize]; row.chunks_exact_mut(4).for_each(|px| px.copy_from_slice(&color)); } lseek(fd, (y0 * stride) as i64, SEEK_SET); write(fd, &buf[..(rows * stride) as usize]); } } /// Dark blue at the top to purple at the bottom. fn gradient(y: u32, height: u32) -> u32 { let mix = |a: u32, b: u32| a + (b - a) * y / height; mix(0x1e, 0x50) << 16 | mix(0x1e, 0x2a) << 8 | mix(0x2e, 0x78) } /// Fill a window with `color` and draw some darker bars, like lines of text. fn fake_content(fd: i64, r: Rect, color: u32) { fill(fd, r, color); let dark = (color >> 1) & 0x7f7f7f; // every channel halved let mut y = r.y + 16; let mut line = 0; while y + 6 < r.y + r.h - 8 { let w = (r.w - 32) * [9, 6, 8, 4, 7][line % 5] / 10; fill(fd, Rect { x: r.x + 16, y, w, h: 6 }, dark); y += 16; line += 1; } } fn shrink(r: Rect, by: u32) -> Rect { Rect { x: r.x + by, y: r.y + by, w: r.w - 2 * by, h: r.h - 2 * by } } fn fill(fd: i64, r: Rect, color: u32) { ioctl(fd, FB_FILL, [r.x, r.y, r.w, r.h, color].as_ptr() as u64); }