From ba304898b8e177d90afb58d201b4c3dd3c927c48 Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 27 Aug 2026 17:23:34 +0200 Subject: GUI: window/event exports, paint binary, event-loop gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rsapi: WmEvent (kind=u8 enum: 0=key, 1=mouse, 2=focus, 3=close), WmKeyKind (Char/Enter/Backspace/F1-F4/…), WmMouseEvent (x/y/buttons), WindowInfo (ptr/w/h). Five new exports: wm_create, wm_buffer, wm_flush, wm_destroy, wm_recv (non-blocking TaskMsg dispatch → WmEvent). Shell: abi_wm_* wrappers bridge rsapi types to rsnix::wm and sched::recv(); wm_recv translates TaskMsg::Key/Mouse/Focus/Close into flat WmEvent union. bins/paint: gate binary — create_window + buffer + event loop with Bresenham line drawing, color select (1-8), clear (c), F4 close. All via wm_* ABI exports. Fully migrated: date, mem, pci, cat, ls, mkdir, touch, rm, klog, info, paint (11 external binaries in /bin/cli/ + 3 gate binaries). --- Cargo.lock | 7 +++ Cargo.toml | 2 +- bins/paint/Cargo.toml | 8 +++ bins/paint/src/main.rs | 137 +++++++++++++++++++++++++++++++++++++++++++++++++ rsapi/src/lib.rs | 72 ++++++++++++++++++++++++++ shell/src/lib.rs | 97 ++++++++++++++++++++++++++++++++++ 6 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 bins/paint/Cargo.toml create mode 100644 bins/paint/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 35ac614..a4accd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,13 @@ dependencies = [ "rsapi", ] +[[package]] +name = "paint" +version = "0.6.0" +dependencies = [ + "rsapi", +] + [[package]] name = "pci" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 42aa13f..fe02cfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["rsnix", "shell", "rsdos", "rsdosedit", "rspaint", "rsapi", "bins/hello", "bins/boom", "bins/count", "bins/date", "bins/mem", "bins/pci", "bins/cat", "bins/mkdir", "bins/touch", "bins/rm", "bins/ls", "bins/klog", "bins/info"] +members = ["rsnix", "shell", "rsdos", "rsdosedit", "rspaint", "rsapi", "bins/hello", "bins/boom", "bins/count", "bins/date", "bins/mem", "bins/pci", "bins/cat", "bins/mkdir", "bins/touch", "bins/rm", "bins/ls", "bins/klog", "bins/info", "bins/paint"] [workspace.package] version = "0.6.0" diff --git a/bins/paint/Cargo.toml b/bins/paint/Cargo.toml new file mode 100644 index 0000000..c89597e --- /dev/null +++ b/bins/paint/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "paint" +version.workspace = true +edition.workspace = true +[lints] +workspace = true +[dependencies] +rsapi = { path = "../../rsapi" } diff --git a/bins/paint/src/main.rs b/bins/paint/src/main.rs new file mode 100644 index 0000000..d395a4c --- /dev/null +++ b/bins/paint/src/main.rs @@ -0,0 +1,137 @@ +//! paint — drawing canvas via the GUI exports table. +//! +//! Keys: 1-8 select color, c clears to black. Mouse draws when pressed. + +#![no_std] +#![no_main] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop { core::hint::spin_loop(); } +} + +const W: u32 = 640; +const H: u32 = 400; + +const fn pack(r: u8, g: u8, b: u8) -> u32 { + ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) +} + +const PALETTE: [u32; 9] = [ + pack(255, 255, 255), // 1 white + pack(255, 0, 0), // 2 red + pack(0, 255, 0), // 3 green + pack(0, 0, 255), // 4 blue + pack(255, 255, 0), // 5 yellow + pack(255, 0, 255), // 6 magenta + pack(0, 255, 255), // 7 cyan + pack(255, 128, 0), // 8 orange + pack(0, 0, 0), // clear (black) +]; + +#[unsafe(no_mangle)] +pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { + // Create window + let title = b"paint"; + let win = (exports.wm_create)(rsapi::Line { ptr: title.as_ptr(), len: title.len() }, W, H); + if win < 0 { return 1; } + + // Get pixel buffer + let mut info = rsapi::WindowInfo { ptr: core::ptr::null_mut(), w: 0, h: 0 }; + if !(exports.wm_buffer)(win, &mut info) { + (exports.wm_destroy)(win); + return 1; + } + + let buf = info.ptr; + let bw = info.w as usize; + let bh = info.h as usize; + + let mut color: usize = 0; + let mut last_x: i32 = -1; + let mut last_y: i32 = -1; + + let mut ev = rsapi::WmEvent { + kind: 0, + key_kind: rsapi::WmKeyKind::Char, + key_char: 0, + mouse: rsapi::WmMouseEvent { x: 0, y: 0, buttons: 0 }, + focused: false, + _pad: [0; 4], + }; + + // Event loop + loop { + if !(exports.wm_recv)(&mut ev) { + continue; + } + match ev.kind { + 0 => { + // Key event + match ev.key_kind { + rsapi::WmKeyKind::Char => { + match ev.key_char { + b'1'..=b'8' => color = (ev.key_char - b'1') as usize, + b'c' => { + unsafe { + + let n = bw * bh; + let p = buf as *mut u32; + for i in 0..n { + *p.add(i) = 0; + } + } + (exports.wm_flush)(win); + } + _ => {} + } + } + rsapi::WmKeyKind::F4 => break, + _ => {} + } + } + 1 => { + // Mouse event + let m = &ev.mouse; + if m.buttons & 1 != 0 { + // Left button: draw pixel at (m.x, m.y) + if m.x >= 0 && m.y >= 0 && (m.x as usize) < bw && (m.y as usize) < bh { + unsafe { + + *buf.add(m.y as usize * bw + m.x as usize) = PALETTE[color % PALETTE.len()]; + } + // Connect with last point (line drawing) + if last_x >= 0 { + let lx = last_x; let ly = last_y; + let dx = (m.x - lx).abs(); let sx = if lx < m.x { 1 } else { -1 }; + let dy = -(m.y - ly).abs(); let sy = if ly < m.y { 1 } else { -1 }; + let mut err = dx + dy; + let mut cx = lx; let mut cy = ly; + loop { + if cx >= 0 && cy >= 0 && (cx as usize) < bw && (cy as usize) < bh { + unsafe { + *buf.add(cy as usize * bw + cx as usize) = PALETTE[color % PALETTE.len()]; } + } + if cx == m.x && cy == m.y { break; } + let e2 = 2 * err; + if e2 >= dy { err += dy; cx += sx; } + if e2 <= dx { err += dx; cy += sy; } + } + } + (exports.wm_flush)(win); + last_x = m.x; + last_y = m.y; + } + } else { + last_x = -1; last_y = -1; + } + } + 2 => {} // focus + 3 => break, // close + _ => {} + } + } + + (exports.wm_destroy)(win); + 0 +} diff --git a/rsapi/src/lib.rs b/rsapi/src/lib.rs index dfe75d6..e5f2119 100644 --- a/rsapi/src/lib.rs +++ b/rsapi/src/lib.rs @@ -105,6 +105,66 @@ pub struct PciDevice { pub device_id: u16, } +// ---- GUI types (v2 window/event) ---- + +/// Window buffer info returned by wm_buffer. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct WindowInfo { + pub ptr: *mut u32, + pub w: u32, + pub h: u32, +} + +/// Key event kind (flattened for ABI stability). +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum WmKeyKind { + Char = 0, + Enter = 1, + Backspace = 2, + Tab = 3, + Escape = 4, + CtrlC = 5, + ArrowUp = 10, + ArrowDown = 11, + ArrowLeft = 12, + ArrowRight = 13, + Home = 14, + End = 15, + PageUp = 16, + PageDown = 17, + Delete = 18, + F1 = 20, + F2 = 21, + F3 = 22, + F4 = 23, + Unknown = 255, +} + +/// Mouse button state. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct WmMouseEvent { + pub x: i32, + pub y: i32, + pub buttons: u8, +} + +/// One window manager event delivered to a GUI binary. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct WmEvent { + pub kind: u8, // 0=key, 1=mouse, 2=focus, 3=close + pub key_kind: WmKeyKind, + pub key_char: u8, + pub mouse: WmMouseEvent, + pub focused: bool, + pub _pad: [u8; 4], +} + + + /// Kernel version information. #[derive(Clone, Copy)] pub struct KernelVersion { @@ -167,4 +227,16 @@ pub struct Exports { pub klog_dump: fn(buf: *mut u8, cap: u64) -> u64, /// Fill `out` with kernel version info. pub kernel_version: fn(out: &mut KernelVersion), + + // ---- window/events (v2 GUI) ---- + /// Create a window; returns window id or -1. + pub wm_create: fn(title: Line, w: u32, h: u32) -> i32, + /// Get the window's pixel buffer info. + pub wm_buffer: fn(id: i32, out: &mut WindowInfo) -> bool, + /// Composite the window to screen. + pub wm_flush: fn(id: i32), + /// Destroy a window. + pub wm_destroy: fn(id: i32), + /// Non-blocking: receive one event. Returns true if an event was delivered. + pub wm_recv: fn(out: &mut WmEvent) -> bool, } diff --git a/shell/src/lib.rs b/shell/src/lib.rs index 11f5786..6de056c 100644 --- a/shell/src/lib.rs +++ b/shell/src/lib.rs @@ -1126,6 +1126,98 @@ fn abi_kernel_version(out: &mut rsapi::KernelVersion) { out.patch = patch; } + +/// ABI: create a window via the window manager. +fn abi_wm_create(title: rsapi::Line, w: u32, h: u32) -> i32 { + let s = title.as_str(); + match rsnix::wm::create_window(s, w as usize, h as usize) { + Some(id) => id as i32, + None => -1, + } +} + +/// ABI: get window pixel buffer info. +fn abi_wm_buffer(id: i32, out: &mut rsapi::WindowInfo) -> bool { + match rsnix::wm::buffer(id as usize) { + Some((ptr, w, h)) => { + out.ptr = ptr; + out.w = w as u32; + out.h = h as u32; + true + } + None => false, + } +} + +/// ABI: composite window to screen. +fn abi_wm_flush(id: i32) { + rsnix::wm::flush(id as usize); +} + +/// ABI: destroy a window. +fn abi_wm_destroy(id: i32) { + rsnix::wm::destroy(id as usize); +} + +/// ABI: non-blocking receive of one WM event. +fn abi_wm_recv(out: &mut rsapi::WmEvent) -> bool { + use rsnix::ps2::KeyEvent; + match rsnix::sched::recv() { + None => false, + Some(msg) => { + match msg { + rsnix::sched::TaskMsg::Key(k) => { + out.kind = 0; + match k { + KeyEvent::Char(c) => { + out.key_kind = rsapi::WmKeyKind::Char; + out.key_char = c as u8; + } + KeyEvent::Enter => { out.key_kind = rsapi::WmKeyKind::Enter; } + KeyEvent::Backspace => { out.key_kind = rsapi::WmKeyKind::Backspace; } + KeyEvent::Tab => { out.key_kind = rsapi::WmKeyKind::Tab; } + KeyEvent::Escape => { out.key_kind = rsapi::WmKeyKind::Escape; } + KeyEvent::Ctrl('c') => { out.key_kind = rsapi::WmKeyKind::CtrlC; } + KeyEvent::ArrowUp => { out.key_kind = rsapi::WmKeyKind::ArrowUp; } + KeyEvent::ArrowDown => { out.key_kind = rsapi::WmKeyKind::ArrowDown; } + KeyEvent::ArrowLeft => { out.key_kind = rsapi::WmKeyKind::ArrowLeft; } + KeyEvent::ArrowRight => { out.key_kind = rsapi::WmKeyKind::ArrowRight; } + KeyEvent::Home => { out.key_kind = rsapi::WmKeyKind::Home; } + KeyEvent::End => { out.key_kind = rsapi::WmKeyKind::End; } + KeyEvent::PageUp => { out.key_kind = rsapi::WmKeyKind::PageUp; } + KeyEvent::PageDown => { out.key_kind = rsapi::WmKeyKind::PageDown; } + KeyEvent::Delete => { out.key_kind = rsapi::WmKeyKind::Delete; } + KeyEvent::F(1) => { out.key_kind = rsapi::WmKeyKind::F1; } + KeyEvent::F(2) => { out.key_kind = rsapi::WmKeyKind::F2; } + KeyEvent::F(3) => { out.key_kind = rsapi::WmKeyKind::F3; } + KeyEvent::F(4) => { out.key_kind = rsapi::WmKeyKind::F4; } + _ => { out.key_kind = rsapi::WmKeyKind::Unknown; } + } + out.key_char = 0; + true + } + rsnix::sched::TaskMsg::AppMouse(m) => { + out.kind = 1; + out.mouse.x = m.x; + out.mouse.y = m.y; + out.mouse.buttons = m.buttons; + true + } + rsnix::sched::TaskMsg::Focus(f) => { + out.kind = 2; + out.focused = f; + true + } + rsnix::sched::TaskMsg::Close => { + out.kind = 3; + true + } + _ => false, + } + } + } +} + /// Construct the Exports table for this invocation. v2 fields are /// populated at runtime (binary callbacks → shell → kernel). fn build_exports() -> rsapi::Exports { @@ -1150,6 +1242,11 @@ fn build_exports() -> rsapi::Exports { pci_device: abi_pci_device, klog_dump: abi_klog_dump, kernel_version: abi_kernel_version, + wm_create: abi_wm_create, + wm_buffer: abi_wm_buffer, + wm_flush: abi_wm_flush, + wm_destroy: abi_wm_destroy, + wm_recv: abi_wm_recv, } } -- cgit v1.2.3