Skip to main content

atmos/kernel/
user_win.rs

1// user_win.rs - EL0 アプリ用ウィンドウブリッジ(Phase 4: GUI アプリの EL0 化)
2//
3// EL0 プロセスはフレームバッファや WM 構造体に直接触れない(MMU で遮断)。
4// 代わりに以下のシステムコール ABI でウィンドウを操作する:
5//   SYS_WIN_CREATE: ウィンドウ生成(カーネル側に UserApp プロキシを登録)
6//   SYS_WIN_BLIT  : ユーザ空間のピクセル列をウィンドウへ提出(カーネルがコピー)
7//   SYS_WIN_POLL  : 入力イベント(キー/マウス/クローズ)を1件取得
8//   SYS_WIN_CLOSE : ウィンドウを閉じる
9//
10// カーネル側では UserApp(App トレイト実装のプロキシ)が WM に住み、
11// 共有ステート(ピクセル・イベントキュー)を介して EL0 プロセスと通信する。
12// EL0 側がフォルトしても落ちるのはそのプロセスだけで、UserApp はウィンドウを
13// "Process Terminated" 表示に切り替えるだけで OS は継続する。
14#![allow(dead_code)]
15// EL0 境界モジュール: パニックにつながる操作を deny で強制排除
16#![deny(
17    clippy::unwrap_used,
18    clippy::expect_used,
19    clippy::panic,
20    clippy::indexing_slicing
21)]
22
23use alloc::collections::VecDeque;
24use alloc::string::String;
25use alloc::sync::Arc;
26use alloc::vec::Vec;
27use spin::Mutex;
28
29/// イベントのエンコード (u64):
30/// bits 56..63 = 種別 (1=Key, 2=Mouse, 3=Closed)
31/// Key  : bits 0..7 = keycode, bit 8 = pressed, bits 16..31 = ascii (0 なら None)
32/// Mouse: bits 0..15 = x (i16), bits 16..31 = y (i16), bit 32 = 左ボタン, bits 40..47 = wheel (i8)
33pub const EV_KEY: u64 = 1 << 56;
34pub const EV_MOUSE: u64 = 2 << 56;
35pub const EV_CLOSED: u64 = 3 << 56;
36
37const MAX_EVENTS: usize = 64;
38const MAX_DIM: u32 = 2048;
39
40/// EL0 プロセスとカーネル側 UserApp が共有するウィンドウ状態
41pub struct UserWinShared {
42    pub width: u32,
43    pub height: u32,
44    /// 提出済みピクセル (ARGB8888, width*height)。EL0 から SYS_WIN_BLIT でコピーされる
45    pub pixels: Vec<u32>,
46    /// UserApp が積み、EL0 が SYS_WIN_POLL で取り出す入力イベント
47    pub events: VecDeque<u64>,
48    /// ウィンドウがまだ開いているか(false = ユーザが閉じた / プロセスが閉じた)
49    pub open: bool,
50    /// 新しいピクセルが提出されたか
51    pub fresh: bool,
52}
53
54struct Registry {
55    next_handle: usize,
56    wins: Vec<(usize, Arc<Mutex<UserWinShared>>)>,
57}
58
59static REGISTRY: Mutex<Registry> = Mutex::new(Registry {
60    next_handle: 1,
61    wins: Vec::new(),
62});
63
64fn lookup(handle: usize) -> Option<Arc<Mutex<UserWinShared>>> {
65    let reg = REGISTRY.lock();
66    reg.wins
67        .iter()
68        .find(|(h, _)| *h == handle)
69        .map(|(_, s)| s.clone())
70}
71
72/// EL0 アプリの代理として WM に住むプロキシアプリ
73pub struct UserApp {
74    shared: Arc<Mutex<UserWinShared>>,
75    title: String,
76}
77
78impl crate::kernel::window_mgr::App for UserApp {
79    fn name(&self) -> &str {
80        "user_app"
81    }
82
83    fn instance_info(&self) -> String {
84        self.title.clone()
85    }
86
87    fn draw(
88        &mut self,
89        screen: &crate::kernel::draw::Screen,
90        win_x: u32,
91        win_y: u32,
92        win_w: u32,
93        win_h: u32,
94    ) {
95        let sh = self.shared.lock();
96        let t = &crate::kernel::config::get_config().theme;
97        if !sh.open {
98            // EL0 プロセス側が終了済み
99            screen.boxfill(
100                win_x,
101                win_y,
102                win_x + win_w,
103                win_y + win_h,
104                crate::kernel::draw::Color(t.ui_overlay_bg),
105            );
106            screen.draw_string_vector(
107                win_x + 16,
108                win_y + 16,
109                "Process Terminated",
110                crate::kernel::draw::Color(t.error_fg),
111                16,
112            );
113            return;
114        }
115        // 背景
116        screen.boxfill(
117            win_x,
118            win_y,
119            win_x + win_w,
120            win_y + win_h,
121            crate::kernel::draw::Color(t.terminal_bg),
122        );
123        // 提出ピクセルを 1:1 でブリット(ウィンドウ領域にクリップ)
124        let bw = sh.width.min(win_w);
125        let bh = sh.height.min(win_h);
126        for row in 0..bh {
127            for col in 0..bw {
128                let px = sh
129                    .pixels
130                    .get((row * sh.width + col) as usize)
131                    .copied()
132                    .unwrap_or(0xFF000000);
133                screen.draw_pixel(win_x + col, win_y + row, crate::kernel::draw::Color(px));
134            }
135        }
136    }
137
138    fn on_key(&mut self, keycode: u8, pressed: bool, ascii: Option<char>) {
139        let mut sh = self.shared.lock();
140        if sh.events.len() >= MAX_EVENTS {
141            sh.events.pop_front();
142        }
143        let mut ev = EV_KEY | (keycode as u64) | ((pressed as u64) << 8);
144        if let Some(c) = ascii {
145            ev |= ((c as u32 as u64) & 0xFFFF) << 16;
146        }
147        sh.events.push_back(ev);
148    }
149
150    fn on_mouse(
151        &mut self,
152        local_x: i32,
153        local_y: i32,
154        btn_left: bool,
155        _btn_right: bool,
156        wheel: i32,
157    ) {
158        let mut sh = self.shared.lock();
159        if sh.events.len() >= MAX_EVENTS {
160            sh.events.pop_front();
161        }
162        let ev = EV_MOUSE
163            | (local_x as i16 as u16 as u64)
164            | ((local_y as i16 as u16 as u64) << 16)
165            | ((btn_left as u64) << 32)
166            | (((wheel as i8 as u8) as u64) << 40);
167        sh.events.push_back(ev);
168    }
169
170    fn close_tab(&mut self, _idx: usize) -> bool {
171        // ユーザがウィンドウを閉じた: EL0 側に通知してウィンドウは破棄
172        let mut sh = self.shared.lock();
173        sh.open = false;
174        sh.events.push_back(EV_CLOSED);
175        true
176    }
177}
178
179impl Drop for UserApp {
180    /// どの経路でウィンドウが破棄されても(Alt+W・X ボタン・WM 内部処理)、
181    /// EL0 プロセスへ確実に CLOSED を通知してポーリングループから抜けさせる。
182    fn drop(&mut self) {
183        let mut sh = self.shared.lock();
184        if sh.open {
185            sh.open = false;
186            sh.events.push_back(EV_CLOSED);
187        }
188    }
189}
190
191// ---------------------------------------------------------------------------
192// システムコール実装 (syscall.rs から呼ばれる。EL1・呼び出し元プロセスの文脈)
193// ---------------------------------------------------------------------------
194
195use crate::kernel::usercopy;
196
197/// SYS_WIN_CREATE: x0 = リクエスト構造体ポインタ [title_ptr, title_len, width, height]
198/// 戻り値: ウィンドウハンドル(0 = 失敗)
199///
200/// 事前条件: req_ptr はユーザ RAM 内・8 バイト境界。タイトルは 64 バイト以下の UTF-8。
201/// 幅・高さは 1..=MAX_DIM。ヒープ残量が不足する場合も失敗を返す。
202pub fn sys_win_create(req_ptr: usize) -> usize {
203    let Some(&[title_ptr, title_len, w_raw, h_raw]) = usercopy::user_slice::<usize>(req_ptr, 4)
204    else {
205        return 0;
206    };
207    let (w, h) = (w_raw as u32, h_raw as u32);
208
209    if w == 0 || h == 0 || w > MAX_DIM || h > MAX_DIM {
210        return 0;
211    }
212
213    let title = match usercopy::user_str(title_ptr, title_len, 64) {
214        Some(s) => String::from(s),
215        None => return 0,
216    };
217
218    // OOM ガード: ピクセルバッファ + ウィンドウバッファ + 余裕
219    let need = (w as usize * h as usize * 4)
220        .saturating_mul(2)
221        .saturating_add(64 * 1024);
222    let (used, total) = crate::kernel::allocator::get_heap_stats();
223    if total.saturating_sub(used) < need {
224        return 0;
225    }
226
227    let shared = Arc::new(Mutex::new(UserWinShared {
228        width: w,
229        height: h,
230        pixels: alloc::vec![0xFF000000u32; (w * h) as usize],
231        events: VecDeque::new(),
232        open: true,
233        fresh: false,
234    }));
235
236    let handle = {
237        let mut reg = REGISTRY.lock();
238        let h_id = reg.next_handle;
239        reg.next_handle += 1;
240        reg.wins.push((h_id, shared.clone()));
241        h_id
242    };
243
244    let app = UserApp {
245        shared,
246        title: title.clone(),
247    };
248    // タイトルバー(31px)とボーダーぶんを足したウィンドウサイズで WM に登録
249    let win = crate::kernel::window_mgr::Window::new(
250        160,
251        120,
252        w + 8,
253        h + 40,
254        &title,
255        alloc::boxed::Box::new(app),
256    );
257    crate::kernel::window_mgr::get_instance().add_window(win);
258
259    crate::info!(
260        "[user_win] EL0 window created: handle={} '{}' {}x{}",
261        handle,
262        title,
263        w,
264        h
265    );
266    handle
267}
268
269/// SYS_WIN_BLIT: x0 = handle, x1 = ピクセルポインタ (u32 ARGB, width*height 個)
270/// 戻り値: 0 = 成功, usize::MAX = 失敗
271///
272/// 事前条件: handle は有効・ウィンドウが open。pix_ptr はユーザ RAM 内・
273/// 4 バイト境界・width*height 個ぶんの長さを持つこと。
274pub fn sys_win_blit(handle: usize, pix_ptr: usize) -> usize {
275    let Some(shared) = lookup(handle) else {
276        return usize::MAX;
277    };
278    let mut sh = shared.lock();
279    if !sh.open {
280        return usize::MAX;
281    }
282    let count = (sh.width * sh.height) as usize;
283    let Some(src) = usercopy::user_slice::<u32>(pix_ptr, count) else {
284        return usize::MAX;
285    };
286    sh.pixels.copy_from_slice(src);
287    sh.fresh = true;
288    drop(sh);
289    // 再描画を要求
290    crate::kernel::window_mgr::get_instance().dirty = true;
291    0
292}
293
294/// SYS_WIN_POLL: x0 = handle, x1 = イベント書き込み先ポインタ (u64)
295/// 戻り値: 1 = イベントあり, 0 = なし, usize::MAX = 失敗
296///
297/// 事前条件: handle は有効。out_ptr はユーザ RAM 内・8 バイト境界。
298pub fn sys_win_poll(handle: usize, out_ptr: usize) -> usize {
299    if !usercopy::user_ptr_ok::<u64>(out_ptr, 1) {
300        return usize::MAX;
301    }
302    let Some(shared) = lookup(handle) else {
303        return usize::MAX;
304    };
305    let mut sh = shared.lock();
306    if let Some(ev) = sh.events.pop_front() {
307        usercopy::write_user::<u64>(out_ptr, ev);
308        1
309    } else if !sh.open {
310        usercopy::write_user::<u64>(out_ptr, EV_CLOSED);
311        1
312    } else {
313        0
314    }
315}
316
317/// SYS_WIN_CLOSE: x0 = handle。EL0 側からウィンドウを閉じる
318pub fn sys_win_close(handle: usize) -> usize {
319    let Some(shared) = lookup(handle) else {
320        return usize::MAX;
321    };
322    {
323        let mut sh = shared.lock();
324        sh.open = false;
325    }
326    {
327        let mut reg = REGISTRY.lock();
328        reg.wins.retain(|(h, _)| *h != handle);
329    }
330    crate::kernel::window_mgr::get_instance().dirty = true;
331    0
332}