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