summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-27 17:23:34 +0200
committersalmonik <salmonik@tuta.io>2026-08-27 17:23:34 +0200
commitba304898b8e177d90afb58d201b4c3dd3c927c48 (patch)
tree3bcca98cb09ed8c035218039d9c86401d8ea0066
parenttool migration: v2 exports + 10 external CLI binaries (diff)
downloadrs-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.lock7
-rw-r--r--Cargo.toml2
-rw-r--r--bins/paint/Cargo.toml8
-rw-r--r--bins/paint/src/main.rs137
-rw-r--r--rsapi/src/lib.rs72
-rw-r--r--shell/src/lib.rs97
6 files changed, 322 insertions, 1 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 35ac614..a4accd7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -73,6 +73,13 @@ dependencies = [
73] 73]
74 74
75[[package]] 75[[package]]
76name = "paint"
77version = "0.6.0"
78dependencies = [
79 "rsapi",
80]
81
82[[package]]
76name = "pci" 83name = "pci"
77version = "0.6.0" 84version = "0.6.0"
78dependencies = [ 85dependencies = [
diff --git a/Cargo.toml b/Cargo.toml
index 42aa13f..fe02cfb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
1[workspace] 1[workspace]
2resolver = "2" 2resolver = "2"
3members = ["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"] 3members = ["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]
6version = "0.6.0" 6version = "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]
2name = "paint"
3version.workspace = true
4edition.workspace = true
5[lints]
6workspace = true
7[dependencies]
8rsapi = { 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]
9fn panic(_info: &core::panic::PanicInfo) -> ! {
10 loop { core::hint::spin_loop(); }
11}
12
13const W: u32 = 640;
14const H: u32 = 400;
15
16const fn pack(r: u8, g: u8, b: u8) -> u32 {
17 ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
18}
19
20const 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)]
33pub 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)]
113pub 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)]
122pub 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)]
148pub 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)]
157pub 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)]
110pub struct KernelVersion { 170pub 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.
1131fn 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.
1140fn 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.
1153fn abi_wm_flush(id: i32) {
1154 rsnix::wm::flush(id as usize);
1155}
1156
1157/// ABI: destroy a window.
1158fn abi_wm_destroy(id: i32) {
1159 rsnix::wm::destroy(id as usize);
1160}
1161
1162/// ABI: non-blocking receive of one WM event.
1163fn 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).
1131fn build_exports() -> rsapi::Exports { 1223fn 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