GUI Desktop for FRISC OS
Building a graphical desktop environment for FRISC OS — window manager, compositor, and applications. From raw framebuffer pixels to a fully functional retro-aesthetic desktop.
◉ Framebuffer Architecture
FRISC GUI is built on direct framebuffer access. No GPU drivers (yet), just a linear block of memory mapped to screen pixels:
struct Framebuffer {
addr: *mut u32, // Physical address of pixel buffer
width: u32, // Screen width in pixels
height: u32, // Screen height in pixels
pitch: u32, // Bytes per row (may include padding)
bpp: u32, // Bits per pixel (32 for BGRA)
}
Double-buffering prevents tearing:
fn render_frame(fb: &Framebuffer) {
// Draw to back buffer
back_buffer
|> clear(COLOR_BG)
|> draw_desktop()
|> draw_windows()
|> draw_cursor(mouse_x, mouse_y)
// Swap buffers (single memcpy)
fb.swap(back_buffer)
}
◉ The Rendering Pipeline
GUI rendering is a natural fit for Lateralus pipelines. Each frame:
fn desktop_render_loop() {
loop {
// Event handling pipeline
events()
|> filter(fn(e) { e.is_input() })
|> dispatch_to_focused_window()
// Render pipeline
windows
|> filter(fn(w) { w.visible })
|> sort_by(fn(w) { w.z_order })
|> each(fn(w) { w.render() })
// Composite and display
compositor.present()
// Aim for 60 FPS
sleep_until_vsync()
}
}
◉ Window Manager
The window manager is tiling by default, with floating mode optional. Windows are arranged automatically based on rules:
// Window layout configuration (in Lateralus)
let layout_rules = [
{ app: "terminal", position: "left-half" },
{ app: "editor", position: "right-half" },
{ app: "browser", position: "maximized" },
{ app: "menu", position: "floating" },
]
fn apply_layout(window: Window) {
layout_rules
|> find(fn(r) { r.app == window.app_name })
|> match {
Some(rule) => window.set_position(rule.position),
None => window.tile_next_available(),
}
}
Keyboard-driven navigation with Vim-style bindings:
Super + H/J/K/L— Focus window left/down/up/rightSuper + Shift + H/J/K/L— Move windowSuper + Enter— Launch terminalSuper + Space— Toggle floating modeSuper + 1-9— Switch workspace
◉ The Retro Theme
FRISC desktop embraces a retro-futuristic aesthetic matching the Lateralus website:
// Color palette
const COLOR_BG = 0xFF0a0a12 // Deep charcoal
const COLOR_PANEL = 0xFF12121c // Slightly lighter
const COLOR_BORDER = 0xFF2a2a3a // Subtle borders
const COLOR_LIME = 0xFF00ff88 // Accent (window focus)
const COLOR_PINK = 0xFFff4081 // Secondary accent
const COLOR_YELLOW = 0xFFffff00 // Highlights
const COLOR_CYAN = 0xFF00d4ff // Links/buttons
Fonts are rendered using a built-in 8x16 bitmap font for that authentic terminal aesthetic. We may add TrueType support later, but bitmap fonts are fast and pixel-perfect.
◉ Built-in Applications
FRISC ships with essential applications, all written in Lateralus:
Terminal (frisc-term): VT100-compatible terminal emulator with 256 colors.
File Manager (frisc-files): Two-panel file browser inspired by Midnight Commander.
Text Editor (frisc-edit): Modal editor with Vim keybindings. Can edit system config files.
System Monitor (frisc-top): Real-time CPU, memory, and process monitoring.
// System monitor is just a pipeline over process data
fn update_display() {
processes()
|> filter(fn(p) { p.state != "zombie" })
|> sort_by(fn(p) { p.cpu_percent })
|> reverse()
|> take(20)
|> each(fn(p) {
draw_row(p.pid, p.name, p.cpu_percent, p.mem_mb)
})
}
◉ Compositor Details
The compositor handles window rendering order, transparency, and damage tracking:
struct Compositor {
windows: list[Window],
damage_regions: list[Rect],
back_buffer: Buffer,
}
fn composite(self) {
// Only redraw damaged regions for efficiency
let regions = self.damage_regions |> merge_overlapping()
for region in regions {
// Draw background
self.back_buffer.fill_rect(region, wallpaper.sample(region))
// Draw windows that intersect this region
self.windows
|> filter(fn(w) { w.bounds.intersects(region) })
|> each(fn(w) { w.blit_to(self.back_buffer, region) })
}
// Swap to front
framebuffer.blit(self.back_buffer)
self.damage_regions.clear()
}
◉ Mouse Support
PS/2 mouse driver (or USB HID on newer hardware) provides cursor movement:
fn mouse_handler(packet: MousePacket) {
// Update cursor position
cursor_x = clamp(cursor_x + packet.dx, 0, screen_width - 1)
cursor_y = clamp(cursor_y - packet.dy, 0, screen_height - 1) // Y inverted
// Hit testing for clicks
if packet.left_click {
windows
|> find(fn(w) { w.bounds.contains(cursor_x, cursor_y) })
|> match {
Some(w) => w.handle_click(cursor_x, cursor_y),
None => desktop.handle_click(cursor_x, cursor_y),
}
}
// Mark cursor region as damaged
compositor.damage(cursor_rect())
}
◉ Performance
On the HiFive Unmatched (no GPU acceleration):
| Operation | Time |
|---|---|
| Full frame redraw (1920x1080) | 14ms |
| Partial redraw (single window) | 2-4ms |
| Cursor movement | <1ms |
| Window drag | ~8ms |
We achieve 60+ FPS for normal usage with damage tracking. Full-screen video playback would need GPU acceleration.
◉ Try It
Boot FRISC with GUI support:
qemu-system-riscv64 -machine virt -m 512M \
-device virtio-gpu-device \
-kernel build/frisc.elf \
-append "console=ttyS0 gui=1"
See the OS page for full build instructions.