RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / rust / sys / src / bin / tile.rs
5809 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! 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::<usize>() {
            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<Rect> {
    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);
}