diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-27 17:23:34 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-27 17:23:34 +0200 |
| commit | ba304898b8e177d90afb58d201b4c3dd3c927c48 (patch) | |
| tree | 3bcca98cb09ed8c035218039d9c86401d8ea0066 | |
| parent | tool migration: v2 exports + 10 external CLI binaries (diff) | |
| download | rs-dos-ba304898b8e177d90afb58d201b4c3dd3c927c48.tar.gz rs-dos-ba304898b8e177d90afb58d201b4c3dd3c927c48.zip | |
GUI: window/event exports, paint binary, event-loop gate
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).
| -rw-r--r-- | Cargo.lock | 7 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | bins/paint/Cargo.toml | 8 | ||||
| -rw-r--r-- | bins/paint/src/main.rs | 137 | ||||
| -rw-r--r-- | rsapi/src/lib.rs | 72 | ||||
| -rw-r--r-- | shell/src/lib.rs | 97 |
6 files changed, 322 insertions, 1 deletions
| @@ -73,6 +73,13 @@ dependencies = [ | |||
| 73 | ] | 73 | ] |
| 74 | 74 | ||
| 75 | [[package]] | 75 | [[package]] |
| 76 | name = "paint" | ||
| 77 | version = "0.6.0" | ||
| 78 | dependencies = [ | ||
| 79 | "rsapi", | ||
| 80 | ] | ||
| 81 | |||
| 82 | [[package]] | ||
| 76 | name = "pci" | 83 | name = "pci" |
| 77 | version = "0.6.0" | 84 | version = "0.6.0" |
| 78 | dependencies = [ | 85 | dependencies = [ |
| @@ -1,6 +1,6 @@ | |||
| 1 | [workspace] | 1 | [workspace] |
| 2 | resolver = "2" | 2 | resolver = "2" |
| 3 | 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"] | 3 | 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"] |
| 4 | 4 | ||
| 5 | [workspace.package] | 5 | [workspace.package] |
| 6 | version = "0.6.0" | 6 | 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 @@ | |||
| 1 | [package] | ||
| 2 | name = "paint" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | [lints] | ||
| 6 | workspace = true | ||
| 7 | [dependencies] | ||
| 8 | 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 @@ | |||
| 1 | //! paint — drawing canvas via the GUI exports table. | ||
| 2 | //! | ||
| 3 | //! Keys: 1-8 select color, c clears to black. Mouse draws when pressed. | ||
| 4 | |||
| 5 | #![no_std] | ||
| 6 | #![no_main] | ||
| 7 | |||
| 8 | #[panic_handler] | ||
| 9 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 10 | loop { core::hint::spin_loop(); } | ||
| 11 | } | ||
| 12 | |||
| 13 | const W: u32 = 640; | ||
| 14 | const H: u32 = 400; | ||
| 15 | |||
| 16 | const fn pack(r: u8, g: u8, b: u8) -> u32 { | ||
| 17 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) | ||
| 18 | } | ||
| 19 | |||
| 20 | const PALETTE: [u32; 9] = [ | ||
| 21 | pack(255, 255, 255), // 1 white | ||
| 22 | pack(255, 0, 0), // 2 red | ||
| 23 | pack(0, 255, 0), // 3 green | ||
| 24 | pack(0, 0, 255), // 4 blue | ||
| 25 | pack(255, 255, 0), // 5 yellow | ||
| 26 | pack(255, 0, 255), // 6 magenta | ||
| 27 | pack(0, 255, 255), // 7 cyan | ||
| 28 | pack(255, 128, 0), // 8 orange | ||
| 29 | pack(0, 0, 0), // clear (black) | ||
| 30 | ]; | ||
| 31 | |||
| 32 | #[unsafe(no_mangle)] | ||
| 33 | pub extern "C" fn efi_main(exports: &'static rsapi::Exports, _argc: usize, _argv: *const rsapi::Arg) -> i32 { | ||
| 34 | // Create window | ||
| 35 | let title = b"paint"; | ||
| 36 | let win = (exports.wm_create)(rsapi::Line { ptr: title.as_ptr(), len: title.len() }, W, H); | ||
| 37 | if win < 0 { return 1; } | ||
| 38 | |||
| 39 | // Get pixel buffer | ||
| 40 | let mut info = rsapi::WindowInfo { ptr: core::ptr::null_mut(), w: 0, h: 0 }; | ||
| 41 | if !(exports.wm_buffer)(win, &mut info) { | ||
| 42 | (exports.wm_destroy)(win); | ||
| 43 | return 1; | ||
| 44 | } | ||
| 45 | |||
| 46 | let buf = info.ptr; | ||
| 47 | let bw = info.w as usize; | ||
| 48 | let bh = info.h as usize; | ||
| 49 | |||
| 50 | let mut color: usize = 0; | ||
| 51 | let mut last_x: i32 = -1; | ||
| 52 | let mut last_y: i32 = -1; | ||
| 53 | |||
| 54 | let mut ev = rsapi::WmEvent { | ||
| 55 | kind: 0, | ||
| 56 | key_kind: rsapi::WmKeyKind::Char, | ||
| 57 | key_char: 0, | ||
| 58 | mouse: rsapi::WmMouseEvent { x: 0, y: 0, buttons: 0 }, | ||
| 59 | focused: false, | ||
| 60 | _pad: [0; 4], | ||
| 61 | }; | ||
| 62 | |||
| 63 | // Event loop | ||
| 64 | loop { | ||
| 65 | if !(exports.wm_recv)(&mut ev) { | ||
| 66 | continue; | ||
| 67 | } | ||
| 68 | match ev.kind { | ||
| 69 | 0 => { | ||
| 70 | // Key event | ||
| 71 | match ev.key_kind { | ||
| 72 | rsapi::WmKeyKind::Char => { | ||
| 73 | match ev.key_char { | ||
| 74 | b'1'..=b'8' => color = (ev.key_char - b'1') as usize, | ||
| 75 | b'c' => { | ||
| 76 | unsafe { | ||
| 77 | |||
| 78 | let n = bw * bh; | ||
| 79 | let p = buf as *mut u32; | ||
| 80 | for i in 0..n { | ||
| 81 | *p.add(i) = 0; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | (exports.wm_flush)(win); | ||
| 85 | } | ||
| 86 | _ => {} | ||
| 87 | } | ||
| 88 | } | ||
| 89 | rsapi::WmKeyKind::F4 => break, | ||
| 90 | _ => {} | ||
| 91 | } | ||
| 92 | } | ||
| 93 | 1 => { | ||
| 94 | // Mouse event | ||
| 95 | let m = &ev.mouse; | ||
| 96 | if m.buttons & 1 != 0 { | ||
| 97 | // Left button: draw pixel at (m.x, m.y) | ||
| 98 | if m.x >= 0 && m.y >= 0 && (m.x as usize) < bw && (m.y as usize) < bh { | ||
| 99 | unsafe { | ||
| 100 | |||
| 101 | *buf.add(m.y as usize * bw + m.x as usize) = PALETTE[color % PALETTE.len()]; | ||
| 102 | } | ||
| 103 | // Connect with last point (line drawing) | ||
| 104 | if last_x >= 0 { | ||
| 105 | let lx = last_x; let ly = last_y; | ||
| 106 | let dx = (m.x - lx).abs(); let sx = if lx < m.x { 1 } else { -1 }; | ||
| 107 | let dy = -(m.y - ly).abs(); let sy = if ly < m.y { 1 } else { -1 }; | ||
| 108 | let mut err = dx + dy; | ||
| 109 | let mut cx = lx; let mut cy = ly; | ||
| 110 | loop { | ||
| 111 | if cx >= 0 && cy >= 0 && (cx as usize) < bw && (cy as usize) < bh { | ||
| 112 | unsafe { | ||
| 113 | *buf.add(cy as usize * bw + cx as usize) = PALETTE[color % PALETTE.len()]; } | ||
| 114 | } | ||
| 115 | if cx == m.x && cy == m.y { break; } | ||
| 116 | let e2 = 2 * err; | ||
| 117 | if e2 >= dy { err += dy; cx += sx; } | ||
| 118 | if e2 <= dx { err += dx; cy += sy; } | ||
| 119 | } | ||
| 120 | } | ||
| 121 | (exports.wm_flush)(win); | ||
| 122 | last_x = m.x; | ||
| 123 | last_y = m.y; | ||
| 124 | } | ||
| 125 | } else { | ||
| 126 | last_x = -1; last_y = -1; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | 2 => {} // focus | ||
| 130 | 3 => break, // close | ||
| 131 | _ => {} | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | (exports.wm_destroy)(win); | ||
| 136 | 0 | ||
| 137 | } | ||
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 { | |||
| 105 | pub device_id: u16, | 105 | pub device_id: u16, |
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | // ---- GUI types (v2 window/event) ---- | ||
| 109 | |||
| 110 | /// Window buffer info returned by wm_buffer. | ||
| 111 | #[repr(C)] | ||
| 112 | #[derive(Clone, Copy)] | ||
| 113 | pub struct WindowInfo { | ||
| 114 | pub ptr: *mut u32, | ||
| 115 | pub w: u32, | ||
| 116 | pub h: u32, | ||
| 117 | } | ||
| 118 | |||
| 119 | /// Key event kind (flattened for ABI stability). | ||
| 120 | #[repr(u8)] | ||
| 121 | #[derive(Clone, Copy, PartialEq, Eq)] | ||
| 122 | pub enum WmKeyKind { | ||
| 123 | Char = 0, | ||
| 124 | Enter = 1, | ||
| 125 | Backspace = 2, | ||
| 126 | Tab = 3, | ||
| 127 | Escape = 4, | ||
| 128 | CtrlC = 5, | ||
| 129 | ArrowUp = 10, | ||
| 130 | ArrowDown = 11, | ||
| 131 | ArrowLeft = 12, | ||
| 132 | ArrowRight = 13, | ||
| 133 | Home = 14, | ||
| 134 | End = 15, | ||
| 135 | PageUp = 16, | ||
| 136 | PageDown = 17, | ||
| 137 | Delete = 18, | ||
| 138 | F1 = 20, | ||
| 139 | F2 = 21, | ||
| 140 | F3 = 22, | ||
| 141 | F4 = 23, | ||
| 142 | Unknown = 255, | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Mouse button state. | ||
| 146 | #[repr(C)] | ||
| 147 | #[derive(Clone, Copy)] | ||
| 148 | pub struct WmMouseEvent { | ||
| 149 | pub x: i32, | ||
| 150 | pub y: i32, | ||
| 151 | pub buttons: u8, | ||
| 152 | } | ||
| 153 | |||
| 154 | /// One window manager event delivered to a GUI binary. | ||
| 155 | #[repr(C)] | ||
| 156 | #[derive(Clone, Copy)] | ||
| 157 | pub struct WmEvent { | ||
| 158 | pub kind: u8, // 0=key, 1=mouse, 2=focus, 3=close | ||
| 159 | pub key_kind: WmKeyKind, | ||
| 160 | pub key_char: u8, | ||
| 161 | pub mouse: WmMouseEvent, | ||
| 162 | pub focused: bool, | ||
| 163 | pub _pad: [u8; 4], | ||
| 164 | } | ||
| 165 | |||
| 166 | |||
| 167 | |||
| 108 | /// Kernel version information. | 168 | /// Kernel version information. |
| 109 | #[derive(Clone, Copy)] | 169 | #[derive(Clone, Copy)] |
| 110 | pub struct KernelVersion { | 170 | pub struct KernelVersion { |
| @@ -167,4 +227,16 @@ pub struct Exports { | |||
| 167 | pub klog_dump: fn(buf: *mut u8, cap: u64) -> u64, | 227 | pub klog_dump: fn(buf: *mut u8, cap: u64) -> u64, |
| 168 | /// Fill `out` with kernel version info. | 228 | /// Fill `out` with kernel version info. |
| 169 | pub kernel_version: fn(out: &mut KernelVersion), | 229 | pub kernel_version: fn(out: &mut KernelVersion), |
| 230 | |||
| 231 | // ---- window/events (v2 GUI) ---- | ||
| 232 | /// Create a window; returns window id or -1. | ||
| 233 | pub wm_create: fn(title: Line, w: u32, h: u32) -> i32, | ||
| 234 | /// Get the window's pixel buffer info. | ||
| 235 | pub wm_buffer: fn(id: i32, out: &mut WindowInfo) -> bool, | ||
| 236 | /// Composite the window to screen. | ||
| 237 | pub wm_flush: fn(id: i32), | ||
| 238 | /// Destroy a window. | ||
| 239 | pub wm_destroy: fn(id: i32), | ||
| 240 | /// Non-blocking: receive one event. Returns true if an event was delivered. | ||
| 241 | pub wm_recv: fn(out: &mut WmEvent) -> bool, | ||
| 170 | } | 242 | } |
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) { | |||
| 1126 | out.patch = patch; | 1126 | out.patch = patch; |
| 1127 | } | 1127 | } |
| 1128 | 1128 | ||
| 1129 | |||
| 1130 | /// ABI: create a window via the window manager. | ||
| 1131 | fn abi_wm_create(title: rsapi::Line, w: u32, h: u32) -> i32 { | ||
| 1132 | let s = title.as_str(); | ||
| 1133 | match rsnix::wm::create_window(s, w as usize, h as usize) { | ||
| 1134 | Some(id) => id as i32, | ||
| 1135 | None => -1, | ||
| 1136 | } | ||
| 1137 | } | ||
| 1138 | |||
| 1139 | /// ABI: get window pixel buffer info. | ||
| 1140 | fn abi_wm_buffer(id: i32, out: &mut rsapi::WindowInfo) -> bool { | ||
| 1141 | match rsnix::wm::buffer(id as usize) { | ||
| 1142 | Some((ptr, w, h)) => { | ||
| 1143 | out.ptr = ptr; | ||
| 1144 | out.w = w as u32; | ||
| 1145 | out.h = h as u32; | ||
| 1146 | true | ||
| 1147 | } | ||
| 1148 | None => false, | ||
| 1149 | } | ||
| 1150 | } | ||
| 1151 | |||
| 1152 | /// ABI: composite window to screen. | ||
| 1153 | fn abi_wm_flush(id: i32) { | ||
| 1154 | rsnix::wm::flush(id as usize); | ||
| 1155 | } | ||
| 1156 | |||
| 1157 | /// ABI: destroy a window. | ||
| 1158 | fn abi_wm_destroy(id: i32) { | ||
| 1159 | rsnix::wm::destroy(id as usize); | ||
| 1160 | } | ||
| 1161 | |||
| 1162 | /// ABI: non-blocking receive of one WM event. | ||
| 1163 | fn abi_wm_recv(out: &mut rsapi::WmEvent) -> bool { | ||
| 1164 | use rsnix::ps2::KeyEvent; | ||
| 1165 | match rsnix::sched::recv() { | ||
| 1166 | None => false, | ||
| 1167 | Some(msg) => { | ||
| 1168 | match msg { | ||
| 1169 | rsnix::sched::TaskMsg::Key(k) => { | ||
| 1170 | out.kind = 0; | ||
| 1171 | match k { | ||
| 1172 | KeyEvent::Char(c) => { | ||
| 1173 | out.key_kind = rsapi::WmKeyKind::Char; | ||
| 1174 | out.key_char = c as u8; | ||
| 1175 | } | ||
| 1176 | KeyEvent::Enter => { out.key_kind = rsapi::WmKeyKind::Enter; } | ||
| 1177 | KeyEvent::Backspace => { out.key_kind = rsapi::WmKeyKind::Backspace; } | ||
| 1178 | KeyEvent::Tab => { out.key_kind = rsapi::WmKeyKind::Tab; } | ||
| 1179 | KeyEvent::Escape => { out.key_kind = rsapi::WmKeyKind::Escape; } | ||
| 1180 | KeyEvent::Ctrl('c') => { out.key_kind = rsapi::WmKeyKind::CtrlC; } | ||
| 1181 | KeyEvent::ArrowUp => { out.key_kind = rsapi::WmKeyKind::ArrowUp; } | ||
| 1182 | KeyEvent::ArrowDown => { out.key_kind = rsapi::WmKeyKind::ArrowDown; } | ||
| 1183 | KeyEvent::ArrowLeft => { out.key_kind = rsapi::WmKeyKind::ArrowLeft; } | ||
| 1184 | KeyEvent::ArrowRight => { out.key_kind = rsapi::WmKeyKind::ArrowRight; } | ||
| 1185 | KeyEvent::Home => { out.key_kind = rsapi::WmKeyKind::Home; } | ||
| 1186 | KeyEvent::End => { out.key_kind = rsapi::WmKeyKind::End; } | ||
| 1187 | KeyEvent::PageUp => { out.key_kind = rsapi::WmKeyKind::PageUp; } | ||
| 1188 | KeyEvent::PageDown => { out.key_kind = rsapi::WmKeyKind::PageDown; } | ||
| 1189 | KeyEvent::Delete => { out.key_kind = rsapi::WmKeyKind::Delete; } | ||
| 1190 | KeyEvent::F(1) => { out.key_kind = rsapi::WmKeyKind::F1; } | ||
| 1191 | KeyEvent::F(2) => { out.key_kind = rsapi::WmKeyKind::F2; } | ||
| 1192 | KeyEvent::F(3) => { out.key_kind = rsapi::WmKeyKind::F3; } | ||
| 1193 | KeyEvent::F(4) => { out.key_kind = rsapi::WmKeyKind::F4; } | ||
| 1194 | _ => { out.key_kind = rsapi::WmKeyKind::Unknown; } | ||
| 1195 | } | ||
| 1196 | out.key_char = 0; | ||
| 1197 | true | ||
| 1198 | } | ||
| 1199 | rsnix::sched::TaskMsg::AppMouse(m) => { | ||
| 1200 | out.kind = 1; | ||
| 1201 | out.mouse.x = m.x; | ||
| 1202 | out.mouse.y = m.y; | ||
| 1203 | out.mouse.buttons = m.buttons; | ||
| 1204 | true | ||
| 1205 | } | ||
| 1206 | rsnix::sched::TaskMsg::Focus(f) => { | ||
| 1207 | out.kind = 2; | ||
| 1208 | out.focused = f; | ||
| 1209 | true | ||
| 1210 | } | ||
| 1211 | rsnix::sched::TaskMsg::Close => { | ||
| 1212 | out.kind = 3; | ||
| 1213 | true | ||
| 1214 | } | ||
| 1215 | _ => false, | ||
| 1216 | } | ||
| 1217 | } | ||
| 1218 | } | ||
| 1219 | } | ||
| 1220 | |||
| 1129 | /// Construct the Exports table for this invocation. v2 fields are | 1221 | /// Construct the Exports table for this invocation. v2 fields are |
| 1130 | /// populated at runtime (binary callbacks → shell → kernel). | 1222 | /// populated at runtime (binary callbacks → shell → kernel). |
| 1131 | fn build_exports() -> rsapi::Exports { | 1223 | fn build_exports() -> rsapi::Exports { |
| @@ -1150,6 +1242,11 @@ fn build_exports() -> rsapi::Exports { | |||
| 1150 | pci_device: abi_pci_device, | 1242 | pci_device: abi_pci_device, |
| 1151 | klog_dump: abi_klog_dump, | 1243 | klog_dump: abi_klog_dump, |
| 1152 | kernel_version: abi_kernel_version, | 1244 | kernel_version: abi_kernel_version, |
| 1245 | wm_create: abi_wm_create, | ||
| 1246 | wm_buffer: abi_wm_buffer, | ||
| 1247 | wm_flush: abi_wm_flush, | ||
| 1248 | wm_destroy: abi_wm_destroy, | ||
| 1249 | wm_recv: abi_wm_recv, | ||
| 1153 | } | 1250 | } |
| 1154 | } | 1251 | } |
| 1155 | 1252 | ||
