Skip to main content

atmos/kernel/
window_mgr.rs

1//! 多層ウィンドウマネージャ。
2//!
3//! [`App`] トレイトを実装する各アプリ([`crate::apps`])をウィンドウとして重ね合わせ表示・
4//! 入力ルーティングする。スタートボタン・タスクバーは持たず、ウィンドウは重ね合わせ型
5//! (`spec/README.md`「UI 方針」)。`ProxyApp` はバックグラウンドスレッドで動くアプリ
6//! (別コアで動く `app_runner_entry`)をメインループ側の `App` として橋渡しする。
7// src/window_mgr.rs - Multi-layered Window Manager for AtmOS
8#![allow(dead_code)]
9
10extern crate alloc;
11use alloc::boxed::Box;
12use alloc::string::String;
13use alloc::vec::Vec;
14
15use crate::kernel::draw::{Color, Screen};
16
17/// アプリケーションが実装すべきトレイト
18pub trait App {
19    fn name(&self) -> &str;
20
21    /// ProxyApp であるかどうか
22    fn is_proxy(&self) -> bool {
23        false
24    }
25
26    /// 共有されるバックバッファを返す
27    fn get_shared_buffer(&self) -> Option<alloc::sync::Arc<spin::Mutex<alloc::vec::Vec<u32>>>> {
28        None
29    }
30
31    /// インスタンス特有の情報(ファイル名やURLなど)を返す
32    fn instance_info(&self) -> alloc::string::String {
33        alloc::string::String::new()
34    }
35
36    /// ウィンドウ内の描画要求
37    fn draw(&mut self, screen: &Screen, win_x: u32, win_y: u32, win_w: u32, win_h: u32);
38
39    /// マウスイベント。ローカル座標とホイール移動量が渡される。
40    fn on_mouse(&mut self, local_x: i32, local_y: i32, btn_left: bool, btn_right: bool, wheel: i32);
41
42    /// キーボードイベント
43    fn on_key(&mut self, keycode: u8, pressed: bool, ascii: Option<char>);
44
45    // -----------------------------------------------------------------------
46    // OS一元管理型 縦タブ API (デフォルト実装付き)
47    // -----------------------------------------------------------------------
48
49    /// タブタイトル一覧。空を返すとタブなし扱い(非推奨: 全アプリは最低1タブ持つ)
50    fn tabs(&self) -> alloc::vec::Vec<alloc::string::String> {
51        alloc::vec![alloc::string::String::from(self.name())]
52    }
53    fn active_tab(&self) -> usize {
54        0
55    }
56    fn switch_tab(&mut self, _idx: usize) {}
57    fn add_tab(&mut self) {}
58    /// タブを閉じる。最後のタブが閉じられウィンドウも閉じる場合は true を返す。
59    fn close_tab(&mut self, _idx: usize) -> bool {
60        true
61    }
62
63    /// Alt+O: ファイルオープンダイアログを開く(未実装アプリはno-op)
64    fn open_file_dialog(&mut self) {}
65    /// Alt+S: ファイルを保存する(未実装アプリはno-op)
66    fn save_file(&mut self) {}
67
68    /// このアプリの各タブ用バックグラウンドスレッド関数を返す。
69    /// Some(fn) を返したアプリはタブ追加時にスケジューラでスレッドを起動する。
70    /// スレッド関数は引数なしの fn() で、グローバル Mutex 経由でアプリ状態と通信する。
71    fn tab_thread_fn(&self) -> Option<fn()> {
72        None
73    }
74
75    // DOM値操作用API (デフォルト実装付き)
76    fn get_dom_value(&self, _id: &str) -> Option<alloc::string::String> {
77        None
78    }
79    fn set_dom_value(&mut self, _id: &str, _val: &str) {}
80
81    // タイマーベースの再描画要求(ローディングアニメーション用など)
82    fn needs_timer_redraw(&self) -> bool {
83        false
84    }
85
86    /// ProxyApp の shared_buffer が更新されたことを通知する AtomicBool を返す。
87    /// ProxyApp でない通常のアプリは None を返す(デフォルト実装)。
88    /// Core 0 の dirty 判定がこのフラグを読んで矩形を登録する。
89    fn get_buffer_updated_flag(&self) -> Option<alloc::sync::Arc<core::sync::atomic::AtomicBool>> {
90        None
91    }
92
93    // DOMコンテンツ操作用API (デフォルト実装付き)
94    fn get_dom_content(&self, _id: &str) -> Option<alloc::string::String> {
95        None
96    }
97    fn set_dom_content(&mut self, _id: &str, _val: &str) {}
98
99    /// ウィンドウサイズ変更の通知
100    fn on_resize(&mut self, _width: u32, _height: u32) {}
101}
102
103use alloc::sync::Arc;
104use spin::Mutex;
105use core::sync::atomic::AtomicBool;
106
107#[derive(Clone, Debug)]
108pub enum AppEvent {
109    Mouse {
110        local_x: i32,
111        local_y: i32,
112        btn_left: bool,
113        btn_right: bool,
114        wheel: i32,
115    },
116    Key {
117        keycode: u8,
118        pressed: bool,
119        ascii: Option<char>,
120    },
121    SwitchTab(usize),
122    AddTab,
123    CloseTab(usize),
124    Resize {
125        width: u32,
126        height: u32,
127    },
128}
129
130pub struct ProxyApp {
131    pub name: String,
132    pub shared_buffer: Arc<Mutex<Vec<u32>>>,
133    pub event_queue: Arc<Mutex<Vec<AppEvent>>>,
134    pub is_alive: Arc<AtomicBool>,
135    pub active_tab: Arc<Mutex<usize>>,
136    pub tabs: Arc<Mutex<Vec<String>>>,
137    pub needs_timer_redraw: Arc<AtomicBool>,
138    /// app_runner_entry 側が shared_buffer を更新したらセットされるフラグ。
139    /// Core 0 の dirty 判定がこれを読んでウィンドウ矩形を dirty 登録し、クリアする。
140    pub buffer_updated: Arc<AtomicBool>,
141}
142
143impl ProxyApp {
144    pub fn new(
145        name: &str,
146        shared_buffer: Arc<Mutex<Vec<u32>>>,
147        event_queue: Arc<Mutex<Vec<AppEvent>>>,
148        is_alive: Arc<AtomicBool>,
149        active_tab: Arc<Mutex<usize>>,
150        tabs: Arc<Mutex<Vec<String>>>,
151        needs_timer_redraw: Arc<AtomicBool>,
152        buffer_updated: Arc<AtomicBool>,
153    ) -> Self {
154        Self {
155            name: String::from(name),
156            shared_buffer,
157            event_queue,
158            is_alive,
159            active_tab,
160            tabs,
161            needs_timer_redraw,
162            buffer_updated,
163        }
164    }
165}
166
167impl App for ProxyApp {
168    fn name(&self) -> &str {
169        &self.name
170    }
171
172    fn is_proxy(&self) -> bool {
173        true
174    }
175
176    fn get_shared_buffer(&self) -> Option<alloc::sync::Arc<spin::Mutex<alloc::vec::Vec<u32>>>> {
177        Some(self.shared_buffer.clone())
178    }
179
180    fn draw(&mut self, _screen: &Screen, _win_x: u32, _win_y: u32, _win_w: u32, _win_h: u32) {
181        // ProxyApp 自体の draw() は、Core 0 側の draw_windows で ProxyApp を検知して
182        // 特殊な memcpy 処理を行うため、ここでは何もしない(あるいは no-op)。
183    }
184
185    fn on_mouse(&mut self, local_x: i32, local_y: i32, btn_left: bool, btn_right: bool, wheel: i32) {
186        let mut q = self.event_queue.lock();
187        q.push(AppEvent::Mouse {
188            local_x,
189            local_y,
190            btn_left,
191            btn_right,
192            wheel,
193        });
194    }
195
196    fn on_key(&mut self, keycode: u8, pressed: bool, ascii: Option<char>) {
197        let mut q = self.event_queue.lock();
198        q.push(AppEvent::Key {
199            keycode,
200            pressed,
201            ascii,
202        });
203    }
204
205    fn tabs(&self) -> alloc::vec::Vec<alloc::string::String> {
206        let t = self.tabs.lock();
207        t.clone()
208    }
209
210    fn active_tab(&self) -> usize {
211        let idx = self.active_tab.lock();
212        *idx
213    }
214
215    fn switch_tab(&mut self, idx: usize) {
216        let mut a = self.active_tab.lock();
217        *a = idx;
218        let mut q = self.event_queue.lock();
219        q.push(AppEvent::SwitchTab(idx));
220    }
221
222    fn add_tab(&mut self) {
223        let mut q = self.event_queue.lock();
224        q.push(AppEvent::AddTab);
225    }
226
227    fn close_tab(&mut self, idx: usize) -> bool {
228        let mut q = self.event_queue.lock();
229        q.push(AppEvent::CloseTab(idx));
230        let t = self.tabs.lock();
231        t.len() <= 1
232    }
233
234    fn needs_timer_redraw(&self) -> bool {
235        self.needs_timer_redraw.load(Ordering::Relaxed)
236    }
237
238    fn get_buffer_updated_flag(&self) -> Option<alloc::sync::Arc<core::sync::atomic::AtomicBool>> {
239        Some(self.buffer_updated.clone())
240    }
241
242    fn on_resize(&mut self, width: u32, height: u32) {
243        let mut q = self.event_queue.lock();
244        q.retain(|e| !matches!(e, AppEvent::Resize { .. }));
245        q.push(AppEvent::Resize { width, height });
246    }
247}
248
249impl Drop for ProxyApp {
250    fn drop(&mut self) {
251        self.is_alive.store(false, Ordering::Relaxed);
252    }
253}
254
255pub struct AppRunnerParams {
256    pub app: Box<dyn App>,
257    pub shared_buffer: Arc<Mutex<Vec<u32>>>,
258    pub event_queue: Arc<Mutex<Vec<AppEvent>>>,
259    pub is_alive: Arc<AtomicBool>,
260    pub active_tab: Arc<Mutex<usize>>,
261    pub tabs: Arc<Mutex<Vec<String>>>,
262    pub needs_timer_redraw: Arc<AtomicBool>,
263    /// shared_buffer 更新通知フラグ(Core 0 への dirty 伝達用)
264    pub buffer_updated: Arc<AtomicBool>,
265    pub width: u32,
266    pub height: u32,
267}
268
269/// アプリのスレッドが描いている共有バッファ(画面へ出すウィンドウのバッファ)。
270/// 動画の再生スレッドが、アプリの描画が詰まっている間もフレームを直接書くために使う
271/// (`web_engine::video` の動画レイヤ)。
272#[derive(Clone)]
273pub struct DrawTarget {
274    pub buffer: Arc<Mutex<Vec<u32>>>,
275    pub width: u32,
276    pub height: u32,
277    pub updated: Arc<AtomicBool>,
278}
279
280/// プロセス ID → そのアプリのスレッドの描画先。`app_runner_entry` が登録・更新・削除する。
281static DRAW_TARGETS: Mutex<alloc::collections::BTreeMap<usize, DrawTarget>> =
282    Mutex::new(alloc::collections::BTreeMap::new());
283
284/// 今のスレッド(アプリの描画中)の描画先。
285pub fn current_draw_target() -> Option<DrawTarget> {
286    let pid = crate::kernel::scheduler::get_current_process_id()?;
287    DRAW_TARGETS.lock().get(&pid).cloned()
288}
289
290fn register_draw_target(params: &AppRunnerParams, w: u32, h: u32) {
291    if let Some(pid) = crate::kernel::scheduler::get_current_process_id() {
292        DRAW_TARGETS.lock().insert(
293            pid,
294            DrawTarget { buffer: params.shared_buffer.clone(), width: w, height: h, updated: params.buffer_updated.clone() },
295        );
296    }
297}
298
299pub fn app_runner_entry(arg: usize) {
300    let params_ptr = arg as *mut AppRunnerParams;
301    let mut params = unsafe { Box::from_raw(params_ptr) };
302
303    let mut w = params.width;
304    let mut h = params.height;
305
306    // スレッドローカルなバックバッファを用意してロック競合を回避
307    let mut local_buf = alloc::vec![0u32; (w * h) as usize];
308    register_draw_target(&params, w, h);
309
310    let mut last_draw_time = 0;
311
312    while params.is_alive.load(Ordering::Relaxed) {
313        let current_time = crate::kernel::timer::get_ticks();
314
315        // OS側に現在のアプリの needs_timer_redraw 状態をアトミックに同期
316        let app_needs_timer = params.app.needs_timer_redraw();
317        params.needs_timer_redraw.store(app_needs_timer, Ordering::Relaxed);
318
319        // 1. イベントの処理
320        let events = {
321            let mut q = params.event_queue.lock();
322            let evts = q.clone();
323            q.clear();
324            evts
325        };
326
327        let mut need_redraw = false;
328
329        for evt in events {
330            match evt {
331                AppEvent::Mouse { local_x, local_y, btn_left, btn_right, wheel } => {
332                    params.app.on_mouse(local_x, local_y, btn_left, btn_right, wheel);
333                    if btn_left || btn_right || wheel != 0 {
334                        need_redraw = true;
335                    }
336                }
337                AppEvent::Key { keycode, pressed, ascii } => {
338                    params.app.on_key(keycode, pressed, ascii);
339                    need_redraw = true;
340                }
341                AppEvent::SwitchTab(idx) => {
342                    params.app.switch_tab(idx);
343                    need_redraw = true;
344                }
345                AppEvent::AddTab => {
346                    params.app.add_tab();
347                    need_redraw = true;
348                }
349                AppEvent::CloseTab(idx) => {
350                    let _ = params.app.close_tab(idx);
351                    need_redraw = true;
352                }
353                AppEvent::Resize { width, height } => {
354                    w = width;
355                    h = height;
356                    local_buf = alloc::vec![0u32; (w * h) as usize];
357                    {
358                        let mut shared = params.shared_buffer.lock();
359                        *shared = alloc::vec![0u32; (w * h) as usize];
360                    }
361                    params.app.on_resize(w, h);
362                    register_draw_target(&params, w, h);
363                    need_redraw = true;
364                }
365            }
366        }
367
368        // 定期同期
369        {
370            let mut t = params.tabs.lock();
371            *t = params.app.tabs();
372        }
373        {
374            let mut a = params.active_tab.lock();
375            *a = params.app.active_tab();
376        }
377
378        if params.app.needs_timer_redraw() {
379            need_redraw = true;
380        }
381
382        let time_since_last_draw = current_time.saturating_sub(last_draw_time);
383
384        // 描画レートを最大30fps(33ms)程度に制限し、かつ不要な背景全クリアを削除してチラつきを完全防止!
385        if need_redraw || time_since_last_draw >= 33 {
386            // 2. アプリの描画 (ローカルバッファに対して行うため、描画中にロックを保持しない)
387            {
388                let screen = Screen::new(local_buf.as_mut_ptr(), w, h, w * 4);
389                let tab_list = params.app.tabs();
390                let has_tabs = !tab_list.is_empty();
391                let tab_w = if has_tabs { 100 } else { 0 };
392                let app_x = tab_w;
393                let app_y = 31u32;
394                let app_w = w.saturating_sub(tab_w);
395                let app_h = h.saturating_sub(31);
396
397                screen.set_clip(app_x, app_y, w, h);
398                params.app.draw(&screen, app_x, app_y, app_w, app_h);
399                screen.clear_clip();
400            }
401
402            // 3. 描画完了後、一瞬だけ共有バッファに一括コピーする(保持時間を極小化)
403            {
404                let mut buf_guard = params.shared_buffer.lock();
405                if buf_guard.len() == local_buf.len() {
406                    buf_guard.copy_from_slice(&local_buf);
407                    // ★ Core 0 に「バッファが更新された」ことを通知する
408                    // Ordering::Release で書き込み順序を保証し、Core 0 側の Acquire と対になる
409                    params.buffer_updated.store(true, Ordering::Release);
410                } else {
411                    // 【2026-07-26診断】長さ不一致だと**黙って転送をスキップ**していた。
412                    // ウィンドウリサイズ等で共有バッファとローカルバッファの大きさが
413                    // ずれると、以降アプリの描画結果が一切画面へ反映されなくなる
414                    // (画面には古いフレームが residual として残り続ける)。
415                    // 「ピクセルは書き込まれているのに画面に出ない」症状の
416                    // 有力な候補なので、発生を可視化する。
417                    static SKIP_DIAG: core::sync::atomic::AtomicU32 =
418                        core::sync::atomic::AtomicU32::new(0);
419                    let n = SKIP_DIAG.fetch_add(1, Ordering::Relaxed);
420                    if n < 4 || n.is_multiple_of(64) {
421                        crate::warn!(
422                            "[WM][DIAG] shared_buffer copy SKIPPED (size mismatch): shared={} local={} w={} h={} count={}",
423                            buf_guard.len(),
424                            local_buf.len(),
425                            w,
426                            h,
427                            n + 1
428                        );
429                    }
430                }
431            }
432
433            last_draw_time = current_time;
434        }
435
436        // CPU負荷を劇的に下げ、ホストCPUエミュレーションに十分な余裕を残すためのスリープ
437        crate::kernel::scheduler::sleep(5);
438    }
439    if let Some(pid) = crate::kernel::scheduler::get_current_process_id() {
440        DRAW_TARGETS.lock().remove(&pid);
441    }
442}
443
444/// 画面上の1つのウィンドウを表現する
445pub struct Window {
446    pub x: i32,
447    pub y: i32,
448    pub width: u32,
449    pub height: u32,
450    pub title: String,
451    pub app: Box<dyn App>,
452    pub is_dragging: bool,
453    pub drag_offset_x: i32,
454    pub drag_offset_y: i32,
455    pub buffer: Option<alloc::vec::Vec<u32>>,
456    // 空間・マルチタスク拡張
457    pub workspace: u8,
458    pub is_minimized: bool,
459    pub is_fullscreen: bool,
460    pub saved_x: i32,
461    pub saved_y: i32,
462    pub saved_w: u32,
463    pub saved_h: u32,
464    pub saved_ws: u8,
465    /// タブごとのバックグラウンドスレッド PID(None = スレッドなし)
466    pub tab_pids: alloc::vec::Vec<Option<usize>>,
467}
468
469impl Window {
470    pub fn new(x: i32, y: i32, w: u32, h: u32, title: &str, app: Box<dyn App>) -> Self {
471        let buffer = alloc::vec![0u32; (w * h) as usize];
472        // アプリの初期タブ数だけ PID スロットを確保(最低1)
473        let init_tabs = 1;
474        Self {
475            x,
476            y,
477            width: w,
478            height: h,
479            title: String::from(title),
480            app,
481            is_dragging: false,
482            drag_offset_x: 0,
483            drag_offset_y: 0,
484            buffer: Some(buffer),
485            workspace: 1,
486            is_minimized: false,
487            is_fullscreen: false,
488            saved_x: x,
489            saved_y: y,
490            saved_w: w,
491            saved_h: h,
492            saved_ws: 1,
493            tab_pids: alloc::vec![None; init_tabs],
494        }
495    }
496
497    /// ウィンドウを new_w × new_h にリサイズする。
498    /// バッファは「容量が足りていれば再利用」し、足りない時だけ再確保する(Phase 1: churn 撲滅)。
499    /// 再確保時はヒープ残量を確認し、不足ならリサイズを拒否して旧状態を保つ(Phase 2: OOM ガード)。
500    /// 描画・合成は stride=width で先頭 width×height 要素のみ使うため、より大きいバッファの再利用は安全。
501    /// 戻り値: リサイズに成功したか。
502    pub fn resize(&mut self, new_w: u32, new_h: u32) -> bool {
503        if new_w == 0 || new_h == 0 {
504            return false;
505        }
506        let needed = (new_w as usize).saturating_mul(new_h as usize);
507        let have = self.buffer.as_ref().map(|b| b.len()).unwrap_or(0);
508
509        if have < needed {
510            // 拡張が必要: OOM ガード(必要バイト + 64KB の余裕を確認)
511            let (used, total) = crate::kernel::allocator::get_heap_stats();
512            let free = total.saturating_sub(used);
513            let need_bytes = needed.saturating_mul(4).saturating_add(64 * 1024);
514            if free < need_bytes {
515                crate::println!(
516                    "[WM] resize denied: insufficient heap (free {} KB, need {} KB) — keeping current size",
517                    free / 1024, need_bytes / 1024
518                );
519                return false;
520            }
521            // 旧バッファを先に解放してから確保する(新旧同時確保によるピークを避ける)
522            self.buffer = None;
523            self.buffer = Some(alloc::vec![0u32; needed]);
524        }
525
526        self.width = new_w;
527        self.height = new_h;
528        self.app.on_resize(new_w, new_h);
529        true
530    }
531
532    /// タブ idx にスレッドを起動してPIDを記録する
533    pub fn spawn_tab_thread(&mut self, tab_idx: usize) {
534        while self.tab_pids.len() <= tab_idx {
535            self.tab_pids.push(None);
536        }
537        if let Some(thread_fn) = self.app.tab_thread_fn() {
538            let pid = crate::kernel::scheduler::spawn_with_stack(
539                thread_fn,
540                crate::kernel::scheduler::Priority::Normal,
541                "app_tab",
542                crate::kernel::scheduler::LARGE_STACK_SIZE,
543            );
544            if pid != 0 {
545                self.tab_pids[tab_idx] = Some(pid);
546                crate::info!("[WM] tab {} thread spawned pid={}", tab_idx, pid);
547            } else {
548                // 【2026-08-25】`spawn` はヒープ不足で 0 を返す。
549                // 状態を先に立てていないので壊れはしないが、
550                // 黙って何もしないとタブは開いているのに
551                // 中身が動かない、という説明の付かない状態になる。
552                // 他の 8 箇所と同じく失敗は必ず記録する。
553                crate::warn!(
554                    "[WM] tab {} のスレッドを起動できません(ヒープ不足)",
555                    tab_idx
556                );
557            }
558        }
559    }
560
561    /// タブ idx のスレッド PID スロットをクリアする
562    /// (スレッド自体はアプリ側の終了フラグで自然に終了させる)
563    pub fn clear_tab_thread(&mut self, tab_idx: usize) {
564        if tab_idx < self.tab_pids.len() {
565            if let Some(pid) = self.tab_pids[tab_idx] {
566                crate::info!("[WM] tab {} thread pid={} released", tab_idx, pid);
567            }
568            self.tab_pids[tab_idx] = None;
569        }
570    }
571}
572
573#[derive(Copy, Clone, Debug)]
574pub struct Rect {
575    pub x: i32,
576    pub y: i32,
577    pub w: u32,
578    pub h: u32,
579}
580
581impl Rect {
582    pub fn new(x: i32, y: i32, w: u32, h: u32) -> Self {
583        Self { x, y, w, h }
584    }
585
586    pub fn union(&self, other: &Self) -> Self {
587        let x0 = self.x.min(other.x);
588        let y0 = self.y.min(other.y);
589        let x1 = (self.x + self.w as i32).max(other.x + other.w as i32);
590        let y1 = (self.y + self.h as i32).max(other.y + other.h as i32);
591        Self {
592            x: x0,
593            y: y0,
594            w: (x1 - x0).max(0) as u32,
595            h: (y1 - y0).max(0) as u32,
596        }
597    }
598}
599
600pub struct WindowManager {
601    // リストの最後尾が「最前面(アクティブ)」
602    pub windows: Vec<Window>,
603    pub dirty: bool,
604    pub terminal_focused: bool,
605    pub active_workspace: u8,
606    pub slide_out: bool,
607    pub alt_hold_mode: bool,
608    pub alt_input_buffer: String,
609
610    // ダーティ矩形の追跡用
611    pub dirty_rects: Vec<Rect>,
612
613    // 直前のマウス状態を記憶してドラッグを判定
614    last_mouse_x: i32,
615    last_mouse_y: i32,
616    last_mouse_btn: bool,
617}
618
619// グローバルマネージャ
620static mut GLOBAL_WM: Option<ReentrantMutex<WindowManager>> = None;
621
622impl Default for WindowManager {
623    fn default() -> Self {
624        Self::new()
625    }
626}
627
628impl WindowManager {
629    pub fn new() -> Self {
630        Self {
631            windows: Vec::new(),
632            dirty: true,
633            terminal_focused: true,
634            active_workspace: 1,
635            slide_out: false,
636            alt_hold_mode: false,
637            alt_input_buffer: String::new(),
638            dirty_rects: Vec::new(),
639            last_mouse_x: 0,
640            last_mouse_y: 0,
641            last_mouse_btn: false,
642        }
643    }
644
645    pub fn add_dirty_rect(&mut self, rect: Rect) {
646        self.dirty_rects.push(rect);
647    }
648
649    pub fn clear_dirty_rects(&mut self) {
650        self.dirty_rects.clear();
651    }
652
653    pub fn get_dirty_bounding_box(&self, screen_w: u32, screen_h: u32) -> Option<Rect> {
654        if self.dirty_rects.is_empty() {
655            return None;
656        }
657        let mut r = self.dirty_rects[0];
658        for i in 1..self.dirty_rects.len() {
659            r = r.union(&self.dirty_rects[i]);
660        }
661
662        let x0 = r.x.clamp(0, screen_w as i32);
663        let y0 = r.y.clamp(0, screen_h as i32);
664        let x1 = (r.x + r.w as i32).clamp(0, screen_w as i32);
665        let y1 = (r.y + r.h as i32).clamp(0, screen_h as i32);
666
667        Some(Rect::new(x0, y0, (x1 - x0) as u32, (y1 - y0) as u32))
668    }
669
670    pub fn add_window(&mut self, mut win: Window) {
671        let app_name = win.app.name();
672        let mut existing_idx = None;
673        for (i, w) in self.windows.iter().enumerate() {
674            if w.app.name() == app_name {
675                existing_idx = Some(i);
676                break;
677            }
678        }
679
680        if let Some(idx) = existing_idx {
681            // 既に同じアプリが起動している場合:最小化解除、ワークスペースへジャンプ、最前面引き上げ
682            self.windows[idx].is_minimized = false;
683            let target_ws = self.windows[idx].workspace;
684            self.active_workspace = target_ws;
685            self.activate_window(idx);
686            self.dirty = true;
687            return;
688        }
689
690        // 新規ウィンドウは現在のアクティブワークスペースに割り当て
691        win.workspace = self.active_workspace;
692        let name = win.title.clone();
693        // 初期タブ (index 0) のバックグラウンドスレッドを起動
694        win.spawn_tab_thread(0);
695        self.add_dirty_rect(Rect::new(win.x, win.y, win.width, win.height));
696        self.windows.push(win);
697        self.dirty = true;
698        self.terminal_focused = false;
699        let (used, total) = crate::kernel::allocator::get_heap_stats();
700        crate::debug!(
701            "[WM] open '{}' -> {} windows | heap {}/{} KB",
702            name,
703            self.windows.len(),
704            used / 1024,
705            total / 1024
706        );
707    }
708
709    pub fn remove_active_window(&mut self) {
710        if !self.windows.is_empty() {
711            let name = self
712                .windows
713                .last()
714                .map(|w| w.title.clone())
715                .unwrap_or_else(|| alloc::string::String::from("?"));
716            // ウォッチドッグ of 違反/Not Responding 状態をリセット
717            let info = self.windows.last().map(|w| {
718                (
719                    Rect::new(w.x, w.y, w.width, w.height),
720                    alloc::string::String::from(w.app.name()),
721                )
722            });
723            if let Some((rect, app_name)) = info {
724                self.add_dirty_rect(rect);
725                crate::kernel::watchdog::clear(&app_name);
726            }
727            self.windows.pop();
728            self.dirty = true;
729            let (used, total) = crate::kernel::allocator::get_heap_stats();
730            crate::debug!(
731                "[WM] close '{}' -> {} windows | heap {}/{} KB",
732                name,
733                self.windows.len(),
734                used / 1024,
735                total / 1024
736            );
737        }
738    }
739
740    /// 指定したインデックス of ウィンドウを最前面に移動してアクティブにする
741    pub fn activate_window(&mut self, idx: usize) {
742        // インデックスの境界チェックを行い、範囲外アクセスによる unwrap のパニックや
743        // 境界外例外を防止して安全にウィンドウをアクティブ化します。
744        if idx < self.windows.len() {
745            let target_ws = self.windows[idx].workspace;
746            self.active_workspace = target_ws;
747            self.terminal_focused = false;
748            // ウィンドウを前面に出す際、退避(スライドアウト)状態を解除して
749            // 確実に画面内・最前面へ復帰させる。
750            self.slide_out = false;
751            let win = self.windows.remove(idx);
752            let rect = Rect::new(win.x, win.y, win.width, win.height);
753            self.add_dirty_rect(rect);
754            self.windows.push(win);
755            self.add_dirty_rect(rect);
756            self.dirty = true;
757        }
758    }
759
760    /// 全画面(背景の上にウィンドウ)を描画 (合成ウィンドウマネージャ方式)
761    /// 指定矩形 (スクリーン座標) を、可視ウィンドウ (アクティブ WS・非最小化) が
762    /// 少しでも覆っているかを返す。
763    ///
764    /// システムモニタのような「背景レイヤー」要素を部分再描画してよいかの判定に使う。
765    /// 背景要素はウィンドウより下の z 順にあるため、ウィンドウが覆っている領域へ
766    /// 単独で描画すると z 順を破ってウィンドウを突き破ってしまう。それを防ぐ。
767    pub fn any_window_covers_rect(&self, x0: i32, y0: i32, x1: i32, y1: i32) -> bool {
768        // スライドアウト中 (WS1) は全ウィンドウが画面外へ退避しているため覆わない
769        if self.active_workspace == 1 && self.slide_out {
770            return false;
771        }
772        for win in self.windows.iter() {
773            if win.workspace != self.active_workspace || win.is_minimized {
774                continue;
775            }
776            let wx1 = win.x + win.width as i32;
777            let wy1 = win.y + win.height as i32;
778            // 矩形交差判定
779            if win.x < x1 && wx1 > x0 && win.y < y1 && wy1 > y0 {
780                return true;
781            }
782        }
783        false
784    }
785
786    pub fn draw_all(&mut self, screen: &Screen) {
787        self.dirty = false;
788        let config = crate::kernel::config::get_config();
789        let base_frame_color = Color(config.theme.non_active_window_border);
790        let title_bar_color = Color(config.theme.window_title_bar);
791        let title_fg_color = Color(config.theme.window_title_fg);
792        let active_frame_color = Color(config.theme.active_window_border);
793
794        let num_windows = self.windows.len();
795        for (i, win) in self.windows.iter_mut().enumerate() {
796            // アクティブなワークスペースかつ最小化されていないウィンドウのみを描画
797            if win.workspace != self.active_workspace || win.is_minimized {
798                continue;
799            }
800            let is_active = i == num_windows.saturating_sub(1) && !self.terminal_focused;
801            let current_frame_color = if is_active {
802                active_frame_color
803            } else {
804                base_frame_color
805            };
806            let border_w = 2; // 常に2pxの極太ボーダー
807
808            let w = win.width;
809            let h = win.height;
810
811            if let Some(ref mut win_buf) = win.buffer {
812                // 1. 描画先をウィンドウ固有のバッファに切り替える
813                screen.set_render_target(win_buf.as_mut_ptr(), w, h);
814
815                // アプリケーション領域のレイアウト計算(描画前に確定させる)
816                let mut tab_list = win.app.tabs();
817                let active_idx = win.app.active_tab();
818                let is_terminal = win.app.name() == "Terminal";
819                if is_terminal && tab_list.is_empty() {
820                    tab_list.push(alloc::string::String::from("Shell"));
821                }
822                let has_tabs = !tab_list.is_empty();
823                let tab_w = if has_tabs { 100 } else { 0 };
824                let app_x = tab_w;
825                let app_y = 31u32;
826                let app_w = w.saturating_sub(tab_w);
827                let _app_h = h.saturating_sub(31);
828
829                let app_name = win.app.name();
830
831                // 【2026-07-27診断】表示フレームの通し番号を進め、
832                // 「ヒーロー文字を最後に描いたフレーム」との差を可視化する。
833                // 差が開き続けるなら、文字を描いたフレームは画面に出ていない。
834                {
835                    let f = crate::kernel::draw::FRAME_NO
836                        .fetch_add(1, Ordering::Relaxed)
837                        .wrapping_add(1);
838                    if f.is_multiple_of(64) {
839                        // 【2026-08-25】取得の待機状態はここから出す。
840                        //
841                        // 最初はブラウザの `draw()` から出していたが、
842                        // **待機に入ると `draw()` 自体が止まる**ので
843                        // 最後に出た行が古い標本のまま残り、
844                        // 「まだ取得中」に見えたまま更新されなくなった。
845                        // 採取スクリプトはこの行で撮影時期を決めるので、
846                        // 止まらない側(画面の提示ループ)から出す必要がある。
847                        crate::os_lib::web_engine::fetch_limit::idle_line();
848                        let ht = crate::kernel::draw::HERO_TEXT_FRAME.load(Ordering::Relaxed);
849                        crate::warn!(
850                            "[WM][FRAME] present_frame={} last_hero_text_frame={}",
851                            f,
852                            if ht == u32::MAX { 0 } else { ht }
853                        );
854                    }
855                }
856
857                if win.app.is_proxy() {
858                    // ★ ProxyApp の描画順序:
859                    //   ① shared_buf → win_buf の全面コピー(前フレームの内容で埋める)
860                    //   ② タイトルバー・枠線を上書き
861                    // これにより「一瞬 app_bg 単色状態」が発生せずちらつきを防止する。
862                    if let Some(shared_buf_arc) = win.app.get_shared_buffer() {
863                        if let Some(shared_buf) = shared_buf_arc.try_lock() {
864                            // shared_buf は app_runner が w×h 全面に描画済みなので全コピー
865                            let copy_len = (w * h) as usize;
866                            if shared_buf.len() >= copy_len && win_buf.len() >= copy_len {
867                                unsafe {
868                                    core::ptr::copy_nonoverlapping(
869                                        shared_buf.as_ptr(),
870                                        win_buf.as_mut_ptr(),
871                                        copy_len,
872                                    );
873                                }
874                            } else {
875                                // 【2026-07-26診断】ここも長さ不足だと**黙ってスキップ**して
876                                // win_buf の前フレーム内容をそのまま表示し続ける。
877                                // アプリ側(app_runner)の転送は成功しているのに画面が
878                                // 更新されない、という症状の候補。発生を可視化する。
879                                static CSKIP: core::sync::atomic::AtomicU32 =
880                                    core::sync::atomic::AtomicU32::new(0);
881                                let n = CSKIP.fetch_add(1, Ordering::Relaxed);
882                                if n < 4 || n.is_multiple_of(128) {
883                                    crate::warn!(
884                                        "[WM][DIAG] compositor copy SKIPPED: shared={} win_buf={} need={} (w={} h={}) count={}",
885                                        shared_buf.len(),
886                                        win_buf.len(),
887                                        copy_len,
888                                        w,
889                                        h,
890                                        n + 1
891                                    );
892                                }
893                            }
894                        } else {
895                            // try_lock 失敗 = app_runner 側が書き込み中。
896                            // win_buf の前フレーム内容をそのまま使い、次フレームでリトライ。
897                            //
898                            // 【2026-07-26診断】ここが連続して失敗し続けると
899                            // **古いフレームを表示し続ける**。ブラウザのように
900                            // 1 フレームの描画が重いアプリでは app_runner が
901                            // ロックを長く保持するため、コンポジタが毎回負ける
902                            // 可能性がある。成功率を可視化する。
903                            static TRYLOCK_FAIL: core::sync::atomic::AtomicU32 =
904                                core::sync::atomic::AtomicU32::new(0);
905                            let n = TRYLOCK_FAIL.fetch_add(1, Ordering::Relaxed);
906                            if n < 4 || n.is_multiple_of(128) {
907                                crate::warn!(
908                                    "[WM][DIAG] compositor try_lock FAILED (using stale frame) count={}",
909                                    n + 1
910                                );
911                            }
912                            self.dirty = true;
913                        }
914                    }
915                } else {
916                    // 2. 非Proxy: 従来通りバッファをクリアしてからアプリが描画
917                    screen.boxfill(0, 0, w, h, Color(config.theme.app_bg)); // 背景
918
919                    // アプリの描画(ウォッチドッグで監視)
920                    screen.set_clip(app_x, app_y, w, h);
921                    if crate::kernel::watchdog::is_not_responding(app_name) {
922                        screen.boxfill(app_x, app_y, w, h, Color(config.theme.ui_overlay_bg));
923                        screen.draw_string_vector(
924                            app_x + 16,
925                            app_y + 16,
926                            "Application Not Responding",
927                            Color(config.theme.error_fg),
928                            16,
929                        );
930                        screen.draw_string_vector(
931                            app_x + 16,
932                            app_y + 40,
933                            "Close the window (Alt+W) to recover.",
934                            Color(config.theme.terminal_fg),
935                            13,
936                        );
937                    } else {
938                        crate::kernel::watchdog::enter(app_name);
939                        win.app.draw(screen, app_x, app_y, app_w, _app_h);
940                        crate::kernel::watchdog::exit();
941                    }
942                    screen.clear_clip();
943                }
944
945                // 3. タイトルバー・枠線・ボタンを最前面に上書き(ProxyApp でも共通)
946                // タイトルバー (高さ30px)
947                screen.boxfill(0, 0, w, 30, title_bar_color);
948
949                // 境界線 (Premium Border Highlight with 立体極暗シャドウ)
950                let shadow_color = Color(0xFF111217);
951                screen.boxfill(0, 0, w, 1, shadow_color); // 最上部極暗
952                screen.boxfill(0, 0, 1, h, shadow_color); // 最左部極暗
953                screen.boxfill(w - 1, 0, w, h, shadow_color); // 最右部極暗
954                screen.boxfill(0, h - 1, w, h, shadow_color); // 最下部極暗
955
956                screen.boxfill(1, 1, w - 1, 1 + border_w, current_frame_color); // 上
957                screen.boxfill(1, 1, 1 + border_w, h - 1, current_frame_color); // 左
958                screen.boxfill(w - 1 - border_w, 1, w - 1, h - 1, current_frame_color); // 右
959                screen.boxfill(1, h - 1 - border_w, w - 1, h - 1, current_frame_color); // 下
960                screen.boxfill(1, 30, w - 1, 30 + border_w, current_frame_color); // タイトル下
961
962                // タイトルテキスト
963                screen.draw_string_vector(10, 5, &win.title, title_fg_color, 16);
964
965                // ウィンドウ制御ボタン群 (右から順に配置) - Fluent UI System Icons (MIT)
966                static ICON_CLOSE: &[u8] =
967                    include_bytes!("../../icons/ic_fluent_dismiss_circle_20_regular.svg");
968                static ICON_FS: &[u8] =
969                    include_bytes!("../../icons/ic_fluent_full_screen_maximize_20_regular.svg");
970                static ICON_MAX: &[u8] =
971                    include_bytes!("../../icons/ic_fluent_maximize_20_regular.svg");
972                static ICON_NORM: &[u8] =
973                    include_bytes!("../../icons/ic_fluent_full_screen_minimize_20_regular.svg");
974                static ICON_MIN: &[u8] =
975                    include_bytes!("../../icons/ic_fluent_subtract_circle_20_regular.svg");
976
977                let btn_w = 20u32;
978                let _btn_h = 20u32;
979                let btn_y = 5u32;
980                let gap = 5i32;
981
982                // [X] 閉じるボタン
983                let bx_close = w - 10 - btn_w;
984                screen.draw_svg_icon(bx_close, btn_y, ICON_CLOSE, btn_w, Color(config.theme.win_btn_close));
985
986                // [F] フルスクリーンボタン
987                let bx_fs = (bx_close as i32 - btn_w as i32 - gap) as u32;
988                screen.draw_svg_icon(bx_fs, btn_y, ICON_FS, btn_w, Color(config.theme.win_btn_fullscreen));
989
990                // [M] 最大化ボタン
991                let bx_max = (bx_fs as i32 - btn_w as i32 - gap) as u32;
992                screen.draw_svg_icon(bx_max, btn_y, ICON_MAX, btn_w, Color(config.theme.win_btn_maximize));
993
994                // [N] 通常化ボタン
995                let bx_norm = (bx_max as i32 - btn_w as i32 - gap) as u32;
996                screen.draw_svg_icon(bx_norm, btn_y, ICON_NORM, btn_w, Color(config.theme.win_btn_restore));
997
998                // [m] 最小化ボタン
999                let bx_min = (bx_norm as i32 - btn_w as i32 - gap) as u32;
1000                screen.draw_svg_icon(bx_min, btn_y, ICON_MIN, btn_w, Color(config.theme.win_btn_minimize));
1001
1002                // ウィンドウ内タブの描画(ProxyApp・非Proxy 共通)
1003                if has_tabs {
1004                    screen.boxfill(0, app_y, tab_w, h, Color(config.theme.tab_area_bg));
1005                    screen.boxfill(tab_w - 1, app_y, tab_w, h, Color(config.theme.tab_area_border)); // 境界線
1006
1007                    screen.draw_string_vector(10, app_y + 10, "TABS", Color(config.theme.tab_inactive_fg), 14);
1008
1009                    // (+) button
1010                    screen.boxfill(75, app_y + 8, 92, app_y + 25, Color(config.theme.tab_area_border));
1011                    screen.draw_string_vector(80, app_y + 10, "+", Color(config.theme.tab_active_fg), 14);
1012
1013                    screen.boxfill(10, app_y + 30, 90, app_y + 31, Color(config.theme.tab_area_border));
1014
1015                    let mut tab_y = app_y + 40;
1016                    for (idx, tab_title) in tab_list.iter().enumerate() {
1017                        if tab_y + 42 > h {
1018                            break;
1019                        }
1020
1021                        let is_active_tab = idx == active_idx;
1022                        let bg_color = if is_active_tab {
1023                            Color(config.theme.tab_active_bg)
1024                        } else {
1025                            Color(config.theme.tab_area_bg)
1026                        };
1027                        let fg_color = if is_active_tab {
1028                            Color(config.theme.tab_active_fg)
1029                        } else {
1030                            Color(config.theme.tab_inactive_fg)
1031                        };
1032
1033                        screen.boxfill(5, tab_y, 95, tab_y + 36, bg_color);
1034
1035                        let label = alloc::format!("{})", idx + 1);
1036                        screen.draw_string_vector(10, tab_y + 12, &label, fg_color, 12);
1037
1038                        // 改行許容および文字数拡大ロジック
1039                        let mut lines = alloc::vec![];
1040                        if tab_title.contains('\n') {
1041                            for (count, part) in tab_title.split('\n').enumerate() {
1042                                if count >= 2 {
1043                                    break;
1044                                }
1045                                lines.push(alloc::string::String::from(part));
1046                            }
1047                        } else {
1048                            let chars: alloc::vec::Vec<char> = tab_title.chars().collect();
1049                            if chars.len() <= 6 {
1050                                lines.push(tab_title.clone());
1051                            } else {
1052                                let first: alloc::string::String = chars.iter().take(6).collect();
1053                                let mut second: alloc::string::String =
1054                                    chars.iter().skip(6).collect();
1055                                if second.chars().count() > 6 {
1056                                    let mut sec_trunc: alloc::string::String =
1057                                        second.chars().take(4).collect();
1058                                    sec_trunc.push_str("..");
1059                                    second = sec_trunc;
1060                                }
1061                                lines.push(first);
1062                                lines.push(second);
1063                            }
1064                        }
1065
1066                        if lines.len() == 1 {
1067                            screen.draw_string_vector(22, tab_y + 12, &lines[0], fg_color, 10);
1068                        } else if lines.len() >= 2 {
1069                            screen.draw_string_vector(22, tab_y + 5, &lines[0], fg_color, 10);
1070                            screen.draw_string_vector(22, tab_y + 19, &lines[1], fg_color, 10);
1071                        }
1072
1073                        // (x) button
1074                        screen.boxfill(78, tab_y + 10, 92, tab_y + 24, Color(config.theme.tab_area_border));
1075                        screen.draw_string_vector(82, tab_y + 12, "x", Color(config.theme.win_btn_close), 10);
1076
1077                        tab_y += 42;
1078                    }
1079                }
1080
1081                // 4. 描画ターゲットをメイン画面バッファへリセット
1082                screen.reset_render_target();
1083
1084                // 5. win_buf → back_buffer へ行単位の高速コピー(ピクセルループを廃止)
1085                let screen_w = screen.width;
1086                let screen_h = screen.height;
1087                let main_ptr = if screen.use_back_buffer.get() && !screen.back_buffer.is_null() {
1088                    screen.back_buffer
1089                } else {
1090                    screen.vram
1091                };
1092
1093                let win_x = if self.active_workspace == 1 && self.slide_out {
1094                    win.x + screen.width as i32
1095                } else {
1096                    win.x
1097                };
1098                let win_y = win.y;
1099
1100                // クリップ計算(ウィンドウが画面端にかかる場合、および画面のダーティクリップ領域に対応)
1101                let src_x0 = if win_x < 0 { (-win_x) as u32 } else { 0 };
1102                let src_y0 = if win_y < 0 { (-win_y) as u32 } else { 0 };
1103                let dst_x0 = win_x.max(0) as u32;
1104                let dst_y0 = win_y.max(0) as u32;
1105                let copy_w = w.saturating_sub(src_x0).min(screen_w.saturating_sub(dst_x0));
1106                let copy_h = h.saturating_sub(src_y0).min(screen_h.saturating_sub(dst_y0));
1107
1108                let (clip_x0, clip_y0, clip_x1, clip_y1) = if let Some(c) = screen.clip.get() {
1109                    c
1110                } else {
1111                    (0, 0, screen_w, screen_h)
1112                };
1113                let eff_dst_x0 = dst_x0.max(clip_x0);
1114                let eff_dst_y0 = dst_y0.max(clip_y0);
1115                let eff_dst_x1 = (dst_x0 + copy_w).min(clip_x1);
1116                let eff_dst_y1 = (dst_y0 + copy_h).min(clip_y1);
1117
1118                if eff_dst_x1 > eff_dst_x0 && eff_dst_y1 > eff_dst_y0 {
1119                    let eff_w = eff_dst_x1 - eff_dst_x0;
1120                    let eff_h = eff_dst_y1 - eff_dst_y0;
1121                    let eff_src_x0 = src_x0 + (eff_dst_x0 - dst_x0);
1122                    let eff_src_y0 = src_y0 + (eff_dst_y0 - dst_y0);
1123                    unsafe {
1124                        for row in 0..eff_h {
1125                            let src_ptr = win_buf.as_ptr().add(((eff_src_y0 + row) * w + eff_src_x0) as usize);
1126                            let dst_ptr = main_ptr.add(((eff_dst_y0 + row) * screen_w + eff_dst_x0) as usize);
1127                            core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, eff_w as usize);
1128                        }
1129                    }
1130                }
1131            }
1132        }
1133
1134        // 6. Altホールド複数桁遅延実行オーバーレイの描画
1135        if self.alt_hold_mode {
1136            let overlay_w = 200u32;
1137            let overlay_h = 100u32;
1138            let overlay_x = (screen.width - overlay_w) / 2;
1139            let overlay_y = (screen.height - overlay_h) / 2;
1140
1141            let overlay_bg_semi = Color(0xEE000000 | (config.theme.ui_overlay_bg & 0x00FFFFFF));
1142            screen.boxfill(
1143                overlay_x,
1144                overlay_y,
1145                overlay_x + overlay_w,
1146                overlay_y + overlay_h,
1147                overlay_bg_semi,
1148            );
1149            screen.boxfill(
1150                overlay_x,
1151                overlay_y,
1152                overlay_x + overlay_w,
1153                overlay_y + 2,
1154                Color(config.theme.ui_overlay_accent),
1155            );
1156            screen.draw_string_vector(
1157                overlay_x + 15,
1158                overlay_y + 10,
1159                "STATELESS JUMP",
1160                Color(config.theme.terminal_fg),
1161                14,
1162            );
1163
1164            let val_str = if self.alt_input_buffer.is_empty() {
1165                alloc::string::String::from("_")
1166            } else {
1167                alloc::format!("{}_", self.alt_input_buffer)
1168            };
1169            screen.draw_string_vector(
1170                overlay_x + 80,
1171                overlay_y + 40,
1172                &val_str,
1173                Color(config.theme.success_fg),
1174                32,
1175            );
1176        }
1177    }
1178
1179    /// マウスイベント処理。Zオーダー順(手前から)判定する。
1180    pub fn handle_mouse(
1181        &mut self,
1182        mx: i32,
1183        my: i32,
1184        btn_left: bool,
1185        btn_right: bool,
1186        wheel: i32,
1187        screen: &Screen,
1188    ) -> bool {
1189        let mut handled = false;
1190
1191        let dx = mx - self.last_mouse_x;
1192        let dy = my - self.last_mouse_y;
1193
1194        // 1. ドラッグ中のウィンドウがあれば移動 (アクティブなワークスペースかつ非最小化時のみ)
1195        // 1. ドラッグ中のウィンドウがあれば移動 (アクティブなワークスペースかつ非最小化時のみ)
1196        let is_dragging = if let Some(win) = self.windows.last() {
1197            win.is_dragging && win.workspace == self.active_workspace && !win.is_minimized
1198        } else {
1199            false
1200        };
1201
1202        if is_dragging {
1203            if btn_left {
1204                // 移動前の矩形をダーティ登録
1205                let old_rect = if let Some(win) = self.windows.last() {
1206                    Rect::new(win.x, win.y, win.width, win.height)
1207                } else {
1208                    Rect::new(0, 0, 0, 0)
1209                };
1210                self.add_dirty_rect(old_rect);
1211
1212                if let Some(win) = self.windows.last_mut() {
1213                    win.x += dx;
1214                    win.y += dy;
1215                }
1216                self.dirty = true;
1217
1218                // 移動後の矩形をダーティ登録
1219                let new_rect = if let Some(win) = self.windows.last() {
1220                    Rect::new(win.x, win.y, win.width, win.height)
1221                } else {
1222                    Rect::new(0, 0, 0, 0)
1223                };
1224                self.add_dirty_rect(new_rect);
1225
1226                self.last_mouse_x = mx;
1227                self.last_mouse_y = my;
1228                self.last_mouse_btn = btn_left;
1229                return true;
1230            } else {
1231                if let Some(win) = self.windows.last_mut() {
1232                    win.is_dragging = false;
1233                }
1234            }
1235        }
1236
1237        // 2. マウスクリック時のヒットテスト(手前から奥へ)
1238        let btn_just_pressed = btn_left && !self.last_mouse_btn;
1239        let mut target_index = None;
1240        let mut close_clicked = false;
1241        let mut fs_clicked = false;
1242        let mut max_clicked = false;
1243        let mut norm_clicked = false;
1244        let mut min_clicked = false;
1245
1246        for (i, win) in self.windows.iter_mut().enumerate().rev() {
1247            // 現在アクティブな空間かつ非最小化ウィンドウのみを対象とする
1248            if win.workspace != self.active_workspace || win.is_minimized {
1249                continue;
1250            }
1251            let win_x = if self.active_workspace == 1 && self.slide_out {
1252                win.x + 2000 // 画面外へ
1253            } else {
1254                win.x
1255            };
1256            if mx >= win_x
1257                && mx <= win_x + win.width as i32
1258                && my >= win.y
1259                && my <= win.y + win.height as i32
1260            {
1261                target_index = Some(i);
1262
1263                // タイトルバー判定 (Y: 0..30)
1264                if my < win.y + 30 {
1265                    let btn_w = 20;
1266                    let gap = 5;
1267                    let bx_close = win_x + win.width as i32 - 10 - btn_w;
1268                    let bx_fs = bx_close - btn_w - gap;
1269                    let bx_max = bx_fs - btn_w - gap;
1270                    let bx_norm = bx_max - btn_w - gap;
1271                    let bx_min = bx_norm - btn_w - gap;
1272
1273                    if btn_just_pressed {
1274                        if mx >= bx_close && mx <= bx_close + btn_w {
1275                            close_clicked = true;
1276                        } else if mx >= bx_fs && mx <= bx_fs + btn_w {
1277                            fs_clicked = true;
1278                        } else if mx >= bx_max && mx <= bx_max + btn_w {
1279                            max_clicked = true;
1280                        } else if mx >= bx_norm && mx <= bx_norm + btn_w {
1281                            norm_clicked = true;
1282                        } else if mx >= bx_min && mx <= bx_min + btn_w {
1283                            min_clicked = true;
1284                        } else {
1285                            // タイトルバークリックでドラッグ開始
1286                            win.is_dragging = true;
1287                            win.drag_offset_x = mx - win.x;
1288                            win.drag_offset_y = my - win.y;
1289                        }
1290                    }
1291                } else {
1292                    let mut tab_list = win.app.tabs();
1293                    let is_terminal = win.app.name() == "Terminal";
1294                    if is_terminal && tab_list.is_empty() {
1295                        tab_list.push(alloc::string::String::from("Shell"));
1296                    }
1297                    let has_tabs = !tab_list.is_empty();
1298                    let tab_w = if has_tabs { 100 } else { 0 };
1299
1300                    let local_x = mx - win_x;
1301                    let local_y = my - (win.y + 31);
1302
1303                    if has_tabs && local_x < tab_w {
1304                        // タブ領域のクリック処理
1305                        if btn_just_pressed {
1306                            let tab_y_click = local_y as u32;
1307                            if (8..=25).contains(&tab_y_click) && (75..=92).contains(&local_x) {
1308                                // '+' ボタン: タブ追加 → バックグラウンドスレッド起動
1309                                let new_tab_idx = win.app.tabs().len();
1310                                win.app.add_tab();
1311                                win.spawn_tab_thread(new_tab_idx);
1312                                self.dirty = true;
1313                                crate::kernel::audio::play_system_se(
1314                                    crate::kernel::audio::SeType::Apply,
1315                                );
1316                            } else if tab_y_click >= 40 {
1317                                let clicked_tab = ((tab_y_click - 40) / 42) as usize;
1318                                if clicked_tab < tab_list.len() {
1319                                    let relative_y = (tab_y_click - 40) % 42;
1320                                    if relative_y <= 36 {
1321                                        if (78..=92).contains(&local_x)
1322                                            && (10..=24).contains(&relative_y)
1323                                        {
1324                                            // 'x' ボタン: タブ削除 → スレッドを解放
1325                                            win.clear_tab_thread(clicked_tab);
1326                                            let should_close = win.app.close_tab(clicked_tab);
1327                                            if should_close {
1328                                                close_clicked = true;
1329                                            } else {
1330                                                crate::kernel::audio::play_system_se(
1331                                                    crate::kernel::audio::SeType::Apply,
1332                                                );
1333                                            }
1334                                        } else {
1335                                            win.app.switch_tab(clicked_tab);
1336                                        }
1337                                        self.dirty = true;
1338                                    }
1339                                }
1340                            }
1341                        }
1342                    } else {
1343                        // アプリ内部の処理(ローカルX座標はタブ幅分シフトする)
1344                        win.app
1345                            .on_mouse(local_x - tab_w, local_y, btn_left, btn_right, wheel);
1346                        // マウスを動かしただけで全画面再描画すると重くなるため、クリックやスクロール時のみ再描画
1347                        if btn_just_pressed || wheel != 0 || (btn_left != self.last_mouse_btn) {
1348                            self.dirty = true;
1349                        }
1350                    }
1351                }
1352
1353                handled = true;
1354                break;
1355            }
1356        }
1357
1358        // フォーカス切り替え(アクティブ化)と閉じる・サイズ変更処理
1359        if let Some(idx) = target_index {
1360            if btn_just_pressed {
1361                self.activate_window(idx);
1362            }
1363            if close_clicked {
1364                if let Some(w) = self.windows.last() {
1365                    self.add_dirty_rect(Rect::new(w.x, w.y, w.width, w.height));
1366                }
1367                self.windows.pop();
1368                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1369                self.dirty = true;
1370                if self.windows.is_empty() {
1371                    self.terminal_focused = true;
1372                }
1373            } else if fs_clicked {
1374                // 1. 空いているワークスペースを探す(2〜8)
1375                let mut target_ws = None;
1376                for ws in 2..=8 {
1377                    let mut used = false;
1378                    for w in &self.windows {
1379                        if w.workspace == ws && !w.is_minimized {
1380                            used = true;
1381                            break;
1382                        }
1383                    }
1384                    if !used {
1385                        target_ws = Some(ws);
1386                        break;
1387                    }
1388                }
1389                let target_ws = target_ws.unwrap_or(self.active_workspace);
1390
1391                let old_rect = self
1392                    .windows
1393                    .last()
1394                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1395                if let Some(r) = old_rect {
1396                    self.add_dirty_rect(r);
1397                }
1398
1399                if let Some(win) = self.windows.last_mut() {
1400                    win.is_fullscreen = true;
1401                    win.saved_x = win.x;
1402                    win.saved_y = win.y;
1403                    win.saved_w = win.width;
1404                    win.saved_h = win.height;
1405                    win.saved_ws = win.workspace;
1406                    let sw = screen.width;
1407                    let sh = screen.height;
1408                    win.x = 0;
1409                    win.y = 0;
1410                    win.resize(sw, sh);
1411                    win.workspace = target_ws;
1412                    self.active_workspace = target_ws;
1413                    self.dirty = true;
1414                }
1415                let sw = screen.width;
1416                let sh = screen.height;
1417                self.add_dirty_rect(Rect::new(0, 0, sw, sh));
1418                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1419            } else if max_clicked {
1420                let old_rect = self
1421                    .windows
1422                    .last()
1423                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1424                if let Some(r) = old_rect {
1425                    self.add_dirty_rect(r);
1426                }
1427
1428                if let Some(win) = self.windows.last_mut() {
1429                    win.is_fullscreen = false;
1430                    win.saved_x = win.x;
1431                    win.saved_y = win.y;
1432                    win.saved_w = win.width;
1433                    win.saved_h = win.height;
1434                    let sh = screen.height.saturating_sub(crate::kernel::fn_bar::BAR_HEIGHT);
1435                    let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1436                    let max_w = screen.width.saturating_sub(sysmon_w);
1437                    win.x = 0;
1438                    win.y = 0;
1439                    win.resize(max_w, sh);
1440                    self.dirty = true;
1441                }
1442
1443                let new_rect = self
1444                    .windows
1445                    .last()
1446                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1447                if let Some(r) = new_rect {
1448                    self.add_dirty_rect(r);
1449                }
1450                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1451            } else if norm_clicked {
1452                let old_rect = self
1453                    .windows
1454                    .last()
1455                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1456                if let Some(r) = old_rect {
1457                    self.add_dirty_rect(r);
1458                }
1459
1460                if let Some(win) = self.windows.last_mut() {
1461                    win.is_fullscreen = false;
1462                    win.x = win.saved_x;
1463                    win.y = win.saved_y;
1464                    let (rw, rh) = (win.saved_w, win.saved_h);
1465
1466                    self.active_workspace = win.saved_ws;
1467                    win.workspace = win.saved_ws;
1468
1469                    win.resize(rw, rh);
1470                    self.dirty = true;
1471                }
1472
1473                let new_rect = self
1474                    .windows
1475                    .last()
1476                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1477                if let Some(r) = new_rect {
1478                    self.add_dirty_rect(r);
1479                }
1480                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1481            } else if min_clicked {
1482                let old_rect = self
1483                    .windows
1484                    .last()
1485                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1486                if let Some(r) = old_rect {
1487                    self.add_dirty_rect(r);
1488                }
1489
1490                if let Some(win) = self.windows.last_mut() {
1491                    win.is_minimized = true;
1492                }
1493                self.dirty = true;
1494                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1495            }
1496        } else if btn_just_pressed {
1497            // ウィンドウ外をクリックした場合はターミナル等にフォーカスを戻す
1498            // ただし、右側ペイン(システムモニター:X >= 1600)をクリックした場合はターミナルにフォーカスを奪わない
1499            let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1500            let sysmon_x = screen.width - sysmon_w;
1501            if mx < sysmon_x as i32 {
1502                self.terminal_focused = true;
1503            }
1504        }
1505
1506        self.last_mouse_x = mx;
1507        self.last_mouse_y = my;
1508        self.last_mouse_btn = btn_left;
1509
1510        handled
1511    }
1512
1513    /// キーボードイベントの処理(ファンクションキーおよびアクティブウィンドウ宛て)
1514    pub fn handle_key(
1515        &mut self,
1516        keycode: u8,
1517        pressed: bool,
1518        ascii: Option<char>,
1519        alt_pressed: bool,
1520        screen: &Screen,
1521    ) -> bool {
1522        if !pressed {
1523            return false;
1524        }
1525
1526        // --- ファンクションキーの絶対アサイン判定 ---
1527
1528        // 1. F1 (単体) & F1 (フォーカス時再押下でウィンドウ退避)
1529        if keycode == 0x3B && !alt_pressed {
1530            if self.terminal_focused && !self.slide_out {
1531                // すでにターミナルが前面・フォーカス中なら、ウィンドウ退避スライドへトグル
1532                self.slide_out = true;
1533                self.dirty = true;
1534            } else {
1535                // 初回(未フォーカス、または退避中)は一段でターミナルを前面+フォーカス化
1536                self.terminal_focused = true;
1537                self.slide_out = false;
1538                self.dirty = true;
1539            }
1540            crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1541            return true;
1542        }
1543
1544        // 2. Alt + F1 〜 F8 (空間移動)
1545        if alt_pressed && (0x3B..=0x42).contains(&keycode) {
1546            let target_ws = if keycode == 0x3B {
1547                1
1548            } else {
1549                (keycode - 0x3C) + 2
1550            };
1551            self.active_workspace = target_ws;
1552            self.dirty = true;
1553
1554            // 移動先にウィンドウがあれば最前面をアクティブにする
1555            let mut last_win_idx = None;
1556            for (i, win) in self.windows.iter().enumerate() {
1557                if win.workspace == target_ws && !win.is_minimized {
1558                    last_win_idx = Some(i);
1559                }
1560            }
1561            if let Some(idx) = last_win_idx {
1562                self.activate_window(idx);
1563            } else {
1564                self.terminal_focused = true;
1565            }
1566            crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1567            return true;
1568        }
1569
1570        // 3. Alt + F9 (アクティブウィンドウの最小化)
1571        if alt_pressed && keycode == 0x43 {
1572            let active_is_matching = if let Some(win) = self.windows.last() {
1573                win.workspace == self.active_workspace && !win.is_minimized
1574            } else {
1575                false
1576            };
1577
1578            if active_is_matching {
1579                let old_rect = self
1580                    .windows
1581                    .last()
1582                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1583                if let Some(r) = old_rect {
1584                    self.add_dirty_rect(r);
1585                }
1586
1587                if let Some(win) = self.windows.last_mut() {
1588                    win.is_minimized = true;
1589                }
1590                self.dirty = true;
1591
1592                // フォーカスを背面のウィンドウまたはターミナルに戻す
1593                let mut found_next = false;
1594                let len = self.windows.len();
1595                if len > 1 {
1596                    for i in (0..len - 1).rev() {
1597                        let is_candidate = {
1598                            let w = &self.windows[i];
1599                            w.workspace == self.active_workspace && !w.is_minimized
1600                        };
1601                        if is_candidate {
1602                            self.activate_window(i);
1603                            found_next = true;
1604                            break;
1605                        }
1606                    }
1607                }
1608                if !found_next {
1609                    self.terminal_focused = true;
1610                }
1611                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1612                // 最小化時の音
1613            }
1614            return true;
1615        }
1616
1617        // 4. Alt + F10 (通常のフローティング状態へ移行)
1618        if alt_pressed && keycode == 0x44 {
1619            let matches_ws = if let Some(win) = self.windows.last() {
1620                win.workspace == self.active_workspace
1621            } else {
1622                false
1623            };
1624
1625            if matches_ws {
1626                let old_rect = self
1627                    .windows
1628                    .last()
1629                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1630                if let Some(r) = old_rect {
1631                    self.add_dirty_rect(r);
1632                }
1633
1634                if let Some(win) = self.windows.last_mut() {
1635                    win.is_fullscreen = false;
1636                    win.x = win.saved_x;
1637                    win.y = win.saved_y;
1638                    let (rw, rh) = (win.saved_w, win.saved_h);
1639                    win.resize(rw, rh);
1640                }
1641                self.dirty = true;
1642
1643                let new_rect = self
1644                    .windows
1645                    .last()
1646                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1647                if let Some(r) = new_rect {
1648                    self.add_dirty_rect(r);
1649                }
1650                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1651            }
1652            return true;
1653        }
1654
1655        // 5. Alt + F11 (フローティング最大化)
1656        if alt_pressed && keycode == 0x57 {
1657            let matches_ws = if let Some(win) = self.windows.last() {
1658                win.workspace == self.active_workspace
1659            } else {
1660                false
1661            };
1662
1663            if matches_ws {
1664                let old_rect = self
1665                    .windows
1666                    .last()
1667                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1668                if let Some(r) = old_rect {
1669                    self.add_dirty_rect(r);
1670                }
1671
1672                if let Some(win) = self.windows.last_mut() {
1673                    if !win.is_fullscreen {
1674                        win.saved_x = win.x;
1675                        win.saved_y = win.y;
1676                        win.saved_w = win.width;
1677                        win.saved_h = win.height;
1678                    }
1679
1680                    // メイン空間表示可能領域 (左端縦タブ100px、右端モニター領域を除く)
1681                    let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1682                    let avail_w = screen.width.saturating_sub(100 + sysmon_w);
1683                    win.x = 100;
1684                    win.y = 0;
1685                    let sh = screen.height.saturating_sub(crate::kernel::fn_bar::BAR_HEIGHT);
1686                    win.resize(avail_w, sh);
1687                }
1688                self.dirty = true;
1689
1690                let new_rect = self
1691                    .windows
1692                    .last()
1693                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1694                if let Some(r) = new_rect {
1695                    self.add_dirty_rect(r);
1696                }
1697                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1698            }
1699            return true;
1700        }
1701
1702        // 6. Alt + F12 (アプリ全体のフルスクリーン化)
1703        if alt_pressed && keycode == 0x58 {
1704            // 空きスロット (2..=8) 探索 (可変借用の前に実施して競合を回避)
1705            let mut target_ws = None;
1706            for ws in 2..=8 {
1707                let mut occupied = false;
1708                for w in &self.windows {
1709                    if w.workspace == ws && !w.is_minimized {
1710                        occupied = true;
1711                        break;
1712                    }
1713                }
1714                if !occupied {
1715                    target_ws = Some(ws);
1716                    break;
1717                }
1718            }
1719
1720            if let Some(ws) = target_ws {
1721                let old_rect = self
1722                    .windows
1723                    .last()
1724                    .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1725                if let Some(r) = old_rect {
1726                    self.add_dirty_rect(r);
1727                }
1728
1729                if let Some(win) = self.windows.last_mut() {
1730                    if !win.is_fullscreen {
1731                        win.saved_x = win.x;
1732                        win.saved_y = win.y;
1733                        win.saved_w = win.width;
1734                        win.saved_h = win.height;
1735                    }
1736
1737                    win.is_fullscreen = true;
1738                    win.workspace = ws;
1739                    win.x = 0;
1740                    win.y = 0;
1741                    let (sw, sh) = (screen.width, screen.height);
1742                    win.resize(sw, sh);
1743                }
1744                self.active_workspace = ws;
1745                self.dirty = true;
1746                let (sw, sh) = (screen.width, screen.height);
1747                self.add_dirty_rect(Rect::new(0, 0, sw, sh));
1748                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1749            } else {
1750                // 空きスロット上限による拒否 (警告音)
1751                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1752            }
1753            return true;
1754        }
1755
1756        // --- 通常のアプリ宛キーイベント送信 ---
1757        if self.terminal_focused {
1758            return false;
1759        }
1760        let is_active = if let Some(win) = self.windows.last() {
1761            win.workspace == self.active_workspace && !win.is_minimized
1762        } else {
1763            false
1764        };
1765
1766        if is_active {
1767            if let Some(win) = self.windows.last_mut() {
1768                win.app.on_key(keycode, pressed, ascii);
1769            }
1770
1771            let rect = self
1772                .windows
1773                .last()
1774                .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1775            if let Some(r) = rect {
1776                self.add_dirty_rect(r);
1777            }
1778            self.dirty = true;
1779            return true;
1780        }
1781        false
1782    }
1783
1784    /// 何らかのアクティブなウィンドウがタイマー再描画を要求しているか
1785    pub fn needs_timer_redraw(&self) -> bool {
1786        for win in &self.windows {
1787            if win.workspace == self.active_workspace
1788                && !win.is_minimized
1789                && win.app.needs_timer_redraw()
1790            {
1791                return true;
1792            }
1793        }
1794        false
1795    }
1796}
1797
1798pub fn init() {
1799    unsafe {
1800        GLOBAL_WM = Some(ReentrantMutex::new(WindowManager::new()));
1801    }
1802}
1803
1804pub fn get_instance() -> ReentrantMutexGuard<'static, WindowManager> {
1805    // init() 前に呼ばれた場合も panic せず、その場で初期化する
1806    unsafe {
1807        GLOBAL_WM
1808            .get_or_insert_with(|| ReentrantMutex::new(WindowManager::new()))
1809            .lock()
1810    }
1811}
1812
1813use core::cell::UnsafeCell;
1814use core::sync::atomic::{AtomicUsize, Ordering};
1815
1816pub struct ReentrantMutex<T> {
1817    inner: UnsafeCell<T>,
1818    owner: AtomicUsize,
1819    recursion: AtomicUsize,
1820}
1821
1822unsafe impl<T: Send> Send for ReentrantMutex<T> {}
1823unsafe impl<T: Send> Sync for ReentrantMutex<T> {}
1824
1825impl<T> ReentrantMutex<T> {
1826    pub const fn new(value: T) -> Self {
1827        Self {
1828            inner: UnsafeCell::new(value),
1829            owner: AtomicUsize::new(usize::MAX),
1830            recursion: AtomicUsize::new(0),
1831        }
1832    }
1833
1834    pub fn lock(&self) -> ReentrantMutexGuard<'_, T> {
1835        let cid = crate::kernel::scheduler::core_id();
1836        loop {
1837            if self.owner.load(Ordering::Relaxed) == cid {
1838                self.recursion.fetch_add(1, Ordering::Relaxed);
1839                return ReentrantMutexGuard { mutex: self };
1840            }
1841            if self
1842                .owner
1843                .compare_exchange_weak(usize::MAX, cid, Ordering::Acquire, Ordering::Relaxed)
1844                .is_ok()
1845            {
1846                self.recursion.store(1, Ordering::Relaxed);
1847                return ReentrantMutexGuard { mutex: self };
1848            }
1849            core::hint::spin_loop();
1850        }
1851    }
1852}
1853
1854pub struct ReentrantMutexGuard<'a, T> {
1855    mutex: &'a ReentrantMutex<T>,
1856}
1857
1858impl<'a, T> core::ops::Deref for ReentrantMutexGuard<'a, T> {
1859    type Target = T;
1860    fn deref(&self) -> &Self::Target {
1861        unsafe { &*self.mutex.inner.get() }
1862    }
1863}
1864
1865impl<'a, T> core::ops::DerefMut for ReentrantMutexGuard<'a, T> {
1866    fn deref_mut(&mut self) -> &mut Self::Target {
1867        unsafe { &mut *self.mutex.inner.get() }
1868    }
1869}
1870
1871impl<'a, T> Drop for ReentrantMutexGuard<'a, T> {
1872    fn drop(&mut self) {
1873        let rec = self.mutex.recursion.fetch_sub(1, Ordering::Relaxed);
1874        if rec == 1 {
1875            self.mutex.owner.store(usize::MAX, Ordering::Release);
1876        }
1877    }
1878}