Skip to main content

atmos/
main.rs

1#![cfg_attr(not(test), no_std)]
2#![cfg_attr(not(test), no_main)]
3#![allow(dead_code)]
4#![allow(static_mut_refs)]
5// ベアメタル OS 特有の意図的スタイル。以下は「修正」がかえって安全性・可読性を損なうため許可する:
6// - needless_range_loop: ハードウェア/カーネルの `static mut` 配列(RUNQUEUES/TCP_SOCKETS/
7//   ARP_CACHE 等)への添字ループ。イテレータ化は `&mut` 借用の衝突や unsafe を増やす。
8// - too_many_arguments: 描画/レイアウト系の座標・色を取る関数(draw_string(x,y,...,color,size) 等)。
9// - type_complexity: JS エンジンの共有キュー(`Rc<RefCell<VecDeque<..>>>`)等の素直な型。
10#![allow(clippy::needless_range_loop)]
11#![allow(clippy::too_many_arguments)]
12#![allow(clippy::type_complexity)]
13// 過去 2 回のカーネルパニックは「&str をバイト境界でスライスして UTF-8 文字の途中で panic」が
14// 原因だった(TECHNOTE 参照)。クレート全体で &str のバイト境界スライスを禁止し、位置は
15// find()/strip_prefix()(ASCII ニードル)由来か str::get(境界違反で None)で安全に扱う。
16// 添字が必要な低レベル処理は &[u8] に対して行う(このリントは &str のみ対象)。
17#![deny(clippy::string_slice)]
18
19extern crate alloc;
20
21use core::arch::global_asm;
22use core::panic::PanicInfo;
23
24// スタートアップアセンブリ boot.S を統合する
25#[cfg(not(test))]
26global_asm!(include_str!("kernel/boot.S"));
27
28#[macro_use]
29pub mod kernel;
30pub mod apps;
31pub mod os_lib;
32
33use kernel::draw::Screen;
34use kernel::mailbox::{FrameBufferRequest, Mailbox};
35use kernel::uart::Uart;
36
37// --------------------------------------------------------------------
38// 2段階キーリピート設定 & 状態変数
39// --------------------------------------------------------------------
40pub const AUTO_TEST_BROWSER: bool = true; // 自動テスト用フラグ
41
42// グローバルなシステム状態
43pub struct SystemState {
44    pub tick: u64,
45}
46
47pub struct KeyRepeatParams {
48    pub first_delay_ms: u32,
49    pub first_interval_ms: u32,
50    pub second_repeat_count: u32,
51    pub second_delay_ms: u32,
52    pub second_interval_ms: u32,
53}
54
55pub static mut REPEAT_PARAMS: KeyRepeatParams = KeyRepeatParams {
56    first_delay_ms: 200,
57    first_interval_ms: 15,
58    second_repeat_count: 25,
59    second_delay_ms: 10000,
60    second_interval_ms: 30,
61};
62
63struct KeyRepeatState {
64    active_keycode: u8,
65    press_start_ms: u64,
66    last_repeat_ms: u64,
67    stage: u8, // 0: なし, 1: 第一リピート, 2: 第二リピート
68}
69
70static mut REPEAT_STATE: KeyRepeatState = KeyRepeatState {
71    active_keycode: 0,
72    press_start_ms: 0,
73    last_repeat_ms: 0,
74    stage: 0,
75};
76
77// 共有スクリーンオブジェクト
78pub static mut CURRENT_SCREEN: Option<Screen> = None;
79
80extern "C" {
81    static exception_vector_table: u8;
82    static __bss_end: u8;
83    static __cpu_stacks_end: u8;
84    static mut __cpu_stacks_start: u64;
85
86    static mut spin_cpu1: u64;
87    static mut spin_cpu2: u64;
88    static mut spin_cpu3: u64;
89
90    fn secondary_entry();
91}
92
93/// コア0スタックの番兵値(`decompress_brotli`の旧64KBスタック配列がスタック
94/// オーバーフローを起こしブート全体を無音でフリーズさせていた事故(2026-07-23
95/// 発見・修正)を踏まえた再発防止策)。ハードウェアのガードページ(MMUで
96/// 未マップにする方式)は改修範囲が大きいため、まずソフトウェア側の番兵チェック
97/// で「検知だけでもできる」状態にする。コア0のスタックは`link.ld`の
98/// `__cpu_stacks_start`(下端、下方向に伸びるスタックが溢れた場合に最初に
99/// 踏み潰す場所)から`__stack_cpu0_top`(上端、64KB)まで。ここへ既知の
100/// マジック値を書き込んでおき、スタックを多用する処理(起動時セルフテスト等)
101/// の前後で`check_stack_canary()`を呼んで破壊されていないか確認する。
102const STACK_CANARY_MAGIC: u64 = 0xDEAD_BEEF_CAFE_BABE;
103
104/// コア0スタック下端に番兵値を書き込む。BSSクリア後・スタックを多用する処理
105/// より前に一度だけ呼ぶこと。
106unsafe fn write_stack_canary() {
107    let ptr = core::ptr::addr_of_mut!(__cpu_stacks_start);
108    core::ptr::write_volatile(ptr, STACK_CANARY_MAGIC);
109}
110
111/// コア0スタック下端の番兵値が破壊されていないか確認する。破壊されていれば
112/// (=スタックオーバーフローでコア0スタック下端を踏み越えた可能性が高い)
113/// `false`を返す。呼び出し側は既存の`KERNEL PANIC`と同じ書式で明確に報告し、
114/// 原因調査が「ビルドごとに再現性が変わる謎のハング」に逆戻りしないようにする。
115unsafe fn check_stack_canary() -> bool {
116    let ptr = core::ptr::addr_of!(__cpu_stacks_start);
117    core::ptr::read_volatile(ptr) == STACK_CANARY_MAGIC
118}
119
120/// `check_stack_canary()`がfalseを返した際に呼ぶ、`rust_el1_sync_handler`
121/// (`kernel/syscall.rs`)と同じ書式の明確な報告。ハードウェア例外を伴わない
122/// (未マップ領域への到達ではなく隣接する有効なBSS領域を静かに破壊する)
123/// タイプのスタックオーバーフローのため、既存のEL1例外ハンドラでは検出
124/// できない。パニックはさせず(既に一部破壊されている可能性はあるが、
125/// 停止せず継続した方がシリアルログを最後まで残せて調査しやすいため)、
126/// 目立つ警告ログのみを出す。
127fn report_stack_canary_violation(context: &str) {
128    println!("==================================================");
129    println!("!!! STACK OVERFLOW DETECTED (software canary) !!!");
130    println!("==================================================");
131    println!("  Context: {}", context);
132    println!(
133        "  Core0 stack base (__cpu_stacks_start) canary value was overwritten."
134    );
135    println!(
136        "  Expected: 0x{:016x}",
137        STACK_CANARY_MAGIC
138    );
139    println!(
140        "  This means some function used more than 64KB of stack and silently"
141    );
142    println!(
143        "  corrupted adjacent .bss memory (no MMU guard page here yet)."
144    );
145    println!("==================================================");
146}
147
148#[no_mangle]
149pub extern "C" fn kmain() -> ! {
150    // 1. UARTの初期化
151    Uart::init();
152    // スタックオーバーフロー検知用の番兵をコア0スタック下端へ書き込む
153    // (BSSクリア直後・スタックを多用する処理より前)。
154    unsafe {
155        write_stack_canary();
156    }
157    println!("==================================================");
158    println!("   AtmOS v3 (ARM64 / OS Base) starting...");
159    println!(
160        "   Version: {} (Git: {})",
161        crate::kernel::fs::BUILD_VERSION,
162        crate::kernel::fs::BUILD_GIT_HASH
163    );
164    println!("   Build Time: {}", crate::kernel::fs::BUILD_TIME);
165    println!("   Author: SUGIMURA Hiroshi (Sugimura Laboratory)");
166    println!("==================================================");
167    crate::warn!(
168        "[SYS] Build v{} git:{} {}",
169        crate::kernel::fs::BUILD_VERSION,
170        crate::kernel::fs::BUILD_GIT_HASH,
171        crate::kernel::fs::BUILD_TIME
172    );
173
174    // 2. GPUにフレームバッファ(画面)の初期化を要求 (MMU/キャッシュ有効化前に実行することで不整合を回避)
175    // 実機ではモニタがサポートしない解像度を要求すると Mailbox が失敗するため、
176    // 複数の解像度で順番にフォールバックを試みる
177    crate::info!("[GPU] Attempting FrameBuffer allocation with fallback resolutions...");
178    let resolutions: [(u32, u32); 5] = [
179        (1024, 600), // 7インチ HDMI パネル等のネイティブ解像度を最優先
180        (1280, 720),
181        (1920, 1080),
182        (1024, 768),
183        (640, 480),
184    ];
185
186    let mut fb_info = None;
187    for &(w, h) in resolutions.iter() {
188        crate::debug!("[GPU] Trying {}x{}...", w, h);
189        let mut mbox_req = FrameBufferRequest::new(w, h);
190        let buf_ptr = &mut mbox_req as *mut FrameBufferRequest as *mut u32;
191        let buf_slice = unsafe { core::slice::from_raw_parts_mut(buf_ptr, 32) };
192
193        // 実機では D-Cache と GPU 間のコヒーレンシが保証されないため、
194        // Mailbox バッファをフラッシュしてからGPUに送信する
195        unsafe {
196            let addr = buf_slice.as_ptr() as usize;
197            let size = buf_slice.len() * 4;
198            let cache_line = 64usize;
199            let start = addr & !(cache_line - 1);
200            let end = addr + size;
201            let mut cur = start;
202            while cur < end {
203                core::arch::asm!("dc civac, {}", in(reg) cur, options(nostack));
204                cur += cache_line;
205            }
206            core::arch::asm!("dsb sy", options(nostack));
207        }
208
209        if Mailbox::call(buf_slice, kernel::mailbox::MBOX_CH_PROP).is_ok() {
210            if let Some(info) = mbox_req.get_info() {
211                let fb_addr = info.pointer as usize;
212                // 境界は `mmu.rs` が権威(マップを決めている側)。
213                let uncached = fb_addr >= kernel::mmu::DEVICE_REGION_START;
214                crate::info!(
215                    "[GPU] FrameBuffer OK: {}x{} pitch={} vptr={:?} vh={} size={}",
216                    info.width,
217                    info.height,
218                    info.pitch,
219                    info.pointer,
220                    info.virtual_height,
221                    info.size
222                );
223                if !uncached {
224                    crate::warn!("[GPU] VRAM in cached RAM region (<0x38000000)!");
225                }
226                fb_info = Some(info);
227                break;
228            } else {
229                crate::debug!("[GPU] Mailbox OK but parse failed for {}x{}", w, h);
230            }
231        } else {
232            crate::debug!("[GPU] Mailbox call failed for {}x{}", w, h);
233        }
234    }
235
236    let info = match fb_info {
237        Some(i) => i,
238        None => {
239            crate::error!(
240                "[GPU] FATAL: FrameBuffer allocation failed for all resolutions. Halting."
241            );
242            loop {
243                unsafe {
244                    core::arch::asm!("wfe");
245                }
246            }
247        }
248    };
249    crate::println!("{:.<50} [OK]", "1. Graphics System (FrameBuffer)");
250    // 起動時スプラッシュ画面の初期化と表示 (黒画面クリアのみ先行)
251    unsafe {
252        let pitch = if info.pitch == 0 {
253            info.width * 4
254        } else {
255            info.pitch
256        };
257        let screen = Screen::new(info.pointer, info.width, info.height, pitch);
258        CURRENT_SCREEN = Some(screen);
259        kernel::draw::SPLASH_ACTIVE = true;
260        if let Some(ref screen) = CURRENT_SCREEN {
261            screen.clear(kernel::draw::Color(0xFF000000));
262        }
263    }
264
265    // 3. 例外ベクタテーブルを VBAR_EL1 に登録
266    let vector_addr = unsafe { &exception_vector_table as *const u8 as usize };
267    unsafe {
268        core::arch::asm!("msr vbar_el1, {}", in(reg) vector_addr);
269    }
270    crate::info!(
271        "[SYS] Exception Vector Table registered at 0x{:016X}",
272        vector_addr
273    );
274
275    // 4. MMU恒等マップ(16KBページ&L1/L2キャッシュ)を有効化
276    kernel::mmu::init();
277
278    // VideoCore IV V3D 初期化
279    if let Err(e) = crate::kernel::v3d::init() {
280        crate::warn!("[V3D] V3D Init Error: {}", e);
281    }
282
283    // 5. カーネルヒープアロケータの初期化
284    let heap_start = unsafe { &__cpu_stacks_end as *const u8 as usize };
285    // 大きさは `allocator::KERNEL_HEAP_SIZE` が唯一の定義。
286    // `fs/mod.rs` がヒープ直後から RAMDisk を取るので、
287    // ここで別の値を書くと領域が重なる。
288    let heap_size = kernel::allocator::KERNEL_HEAP_SIZE;
289    kernel::allocator::init(heap_start, heap_size);
290    crate::info!(
291        "[MEM] Kernel Heap Allocator initialized. (Heap start: 0x{:016X}, Heap size: 512 MB)",
292        heap_start
293    );
294    crate::println!("{:.<50} [OK]", "2. Memory System (MMU & Heap)");
295    // ヒープアロケータが有効になったのでロゴを描画
296    unsafe {
297        kernel::draw::draw_splash_logo();
298    }
299
300    // 起動時セルフテスト群は `selftest` feature(CI/開発ビルドのみ)で有効化する。
301    // 通常起動(make run)ではこのブロックごとコンパイルされないため、起動が速くなり、
302    // かつテストコード側の不具合(かつての decompress_brotli スタックオーバーフローや、
303    // JS 集計ずれ→`tests_failed`→`wfe` 無限ループ等)が本番起動を巻き込むことを防ぐ。
304    // 回帰検出は CI(`--features selftest` + `test_browser.sh`)で従来通り行う。
305    #[cfg(feature = "selftest")]
306    {
307    // 各自己テストをサイレントモードで実行
308    let mut tests_failed = false;
309    kernel::uart::set_silent(true);
310
311    let mut js_ok = false;
312    let mut bigint_ok = false;
313    let mut aura_ok = false;
314    let mut css_ok = false;
315    let mut layout_ok = false;
316    let mut ime_ok = false;
317    let mut cloud_ok = false;
318    let mut h264_ok = false;
319    let mut h264_golden_ok = false;
320    let mut reclock_ok = false;
321    let mut fs_ok = false;
322    let mut usbsched_ok = false;
323
324    // 【安全性・重要、2026-07-23】この起動時診断行はここに置かれた `decompress_brotli`
325    // 呼び出し自体(入力値を`[3]`/`[6]`いずれに変えても)が、この起動シーケンス最早期・
326    // 低ヒープフラグメント状態で毎回QEMU起動を完全にハングさせることを実証したため、
327    // 呼び出しごと削除した(`woff2_brotli_ok`はどのテストのpass/fail判定にも使われて
328    // いない純粋な診断ログ専用の値であり、削除しても機能上の後退は無い)。
329    // `decompress_brotli`のアロケータ状態依存のハングは既知の未解決リスクであり、
330    // 根本原因が特定されるまでこの関数を起動パスの早期段階で無条件に呼び出しては
331    // ならない。詳細は spec/walkthrough.md 参照。
332
333    // 【2026-07-24 発見・追加】各カテゴリの合否は`pass == total`の一言判定のみで、
334    // 個別テストの`JS_SELFTEST FAIL: ...`行が一件も出ないまま(=全ケースが
335    // 実際にはPASSしているにも関わらず)カテゴリ丸ごとFAIL表示になる「集計ずれ」
336    // (`total`側の手計算の合計式が実際のケース数とズレる等)が起きても、
337    // どこが食い違っているのか一切わからなかった。成功時は従来どおり静かなまま、
338    // 失敗時のみ`pass`/`total`の実数値を出力し、個別FAIL行が無いのに合計だけ
339    // 合わない「集計ずれ」なのか、実際に個別テストが落ちているのかを区別できる
340    // ようにする。
341
342    // JS
343    let (pass, total) = os_lib::js::selftest();
344    if pass == total {
345        js_ok = true;
346    } else {
347        tests_failed = true;
348        crate::println!("[SELFTEST] JS: {}/{} passed", pass, total);
349    }
350
351    // BigInt
352    let (pass, total) = os_lib::js::bigint::selftest();
353    if pass == total {
354        bigint_ok = true;
355    } else {
356        tests_failed = true;
357        crate::println!("[SELFTEST] BigInt: {}/{} passed", pass, total);
358    }
359
360    // Aura
361    let (pass, total) = os_lib::aura::selftest();
362    if pass == total {
363        aura_ok = true;
364    } else {
365        tests_failed = true;
366        crate::println!("[SELFTEST] Aura: {}/{} passed", pass, total);
367    }
368
369    // CSS
370    let (pass, total) = os_lib::css::selftest();
371    if pass == total {
372        css_ok = true;
373    } else {
374        tests_failed = true;
375        crate::println!("[SELFTEST] CSS: {}/{} passed", pass, total);
376    }
377
378    // Layout
379    let (pass, total) = os_lib::layout::selftest();
380    if pass == total {
381        layout_ok = true;
382    } else {
383        tests_failed = true;
384        crate::println!("[SELFTEST] Layout: {}/{} passed", pass, total);
385    }
386
387    // IME
388    let (pass, total) = kernel::ime::selftest();
389    if pass == total {
390        ime_ok = true;
391    } else {
392        tests_failed = true;
393        crate::println!("[SELFTEST] IME: {}/{} passed", pass, total);
394    }
395
396    // Cloud
397    let (pass, total) = os_lib::cloud::selftest();
398    if pass == total {
399        cloud_ok = true;
400    } else {
401        tests_failed = true;
402        crate::println!("[SELFTEST] Cloud: {}/{} passed", pass, total);
403    }
404
405    // H264
406    let (pass, total) = os_lib::h264::selftest();
407    if pass == total {
408        h264_ok = true;
409    } else {
410        tests_failed = true;
411        crate::println!("[SELFTEST] H264: {}/{} passed", pass, total);
412    }
413
414    // H264 Golden
415    if os_lib::h264_decode::golden_test() {
416        h264_golden_ok = true;
417    } else {
418        tests_failed = true;
419        crate::println!("[SELFTEST] H264 Golden: failed");
420    }
421
422    // Reclock
423    if kernel::reclock::selftest() {
424        reclock_ok = true;
425    } else {
426        tests_failed = true;
427        crate::println!("[SELFTEST] Reclock: failed");
428    }
429
430    // FS
431    let (pass, total) = kernel::fs::selftest();
432    if pass == total {
433        fs_ok = true;
434    } else {
435        tests_failed = true;
436        crate::println!("[SELFTEST] FS: {}/{} passed", pass, total);
437    }
438
439    // USB Sched
440    let (pass, total) = kernel::usb::frame_sched::selftest();
441    if pass == total {
442        usbsched_ok = true;
443    } else {
444        tests_failed = true;
445        crate::println!("[SELFTEST] USB Sched: {}/{} passed", pass, total);
446    }
447
448    kernel::uart::set_silent(false);
449
450    let js_str = if js_ok {
451        "JS_SELFTEST: PASS"
452    } else {
453        "JS_SELFTEST: FAIL"
454    };
455    let bigint_str = if bigint_ok {
456        "BIGINT_SELFTEST: PASS"
457    } else {
458        "BIGINT_SELFTEST: FAIL"
459    };
460    let aura_str = if aura_ok {
461        "AURA_SELFTEST: PASS"
462    } else {
463        "AURA_SELFTEST: FAIL"
464    };
465    let css_str = if css_ok {
466        "CSS_SELFTEST: PASS"
467    } else {
468        "CSS_SELFTEST: FAIL"
469    };
470    let layout_str = if layout_ok {
471        "LAYOUT_SELFTEST: PASS"
472    } else {
473        "LAYOUT_SELFTEST: FAIL"
474    };
475    let ime_str = if ime_ok {
476        "IME_SELFTEST: PASS"
477    } else {
478        "IME_SELFTEST: FAIL"
479    };
480    let cloud_str = if cloud_ok {
481        "CLOUD_SELFTEST: PASS"
482    } else {
483        "CLOUD_SELFTEST: FAIL"
484    };
485    let h264_str = if h264_ok {
486        "H264_SELFTEST: PASS"
487    } else {
488        "H264_SELFTEST: FAIL"
489    };
490    let h264_golden_str = if h264_golden_ok {
491        "H264_GOLDEN: PASS"
492    } else {
493        "H264_GOLDEN: FAIL"
494    };
495    let reclock_str = if reclock_ok {
496        "RECLOCK_SELFTEST: PASS"
497    } else {
498        "RECLOCK_SELFTEST: FAIL"
499    };
500    let fs_str = if fs_ok {
501        "FS_SELFTEST: PASS"
502    } else {
503        "FS_SELFTEST: FAIL"
504    };
505    let usbsched_str = if usbsched_ok {
506        "USBSCHED_SELFTEST: PASS"
507    } else {
508        "USBSCHED_SELFTEST: FAIL"
509    };
510
511    if !tests_failed {
512        // expectが順次アサーションできるようにPASSマーカーを1行に埋め込む
513        crate::println!(
514            "3. Core Software Self-Tests ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) [OK]",
515            js_str,
516            bigint_str,
517            aura_str,
518            css_str,
519            layout_str,
520            ime_str,
521            cloud_str,
522            h264_str,
523            h264_golden_str,
524            reclock_str,
525            fs_str,
526            usbsched_str
527        );
528    } else {
529        crate::println!(
530            "3. Core Software Self-Tests ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}) [FAIL]",
531            js_str,
532            bigint_str,
533            aura_str,
534            css_str,
535            layout_str,
536            ime_str,
537            cloud_str,
538            h264_str,
539            h264_golden_str,
540            reclock_str,
541            fs_str,
542            usbsched_str
543        );
544        loop {
545            unsafe {
546                core::arch::asm!("wfe");
547            }
548        }
549    }
550    } // end #[cfg(feature = "selftest")] 起動時セルフテスト群
551
552    // 起動時セルフテスト群はスタック使用量が最も深くなりやすい経路(かつて
553    // `decompress_brotli`のスタック配列がここで64KBコアスタックを溢れさせ、
554    // 無音のままブート全体をフリーズさせていた)だったため、直後に番兵を
555    // 確認する。`selftest` feature が無効なビルドでも呼ぶことで、将来他の
556    // 経路がスタックを溢れさせた場合の検知にも流用できる。
557    unsafe {
558        if !check_stack_canary() {
559            report_stack_canary_violation("selftest block (main.rs)");
560        }
561    }
562
563    // 6. 独自ファイルシステム SylFS のマウント
564    // マウントは失敗しても起動を続けるが、**[OK] と表示してはいけない**
565    // (フォーマットできていないのに正常起動に見えると、以後の「保存できない」
566    //  という症状の原因がここだと誰も気づけない)。
567    match kernel::fs::mount() {
568        Ok(()) => crate::println!("{:.<50} [OK]", "4. File System (SylFS)"),
569        Err(e) => {
570            crate::error!("[FS] Mount completed with errors: {}", e);
571            crate::println!("{:.<50} [NG] {}", "4. File System (SylFS)", e);
572        }
573    }
574
575    // 6.5 Phase 3 ネットワークコア初期化(現時点ではソフト実装)
576    kernel::net::init();
577
578    // 7. スクリーンオブジェクトの作成
579    let pitch = if info.pitch == 0 {
580        info.width * 4
581    } else {
582        info.pitch
583    };
584    let screen = Screen::new(info.pointer, info.width, info.height, pitch);
585    // ダブルバッファ(2 ページ = virtual_height 2*H)が実際に確保できたかを Screen に伝える。
586    // enable_vsync() がこれを見て、確保できた時だけページフリップを有効化する
587    // (未確保のまま flip すると page1 がフレームバッファ外を指し横線ノイズになる)。
588    screen.set_fb_geometry(info.virtual_height, info.size);
589    unsafe {
590        CURRENT_SCREEN = Some(screen);
591    }
592    kernel::audio::Audio::init();
593    crate::info!("[GPU] Screen initialization completed!");
594    crate::println!("{:.<50} [OK]", "5. Audio Systems");
595
596    // 8. プロセススケジューラの初期化
597    kernel::scheduler::init();
598
599    // デモプロセスの起動 (ここをチャットUI of プロセスに差し替える)
600    kernel::scheduler::spawn_on_core(
601        gui_shell_process,
602        kernel::scheduler::Priority::High,
603        "gui_shell",
604        0,
605    );
606
607    // EL0テストプロセスは確認済みのためコメントアウト
608    // kernel::scheduler::spawn_user_on_core(
609    //     user_test_normal,
610    //     kernel::scheduler::Priority::Normal,
611    //     "user_test_normal",
612    //     0,
613    // );
614    // kernel::scheduler::spawn_user_on_core(
615    //     user_test_abort,
616    //     kernel::scheduler::Priority::Normal,
617    //     "user_test_abort",
618    //     0,
619    // );
620
621    // 9. 割り込みコントローラと全体の割り込みを有効化
622    crate::info!("[SYS] Initializing Interrupt Controller...");
623    kernel::interrupt::init();
624
625    // 10. タイマー割り込み(10ms)の有効化
626    kernel::timer::init();
627
628    // 10.5. USB ホストコントローラの初期化と列挙を「割り込み有効化の前」に実行する。
629
630    kernel::usb::init();
631
632    kernel::interrupt::enable_interrupts();
633    crate::println!("{:.<50} [OK]", "6. USB Host Controller & HID Stack");
634
635    crate::println!("{:.<50} [OK]", "7. System Scheduler & Interrupts");
636    crate::println!("--------------------------------------------------");
637    crate::println!("   AtmOS OS Base Phase 3 (merged) running!");
638    crate::println!("   Author: SUGIMURA Hiroshi (Sugimura Laboratoly)");
639    crate::println!("--------------------------------------------------");
640    unsafe {
641        kernel::draw::SPLASH_ACTIVE = false;
642    }
643
644    crate::info!("[SYS] Waking up secondary cores...");
645    unsafe {
646        // 1. Write startup address (jump to secondary_entry trampoline).
647        // For physical hardware (spinning on spin_cpu variables)
648        core::ptr::write_volatile(
649            &mut spin_cpu1 as *mut u64,
650            secondary_entry as *const () as u64,
651        );
652        core::ptr::write_volatile(
653            &mut spin_cpu2 as *mut u64,
654            secondary_entry as *const () as u64,
655        );
656        core::ptr::write_volatile(
657            &mut spin_cpu3 as *mut u64,
658            secondary_entry as *const () as u64,
659        );
660
661        // For QEMU (spinning on mailboxes 0xe0, 0xe8, 0xf0)
662        core::ptr::write_volatile(0xe0 as *mut u64, secondary_entry as *const () as u64);
663        core::ptr::write_volatile(0xe8 as *mut u64, secondary_entry as *const () as u64);
664        core::ptr::write_volatile(0xf0 as *mut u64, secondary_entry as *const () as u64);
665
666        // 2. Force compiler to issue writes before inline assembly
667        core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
668
669        // 3. Clean cache to Point of Coherency (DDR)
670        core::arch::asm!(
671            "dc cvac, {}",
672            "dc cvac, {}",
673            "dc cvac, {}",
674            "dsb sy",
675            in(reg) &spin_cpu1 as *const u64,
676            in(reg) &spin_cpu2 as *const u64,
677            in(reg) &spin_cpu3 as *const u64,
678        );
679
680        // Clean cache for QEMU mailboxes
681        core::arch::asm!(
682            "dc cvac, {0}",
683            "dc cvac, {1}",
684            "dc cvac, {2}",
685            "dsb sy",
686            in(reg) 0xe0usize,
687            in(reg) 0xe8usize,
688            in(reg) 0xf0usize,
689        );
690
691        // 4. Wake up cores
692        core::arch::asm!("sev");
693    }
694
695    // カーネルのメインコンテキストはダミープロセス0として他のスレッドへ制御を譲り続けます
696    loop {
697        kernel::scheduler::yield_now();
698    }
699}
700
701#[no_mangle]
702pub extern "C" fn secondary_startup() -> ! {
703    let core_id: usize;
704    unsafe {
705        let mpidr: u64;
706        core::arch::asm!("mrs {}, mpidr_el1", out(reg) mpidr);
707        core_id = (mpidr & 0xFF) as usize;
708    }
709    crate::debug!("[SMP] Core {}: secondary_startup entry", core_id);
710
711    // VBARの設定
712    let vector_addr = unsafe { &exception_vector_table as *const u8 as usize };
713    unsafe {
714        core::arch::asm!("msr vbar_el1, {}", in(reg) vector_addr);
715    }
716    crate::debug!("[SMP] Core {}: VBAR set", core_id);
717
718    // MMUとキャッシュを有効化(secondary では一度だけ実行)
719    kernel::mmu::init_secondary();
720    crate::debug!("[SMP] Core {}: MMU ready", core_id);
721
722    // 割り込みコントローラ設定 -> タイマー有効化 の順で初期化
723    kernel::interrupt::init_secondary();
724    crate::debug!("[SMP] Core {}: IRQ controller ready", core_id);
725    kernel::timer::init_secondary();
726    crate::debug!("[SMP] Core {}: timer ready", core_id);
727
728    // コア起動完了は 1 行サマリのみ常時表示
729    println!("[SMP] Core {} started!", core_id);
730
731    // スケジューラに idle プロセスを登録
732    kernel::scheduler::register_idle_process(core_id);
733
734    // 割り込みを許可してスケジューラの稼働を待つ
735    kernel::interrupt::enable_interrupts();
736
737    // スケジューラのループに入るか待機
738    loop {
739        kernel::scheduler::yield_now();
740    }
741}
742
743/// GUIシェルプロセス(チャットUI)
744static mut TABLET_MAX_X: u32 = 32768;
745static mut TABLET_MAX_Y: u32 = 32768;
746
747fn usb_poll_thread() {
748    crate::warn!(
749        "USB Poll Thread started on Core {}",
750        crate::kernel::scheduler::core_id()
751    );
752    unsafe {
753        crate::kernel::usb::POLL_THREAD_ACTIVE = true;
754    }
755    let mut divider: u32 = 0;
756    loop {
757        crate::kernel::usb::poll();
758        divider = divider.wrapping_add(1);
759        if divider.is_multiple_of(32) {
760            crate::kernel::net::dhcp_tick();
761        }
762        // NTP 同期はこのスレッドからは行わない。ntp_sync() は最大数十秒ブロックし得るため、
763        // ここで呼ぶと HID(キーボード/タブレット) ポーリングが停止し入力フリーズの原因になる。
764        // 同期処理は net_service_thread に分離した。
765        // ヒープ canary の定期診断: バッファオーバーフローの発生源を最初の破損時点で捕捉する。
766        // 一度検出したら以後は走査しない(最初の証拠を上書きしない・コストをかけない)。
767        static mut HEAP_CORRUPTION_FOUND: bool = false;
768        if divider.is_multiple_of(4096)
769            && unsafe { !HEAP_CORRUPTION_FOUND }
770            && crate::kernel::allocator::check_heap_integrity()
771        {
772            unsafe {
773                HEAP_CORRUPTION_FOUND = true;
774            }
775        }
776        let start = crate::kernel::timer::get_system_time_ms();
777        while crate::kernel::timer::get_system_time_ms().wrapping_sub(start) < 8 {
778            unsafe { core::arch::asm!("nop") };
779        }
780        crate::kernel::scheduler::yield_now(); // 10msウェイトから即時譲渡へ変更してレイテンシを極小化
781    }
782}
783
784/// ネットワークサービススレッド(NTP 同期などのブロッキング処理を担当)。
785/// HID ポーリング(usb_poll_thread)から分離することで、NTP の長時間ブロックが
786/// キーボード/タブレット入力を固める「起動直後フリーズ」を解消する。
787fn net_service_thread() {
788    crate::info!(
789        "Net Service Thread started on Core {}",
790        crate::kernel::scheduler::core_id()
791    );
792    let mut ntp_last_attempt_ms: u64 = 0;
793    loop {
794        // 【2026-07-24発見・修正】`poll_ethernet_data_plane_only()`はマルチコア
795        // でのUSBレジスタ/DMA_BUF競合を避けるためCore 2からの呼び出しのみ実際に
796        // 動作する設計だが、このスレッド自身の処理はNTP同期(起動直後・壁時計が
797        // 未設定の間だけ)に限られており、NTP同期完了後は一切ポーリングされて
798        // いなかった。このため「他コア(GUIレンダリングスレッド等)から開始した
799        // TCP/TLS接続は、たまたまこのスレッドがNTP同期でポーリング中でなければ
800        // 誰にもパケットを処理してもらえずタイムアウトする」という重大なバグの
801        // 原因となっていた(実サイトのメインHTML取得は成功するのに、直後の外部
802        // CSS取得が毎回TLSハンドシェイクに失敗する現象として顕在化)。
803        // Core 2は本来この用途の専用スレッドなので、NTP同期の有無に関わらず
804        // 毎ループ確実にポーリングするよう変更する。
805        crate::kernel::usb::poll_ethernet_data_plane_only();
806
807        // リンクアップ後、壁時計が未同期なら NTP(NICT) で同期を試みる(30秒間隔)。
808        // ntp_sync() は内部で poll_ethernet_data_plane_only() を回しつつ最大数十秒ブロックし得るが、
809        // ここは専用スレッドなので HID ポーリングを妨げない。
810        let net_st = crate::kernel::net::status();
811        if !crate::kernel::timer::is_wall_clock_set() && net_st.link_up && net_st.ip != [0; 4] {
812            let now = crate::kernel::timer::get_system_time_ms();
813            if ntp_last_attempt_ms == 0 || now.wrapping_sub(ntp_last_attempt_ms) > 30_000 {
814                ntp_last_attempt_ms = now;
815                if let Err(e) = crate::kernel::net::ntp_sync() {
816                    crate::warn!("[SYS] NTP sync failed: {}", e);
817                }
818            }
819        } else if net_st.ip == [0; 4] {
820            // IP未設定の間は試行タイマーをリセットし続けることで、IP取得直後に即同期できるようにする
821            ntp_last_attempt_ms = 0;
822        }
823        // ポーリング自体は毎ループ行うが、CPUを独占しないよう短く譲る。
824        // 【2026-07-30 検証したが戻した】ポーリング間隔を 100ms→20ms、
825        // 1 回の回収上限を 8→64 に増やしたところ、実機で **8/8 全滅**した
826        // (それまでは 1〜3 回成功していた)。ポーリングを詰めると
827        // USB レジスタへのアクセスが増え、かえって転送を阻害するとみられる。
828        // 「速くすれば改善する」という思い込みを実測が否定した。
829        // 再挑戦するなら間隔と回収数を**片方ずつ**変えて測ること。
830        //
831        // 【2026-08-05】この間隔が画像取得の律速そのものだと実測で確定した。
832        // 100ms × 1 回 8 フレーム × 1500 バイト = 120KB/s で、
833        // 実効 100KB/s とほぼ一致する。
834        //
835        // 2026-07-30 は間隔と回収数を**同時に**変えて全滅したが、
836        // 今回**回収数だけ** 8→32 にしたところ 13/13 成功のまま
837        // 2.3 倍遅くなった(fetch 3685→8329 tick)。
838        // つまり全滅の原因は回収数ではない。残るのは間隔の方。
839        //
840        // 【2026-08-05】眠らずに譲るだけにした。
841        //
842        // ## 経緯
843        //
844        // 間隔を 100ms→50ms にしても取得は速くならなかった
845        // (fetch 3685→3907tick)。当時は「間隔は律速ではない」と結論したが、
846        // **理由は Core 1 の `poll()` が受信の 87% を担っていたから**で、
847        // このスレッドの間隔を変えても全体はほとんど動かなかっただけだった。
848        //
849        // Ethernet 収穫を Core 1 から外して当スレッドへ一本化したので、
850        // 今度はこの間隔が受信頻度そのものになる。
851        // 1 回の収穫は実測 8ms(`hid_eth_us`/`hid_calls` より)なので、
852        // 10ms 眠ると半分以上を寝て過ごすことになる。
853        // 専用スレッドなので `yield_now` で詰めてみたが、
854        // **3.7MB の画像が 200 秒で完了しなくなり明確に悪化した**ため戻した。
855        // Core 2 には画像取得ワーカーも載るので、譲ると番が回ってこない。
856        // 受信を実際に駆動しているのは Core 1 の `poll()` の方だった。
857        crate::kernel::scheduler::sleep(10);
858    }
859}
860
861fn gui_shell_process() {
862    crate::info!(
863        "GUI Shell process has started on Core {}",
864        crate::kernel::scheduler::core_id()
865    );
866    crate::os_lib::svg::init_cache();
867
868    let Some(screen) = (unsafe { CURRENT_SCREEN.as_mut() }) else {
869        crate::println!("GUI Shell: CURRENT_SCREEN is not initialized. Aborting GUI shell.");
870        return;
871    };
872
873    // USB ホストコントローラの初期化と列挙は、プリエンプション無効の単一スレッド区間で
874    // 走らせるため main()(enable_interrupts の直前)へ移動済み。ここでは何もしない。
875
876    // WiFi (CYW43455 SDIO) の初期化
877    crate::info!("[NET] Initializing WiFi SDIO controller...");
878    if kernel::sdio::init() {
879        crate::info!("[NET] SDIO initialized. Starting WiFi driver...");
880        if kernel::wifi::init() {
881            crate::info!("[NET] WiFi ready.");
882        } else {
883            crate::info!("[NET] WiFi init failed (firmware may be missing from /boot/).");
884        }
885    } else {
886        crate::info!("[NET] SDIO init failed (no WiFi chip detected).");
887    }
888
889    // USB初期化完了後にポーリングスレッドをCore 1に割り当てる
890    kernel::scheduler::spawn_on_core(
891        usb_poll_thread,
892        kernel::scheduler::Priority::High,
893        "usb_poll",
894        1,
895    );
896
897    // NTP 等のブロッキングなネットワーク処理を担当する専用スレッド。
898    // HID ポーリング(usb_poll)と分離することで、NTP の長時間ブロックが
899    // 入力フリーズを引き起こさないようにする。Core 2 に割り当てる。
900    kernel::scheduler::spawn_on_core(
901        net_service_thread,
902        kernel::scheduler::Priority::Normal,
903        "net_service",
904        2,
905    );
906
907    // ベクトルフォントのロードと初期化 (include_bytes! によりカーネルに直結してメモリ展開)
908    // フォールバックチェーン: config の font_names 順でグリフを補う。
909    let conf = kernel::config::get_config();
910    crate::info!("[GPU] Initializing Vector Font Engine: {:?}...", conf.font_names);
911    let chain: alloc::vec::Vec<&str> = conf.font_names.iter().map(|s| s.as_str()).collect();
912    kernel::vector_font::init_global_font_chain(&chain);
913    kernel::vector_font::init_terminal_font();
914    crate::info!("[GPU] Vector Font initialized successfully!");
915
916    // Webブラウザの初期化
917    crate::info!("[SYS] Initializing Web Browser...");
918    apps::browser::init();
919    crate::info!("[SYS] Web Browser initialized successfully!");
920
921    crate::info!("[SYS] Initializing Window Manager...");
922    kernel::window_mgr::init();
923    crate::info!("[SYS] Window Manager initialized successfully!");
924
925    crate::info!("[SYS] Initializing Terminal...");
926
927    // 設定の読み込みは失敗しても起動は続けるが、**握り潰さずに必ず報告する**
928    // (「設定したのに効かない」の原因がログに残らないのを防ぐ)。
929    let loaded_config = crate::kernel::config::init_or_load();
930    if let Err(e) = loaded_config.status {
931        crate::error!(
932            "[SYS] Configuration loaded with errors (defaults were used): {}",
933            e
934        );
935    }
936    let os_config = loaded_config.config;
937    crate::kernel::net::set_dhcp_auto_pending(os_config.auto_dhcp);
938
939    let mut terminal = crate::apps::terminal::Terminal::new();
940
941    let mut mouse_tracker = crate::kernel::mouse_tracker::MouseTracker::new(
942        (screen.width - 16) / 2,
943        (screen.height - 28 - 16) / 2,
944        screen.width,
945        screen.height,
946    );
947    let mut mx = mouse_tracker.x;
948    let mut my = mouse_tracker.y;
949    let mut mouse_obj = kernel::draw::Mouse::new(mx, my);
950
951    let mut shortcut_mgr = crate::kernel::shortcut_manager::ShortcutManager::new();
952    use crate::kernel::shortcut_manager::{InputAction, Modifiers, Shortcut, ShortcutLayer};
953    // OS Shortcuts
954    shortcut_mgr.register(Shortcut::new(
955        0x48,
956        Modifiers::new(false, true, false),
957        ShortcutLayer::OS,
958        InputAction::SwitchTabRelative(-1),
959    )); // Alt+Up
960    shortcut_mgr.register(Shortcut::new(
961        0x50,
962        Modifiers::new(false, true, false),
963        ShortcutLayer::OS,
964        InputAction::SwitchTabRelative(1),
965    )); // Alt+Down
966    shortcut_mgr.register(Shortcut::new(
967        0x14,
968        Modifiers::new(false, true, false),
969        ShortcutLayer::OS,
970        InputAction::NewTab,
971    )); // Alt+T
972    shortcut_mgr.register(Shortcut::new(
973        0x11,
974        Modifiers::new(false, true, false),
975        ShortcutLayer::OS,
976        InputAction::CloseTab,
977    )); // Alt+W
978    shortcut_mgr.register(Shortcut::new(
979        0x18,
980        Modifiers::new(false, true, false),
981        ShortcutLayer::OS,
982        InputAction::OpenFileDialog,
983    )); // Alt+O
984    shortcut_mgr.register(Shortcut::new(
985        0x1F,
986        Modifiers::new(false, true, false),
987        ShortcutLayer::OS,
988        InputAction::SaveFile,
989    )); // Alt+S
990    shortcut_mgr.register(Shortcut::new(
991        0x01,
992        Modifiers::new(false, false, false),
993        ShortcutLayer::OS,
994        InputAction::ToggleFocusSysmonTerminal,
995    )); // Esc
996
997    let mut kbd = crate::kernel::keyboard::Keyboard::new();
998    kbd.set_layout(os_config.keyboard_layout);
999    let mut mouse_dec = crate::kernel::mouse::MouseDecoder::new();
1000    let mut input_state = 0;
1001    let mut ime_state = crate::kernel::ime::ImeState::new();
1002    // Mozc 辞書(7.8万語)は重い。初回 IME 切替時にメインスレッドで読むと使用中にフリーズするため、
1003    // 起動初期化フェーズ(ここ)で先に読み込んでおく。
1004    ime_state.load_dict();
1005
1006    // タブレット用の一時変数
1007    let mut tablet_btn = 0u32;
1008    let mut tablet_abs_x = 0u32;
1009    let mut tablet_abs_y = 0u32;
1010
1011    let mut login_mgr = crate::apps::login::LoginManager::new();
1012    let _first_unlock = true;
1013
1014    // 動的レイアウトの計算
1015    let tab_w = 100;
1016    let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1017    let sysmon_x = screen.width - sysmon_w;
1018    let center_w = sysmon_x - tab_w - 2;
1019    // 画面最下部のファンクションキー・ガイドバー分を除いた表示高さ
1020    let content_h = screen.height.saturating_sub(kernel::fn_bar::BAR_HEIGHT);
1021
1022    // 固定システムモニターペインの初期化(バーと重ならないよう高さを制限)
1023    let mut sysmon_pane =
1024        kernel::window::SystemMonitorPane::new(sysmon_x as i32 + 2, 0, sysmon_w - 2, content_h);
1025
1026    // 実機デバッグ: フレームバッファの実ジオメトリを出力(SMP 起床後の静かな区間なので化けない)。
1027    screen.debug_geometry();
1028    screen.enable_double_buffering();
1029    // VSync ページフリップは 2 ページが確保できた場合のみ有効化する。
1030    let vsync_on = screen.enable_vsync();
1031    if vsync_on {
1032        println!("[FB] VSync page-flip ENABLED (double buffer / 2 pages confirmed)");
1033    } else {
1034        println!("[FB] VSync page-flip DISABLED (single buffer; 2 pages not allocated)");
1035    }
1036    let mut last_render_time = crate::kernel::timer::get_system_time_ms();
1037    let mut last_sysmon_ticks = 0;
1038
1039    let mut power_modal_visible = false;
1040    let mut pwr_last_mouse_btn = false;
1041    let mut first_render = true;
1042    let mut was_unlocked = login_mgr.is_unlocked();
1043
1044    // マイルストーン(CI が expect で待つ): 閾値に関わらず常に出す → warn!
1045    crate::warn!("[SYS] === GUI Shell: All initialization complete. Entering main loop ===");
1046    unsafe {
1047        crate::kernel::dialog::GLOBAL_FILE_DIALOG = Some(crate::kernel::dialog::FileDialog::new());
1048    }
1049
1050    loop {
1051        // kernel::usb::poll() は usb_poll_thread (Core 1) にオフロードされました
1052
1053        let mut processed_any = false;
1054
1055        loop {
1056            let (kbd_data, mouse_data, uart_data) = {
1057                crate::kernel::interrupt::disable_interrupts();
1058                let kd = crate::kernel::interrupt::KBD_FIFO.lock().pop();
1059                let md = crate::kernel::interrupt::MOUSE_FIFO.lock().pop();
1060                let ud = crate::kernel::interrupt::UART_FIFO.lock().pop();
1061                crate::kernel::interrupt::enable_interrupts();
1062                (kd, md, ud)
1063            };
1064
1065            if kbd_data.is_none() && mouse_data.is_none() && uart_data.is_none() {
1066                break;
1067            }
1068            processed_any = true;
1069
1070            // --- シリアルコンソール入力 ---
1071            // UART で届いた ASCII バイトを合成スキャンコードへ展開し、物理キーボードと
1072            // 全く同じ decode() → process_key_event パイプラインに流す。
1073            // これによりログイン・ターミナル・IME 等が一切区別なく動作し、
1074            // QEMU の expect スクリプト等によるシリアル自動操作 (回帰テスト) が可能になる。
1075            if let Some(byte) = uart_data {
1076                let b = byte as u8;
1077                if b < 0x80 {
1078                    let layout = crate::kernel::config::get_config().keyboard_layout;
1079                    if let Some((kc, shift)) =
1080                        crate::kernel::keyboard::ascii_to_scancode(b as char, layout)
1081                    {
1082                        // make/break を最大4つ (Shift make, key make, key break, Shift break)
1083                        let mut seq = [0u8; 4];
1084                        let mut n = 0;
1085                        if shift {
1086                            seq[n] = 0x2A;
1087                            n += 1;
1088                        } // LShift make
1089                        seq[n] = kc;
1090                        n += 1; // key make
1091                        seq[n] = kc | 0x80;
1092                        n += 1; // key break
1093                        if shift {
1094                            seq[n] = 0xAA;
1095                            n += 1;
1096                        } // LShift break
1097                        for &sc in &seq[..n] {
1098                            if let Some(ev) = kbd.decode(sc) {
1099                                process_key_event(
1100                                    ev,
1101                                    1,
1102                                    &mut terminal,
1103                                    &mut kbd,
1104                                    &mut ime_state,
1105                                    &mut login_mgr,
1106                                    &mut sysmon_pane,
1107                                    &mut power_modal_visible,
1108                                    screen,
1109                                    &shortcut_mgr,
1110                                );
1111                            }
1112                        }
1113                    }
1114                }
1115            }
1116
1117            if let Some(data_u32) = mouse_data {
1118                let val = data_u32 as u8;
1119                match input_state {
1120                    0 => {
1121                        if val == 0xFE {
1122                            input_state = 2;
1123                        } else if val == 0xFC {
1124                            input_state = 5;
1125                        }
1126                    }
1127
1128                    2 => {
1129                        let _ = mouse_dec.decode(val);
1130                        input_state = 3;
1131                    }
1132                    3 => {
1133                        let _ = mouse_dec.decode(val);
1134                        input_state = 4;
1135                    }
1136                    4 => {
1137                        if let Some(mev) = mouse_dec.decode(val) {
1138                            mouse_tracker.move_relative(mev.dx, mev.dy);
1139                            mx = mouse_tracker.x;
1140                            my = mouse_tracker.y;
1141
1142                            let left_click = mev.left_btn;
1143                            let just_pressed = left_click && !pwr_last_mouse_btn;
1144                            pwr_last_mouse_btn = left_click;
1145
1146                            if power_modal_visible {
1147                                if just_pressed {
1148                                    let cx = screen.width / 2;
1149                                    let cy = screen.height / 2;
1150                                    if mx >= cx - 150 && mx <= cx + 150 {
1151                                        if my >= cy - 60 && my <= cy - 20 {
1152                                            // Shutdown
1153                                            unsafe {
1154                                                screen.clear(kernel::draw::Color::BLACK);
1155                                                screen.draw_string_vector(cx.saturating_sub(250), cy - 10, "System Halted. You can safely turn off your machine.", kernel::draw::Color::WHITE, 24);
1156                                                screen.flush();
1157                                                loop {
1158                                                    core::arch::asm!("wfe");
1159                                                }
1160                                            }
1161                                        } else if my >= cy && my <= cy + 40 {
1162                                            // Reboot using Watchdog
1163                                            unsafe {
1164                                                let rstc = 0x3F10_001C as *mut u32;
1165                                                let wdog = 0x3F10_0024 as *mut u32;
1166                                                core::ptr::write_volatile(wdog, 0x5A00_0000 | 10);
1167                                                let mut rstc_val = core::ptr::read_volatile(rstc);
1168                                                rstc_val = (rstc_val & 0xFFFF_FFCF) | 0x5A00_0020;
1169                                                core::ptr::write_volatile(rstc, rstc_val);
1170                                                loop {
1171                                                    core::arch::asm!("wfe");
1172                                                }
1173                                            }
1174                                        } else if my >= cy + 60 && my <= cy + 100 {
1175                                            // Cancel
1176                                            power_modal_visible = false;
1177                                            let mut wm = kernel::window_mgr::get_instance();
1178                                            wm.dirty = true;
1179                                        }
1180                                    }
1181                                }
1182                            } else {
1183                                let mut wm_handled = false;
1184                                if login_mgr.is_unlocked() {
1185                                    let mut wm = kernel::window_mgr::get_instance();
1186                                    wm_handled = wm.handle_mouse(
1187                                        mx as i32,
1188                                        my as i32,
1189                                        left_click,
1190                                        mev.right_btn,
1191                                        0,
1192                                        screen,
1193                                    );
1194                                    // ターミナルランチャー: クリックでアプリ起動
1195                                    if wm.terminal_focused && just_pressed {
1196                                        if let Some(app_name) = terminal.handle_click(mx as i32, my as i32) {
1197                                            let cmd = alloc::format!("os.run(\"{}\")", app_name);
1198                                            if let Some(ctx) = &terminal.context {
1199                                                ctx.cmd_queue.push(cmd);
1200                                            }
1201                                            terminal.print_line(alloc::format!("> run {}", app_name));
1202                                            terminal.dirty = true;
1203                                            wm.dirty = true;
1204                                        }
1205                                    }
1206                                }
1207
1208                                if wm_handled {
1209                                    sysmon_pane.handle_mouse(-1, -1, left_click);
1210                                } else {
1211                                    let sysmon_handled =
1212                                        sysmon_pane.handle_mouse(mx as i32, my as i32, left_click);
1213                                    if sysmon_pane.wants_power_menu {
1214                                        power_modal_visible = true;
1215                                        sysmon_pane.wants_power_menu = false;
1216                                        let mut wm = kernel::window_mgr::get_instance();
1217                                        wm.dirty = true;
1218                                    }
1219                                    if !sysmon_handled && !login_mgr.is_unlocked() {
1220                                        login_mgr.handle_mouse_input(mx, my, left_click, 0);
1221                                    }
1222                                }
1223                            }
1224                        }
1225                        input_state = 0;
1226                    }
1227                    5 => {
1228                        tablet_btn = data_u32;
1229                        input_state = 6;
1230                    }
1231                    6 => {
1232                        tablet_abs_x = data_u32;
1233                        input_state = 7;
1234                    }
1235                    7 => {
1236                        tablet_abs_y = data_u32;
1237                        input_state = 8;
1238                    }
1239                    8 => {
1240                        let tablet_wheel = data_u32 as i8 as i32;
1241                        mouse_tracker.move_absolute_tablet(
1242                            tablet_abs_x,
1243                            tablet_abs_y,
1244                            unsafe { TABLET_MAX_X },
1245                            unsafe { TABLET_MAX_Y },
1246                        );
1247                        mx = mouse_tracker.x;
1248                        my = mouse_tracker.y;
1249
1250                        let left_click = (tablet_btn & 0x01) != 0;
1251                        let right_click = (tablet_btn & 0x02) != 0;
1252                        let just_pressed = left_click && !pwr_last_mouse_btn;
1253                        pwr_last_mouse_btn = left_click;
1254
1255                        let mut dialog_handled = false;
1256                        unsafe {
1257                            if let Some(ref mut d) = crate::kernel::dialog::GLOBAL_FILE_DIALOG {
1258                                if d.is_open {
1259                                    d.handle_mouse(
1260                                        mx as i32,
1261                                        my as i32,
1262                                        left_click,
1263                                        screen.width,
1264                                        screen.height,
1265                                    );
1266                                    dialog_handled = true;
1267                                }
1268                            }
1269                        }
1270                        if dialog_handled {
1271                            kernel::window_mgr::get_instance().dirty = true;
1272                        } else if power_modal_visible {
1273                            if just_pressed {
1274                                let cx = screen.width / 2;
1275                                let cy = screen.height / 2;
1276                                if mx >= cx - 150 && mx <= cx + 150 {
1277                                    if my >= cy - 60 && my <= cy - 20 {
1278                                        unsafe {
1279                                            screen.clear(kernel::draw::Color::BLACK);
1280                                            screen.draw_string_vector(cx.saturating_sub(250), cy - 10, "System Halted. You can safely turn off your machine.", kernel::draw::Color::WHITE, 24);
1281                                            screen.flush();
1282                                            loop {
1283                                                core::arch::asm!("wfe");
1284                                            }
1285                                        }
1286                                    } else if my >= cy && my <= cy + 40 {
1287                                        // Reboot using Watchdog
1288                                        unsafe {
1289                                            let rstc = 0x3F10_001C as *mut u32;
1290                                            let wdog = 0x3F10_0024 as *mut u32;
1291                                            core::ptr::write_volatile(wdog, 0x5A00_0000 | 10);
1292                                            let mut rstc_val = core::ptr::read_volatile(rstc);
1293                                            rstc_val = (rstc_val & 0xFFFF_FFCF) | 0x5A00_0020;
1294                                            core::ptr::write_volatile(rstc, rstc_val);
1295                                            loop {
1296                                                core::arch::asm!("wfe");
1297                                            }
1298                                        }
1299                                    } else if my >= cy + 60 && my <= cy + 100 {
1300                                        power_modal_visible = false;
1301                                        let mut wm = kernel::window_mgr::get_instance();
1302                                        wm.dirty = true;
1303                                    }
1304                                }
1305                            }
1306                        } else {
1307                            let mut wm_handled = false;
1308                            if login_mgr.is_unlocked() {
1309                                let mut wm = kernel::window_mgr::get_instance();
1310                                wm_handled = wm.handle_mouse(
1311                                    mx as i32,
1312                                    my as i32,
1313                                    left_click,
1314                                    right_click,
1315                                    tablet_wheel,
1316                                    screen,
1317                                );
1318                                if wm.terminal_focused && tablet_wheel != 0 {
1319                                    terminal.scroll(tablet_wheel * 2);
1320                                }
1321                                // ターミナルランチャー: クリックでアプリ起動
1322                                if wm.terminal_focused && just_pressed {
1323                                    if let Some(app_name) = terminal.handle_click(mx as i32, my as i32) {
1324                                        let cmd = alloc::format!("os.run(\"{}\")", app_name);
1325                                        if let Some(ctx) = &terminal.context {
1326                                            ctx.cmd_queue.push(cmd);
1327                                        }
1328                                        terminal.print_line(alloc::format!("> run {}", app_name));
1329                                        terminal.dirty = true;
1330                                        wm.dirty = true;
1331                                    }
1332                                }
1333                            }
1334
1335                            if wm_handled {
1336                                sysmon_pane.handle_mouse(-1, -1, left_click);
1337                            } else {
1338                                let sysmon_handled =
1339                                    sysmon_pane.handle_mouse(mx as i32, my as i32, left_click);
1340                                if sysmon_pane.wants_power_menu {
1341                                    power_modal_visible = true;
1342                                    sysmon_pane.wants_power_menu = false;
1343                                    let mut wm = kernel::window_mgr::get_instance();
1344                                    wm.dirty = true;
1345                                }
1346                                if !sysmon_handled && !login_mgr.is_unlocked() {
1347                                    login_mgr.handle_mouse_input(mx, my, left_click, tablet_wheel);
1348                                }
1349                            }
1350                        }
1351                        input_state = 0;
1352                    }
1353                    _ => input_state = 0,
1354                }
1355            }
1356
1357            if let Some(kbd_u32) = kbd_data {
1358                let val = kbd_u32 as u8;
1359                // 【2026-09-08 一時診断】かなキーがゲストへ届いているかの確認用。
1360                // QEMU の cocoa がどの qcode で送るかはバージョン依存で、
1361                // 届いていないのか・届いているが処理されていないのかを
1362                // ログでしか切り分けられない。原因特定後に削除すること。
1363                {
1364                    static KEY_TRACE: core::sync::atomic::AtomicU32 =
1365                        core::sync::atomic::AtomicU32::new(0);
1366                    if KEY_TRACE.fetch_add(1, core::sync::atomic::Ordering::Relaxed) < 64 {
1367                        crate::warn!(
1368                            "[KBD][TRACE] raw=0x{:02X} make={} code=0x{:02X}",
1369                            val,
1370                            (val & 0x80) == 0,
1371                            val & 0x7F
1372                        );
1373                    }
1374                }
1375                if let Some(ev) = kbd.decode(val) {
1376                    process_key_event(
1377                        ev,
1378                        1,
1379                        &mut terminal,
1380                        &mut kbd,
1381                        &mut ime_state,
1382                        &mut login_mgr,
1383                        &mut sysmon_pane,
1384                        &mut power_modal_visible,
1385                        screen,
1386                        &shortcut_mgr,
1387                    );
1388
1389                    // 修飾キー(Ctrl/Alt/Shift)の押下・解放でガイドバーを更新するため再描画
1390                    match ev.keycode {
1391                        0x1D | 0x38 | 0x2A | 0x36 => {
1392                            kernel::window_mgr::get_instance().dirty = true;
1393                        }
1394                        _ => {}
1395                    }
1396
1397                    unsafe {
1398                        if ev.pressed {
1399                            if REPEAT_STATE.active_keycode != ev.keycode {
1400                                REPEAT_STATE.active_keycode = ev.keycode;
1401                                let now = kernel::timer::get_system_time_ms();
1402                                REPEAT_STATE.press_start_ms = now;
1403                                REPEAT_STATE.last_repeat_ms = now;
1404                                REPEAT_STATE.stage = 0;
1405                            }
1406                        } else {
1407                            if REPEAT_STATE.active_keycode == ev.keycode {
1408                                REPEAT_STATE.active_keycode = 0;
1409                            }
1410                        }
1411                    }
1412                }
1413            }
1414        }
1415
1416        if !processed_any {
1417            // INPUT_FIFO にデータがない場合、キーリピート判定を行う
1418            let mut key_event_to_process: Option<kernel::keyboard::KeyEvent> = None;
1419            let mut repeat_count: u32 = 1;
1420            unsafe {
1421                if REPEAT_STATE.active_keycode != 0 {
1422                    let now = kernel::timer::get_system_time_ms();
1423                    let elapsed = now.saturating_sub(REPEAT_STATE.press_start_ms);
1424                    let since_last_repeat = now.saturating_sub(REPEAT_STATE.last_repeat_ms);
1425
1426                    // 第二リピートの条件判定
1427                    if elapsed >= REPEAT_PARAMS.second_delay_ms as u64 {
1428                        if since_last_repeat >= REPEAT_PARAMS.second_interval_ms as u64 {
1429                            REPEAT_STATE.stage = 2;
1430                            REPEAT_STATE.last_repeat_ms = now;
1431                            key_event_to_process = Some(kernel::keyboard::KeyEvent {
1432                                keycode: REPEAT_STATE.active_keycode,
1433                                pressed: true,
1434                            });
1435                            repeat_count = REPEAT_PARAMS.second_repeat_count;
1436                        }
1437                    }
1438                    // 第一リピートの条件判定
1439                    else if elapsed >= REPEAT_PARAMS.first_delay_ms as u64
1440                        && since_last_repeat >= REPEAT_PARAMS.first_interval_ms as u64
1441                    {
1442                        REPEAT_STATE.stage = 1;
1443                        REPEAT_STATE.last_repeat_ms = now;
1444                        key_event_to_process = Some(kernel::keyboard::KeyEvent {
1445                            keycode: REPEAT_STATE.active_keycode,
1446                            pressed: true,
1447                        });
1448                        repeat_count = 1;
1449                    }
1450                }
1451            }
1452
1453            if let Some(ev) = key_event_to_process {
1454                process_key_event(
1455                    ev,
1456                    repeat_count,
1457                    &mut terminal,
1458                    &mut kbd,
1459                    &mut ime_state,
1460                    &mut login_mgr,
1461                    &mut sysmon_pane,
1462                    &mut power_modal_visible,
1463                    screen,
1464                    &shortcut_mgr,
1465                );
1466            }
1467
1468            let ticks = crate::kernel::timer::get_ticks();
1469            static mut NIC_DEBUG_DONE: bool = false;
1470            if ticks == 500 && unsafe { !NIC_DEBUG_DONE } {
1471                unsafe {
1472                    NIC_DEBUG_DONE = true;
1473                }
1474                crate::info!("[NET] === HEADLESS 5 SEC NIC DEBUG ===");
1475                crate::info!("[NET] {}", crate::kernel::net::nic_debug_info());
1476                crate::info!("[NET] === END NIC DEBUG ===");
1477            }
1478
1479            crate::kernel::scheduler::yield_now();
1480        }
1481
1482        // ---------------------------------------------------------
1483        // 描画処理 (最大 60 FPS 制約 + ダブルバッファリング)
1484        // ---------------------------------------------------------
1485        let now = crate::kernel::timer::get_system_time_ms();
1486        if now.saturating_sub(last_render_time) >= 16 {
1487            last_render_time = now;
1488
1489            let mouse_moved = mx != mouse_obj.x || my != mouse_obj.y;
1490            
1491            // --- マウスクリック状態のアニメーション更新 ---
1492            let was_clicked = mouse_obj.is_clicked;
1493            mouse_obj.is_clicked = pwr_last_mouse_btn;
1494            if pwr_last_mouse_btn {
1495                if !was_clicked {
1496                    // 押した瞬間にアニメーション開始
1497                    mouse_obj.click_frame = 1;
1498                } else if mouse_obj.click_frame < 10 {
1499                    mouse_obj.click_frame += 1;
1500                }
1501            } else {
1502                mouse_obj.click_frame = 0;
1503            }
1504            let mouse_animating = mouse_obj.click_frame > 0 && mouse_obj.click_frame < 10;
1505
1506            // CSS `cursor: text` の要素上にマウスがあれば矢印の代わりに I-beam を描く
1507            // (前フレームのブラウザ描画結果を参照する1フレーム遅延だが、60FPS では
1508            // 体感できない)。
1509            mouse_obj.is_text_cursor = apps::browser::hovered_cursor_is_text();
1510
1511            let now_ticks = crate::kernel::timer::get_ticks();
1512            let sysmon_needs_update = now_ticks.saturating_sub(last_sysmon_ticks) >= 1000;
1513            let timer_redraw = kernel::window_mgr::get_instance().needs_timer_redraw();
1514            let browser_active_render = apps::browser::needs_render();
1515
1516            // --- ダーティ矩形の収集パイプライン ---
1517            let mut wm = kernel::window_mgr::get_instance();
1518
1519            // マウスカーソルはコンテンツのダーティ矩形とは独立して処理する。
1520            // マウス移動だけで全コンポーネントを再描画する無駄を防ぐ。
1521
1522            // 2. 各種コンポーネントの更新に伴うダーティ登録
1523            let is_unlocked = login_mgr.is_unlocked();
1524            if is_unlocked && !was_unlocked {
1525                wm.add_dirty_rect(kernel::window_mgr::Rect::new(
1526                    0,
1527                    0,
1528                    screen.width,
1529                    screen.height,
1530                ));
1531                was_unlocked = true;
1532            }
1533
1534            if !is_unlocked {
1535                if login_mgr.needs_redraw || first_render {
1536                    wm.add_dirty_rect(kernel::window_mgr::Rect::new(
1537                        0,
1538                        0,
1539                        screen.width,
1540                        screen.height,
1541                    ));
1542                }
1543            } else {
1544                // 非同期 Aura 実行の結果が届いていたら再描画を促す(draw() が result_queue を
1545                // 消費してログ化するため。これが無いと次のキー入力まで結果が表示されない)。
1546                if terminal.has_pending_results() {
1547                    terminal.dirty = true;
1548                }
1549                if terminal.dirty || terminal.input_dirty {
1550                    // ターミナルとタブバーの範囲をダーティ登録
1551                    wm.add_dirty_rect(kernel::window_mgr::Rect::new(
1552                        0,
1553                        0,
1554                        tab_w + center_w,
1555                        content_h,
1556                    ));
1557                }
1558                if sysmon_needs_update {
1559                    // システムモニターの範囲をダーティ登録
1560                    wm.add_dirty_rect(kernel::window_mgr::Rect::new(
1561                        sysmon_x as i32,
1562                        0,
1563                        sysmon_w,
1564                        content_h,
1565                    ));
1566                }
1567                if wm.dirty {
1568                    // アクティブな全ウィンドウの現在の矩形をダーティ登録
1569                    let rects: alloc::vec::Vec<_> = wm
1570                        .windows
1571                        .iter()
1572                        .filter(|win| win.workspace == wm.active_workspace && !win.is_minimized)
1573                        .map(|win| {
1574                            kernel::window_mgr::Rect::new(win.x, win.y, win.width, win.height)
1575                        })
1576                        .collect();
1577                    for r in rects {
1578                        wm.add_dirty_rect(r);
1579                    }
1580                }
1581                // ProxyApp の shared_buffer 更新通知を確認し、更新があれば矩形をダーティ登録する。
1582                // これにより needs_timer_redraw が false のアプリでも描画が反映される。
1583                {
1584                    let rects: alloc::vec::Vec<_> = wm
1585                        .windows
1586                        .iter()
1587                        .filter(|win| {
1588                            win.workspace == wm.active_workspace
1589                                && !win.is_minimized
1590                                && win.app.get_buffer_updated_flag()
1591                                    .map(|flag| {
1592                                        // Ordering::Acquire で読み取り、app_runner の Release と同期
1593                                        flag.swap(false, core::sync::atomic::Ordering::Acquire)
1594                                    })
1595                                    .unwrap_or(false)
1596                        })
1597                        .map(|win| {
1598                            kernel::window_mgr::Rect::new(win.x, win.y, win.width, win.height)
1599                        })
1600                        .collect();
1601                    for r in rects {
1602                        wm.add_dirty_rect(r);
1603                    }
1604                }
1605                if timer_redraw {
1606                    // タイマー再描画要求があるウィンドウの矩形をダーティ登録
1607                    let rects: alloc::vec::Vec<_> = wm
1608                        .windows
1609                        .iter()
1610                        .filter(|win| {
1611                            win.workspace == wm.active_workspace
1612                                && !win.is_minimized
1613                                && win.app.needs_timer_redraw()
1614                        })
1615                        .map(|win| {
1616                            kernel::window_mgr::Rect::new(win.x, win.y, win.width, win.height)
1617                        })
1618                        .collect();
1619                    for r in rects {
1620                        wm.add_dirty_rect(r);
1621                    }
1622                }
1623                if browser_active_render {
1624                    // ブラウザ表示ウィンドウの矩形をダーティ登録
1625                    let rects: alloc::vec::Vec<_> = wm
1626                        .windows
1627                        .iter()
1628                        .filter(|win| {
1629                            win.workspace == wm.active_workspace
1630                                && !win.is_minimized
1631                                && (win.app.name() == "Browser" || win.app.name() == "WebBrowser")
1632                        })
1633                        .map(|win| {
1634                            kernel::window_mgr::Rect::new(win.x, win.y, win.width, win.height)
1635                        })
1636                        .collect();
1637                    for r in rects {
1638                        wm.add_dirty_rect(r);
1639                    }
1640                }
1641                if ime_state.mode == crate::kernel::ime::ImeMode::Japanese && ime_state.composing {
1642                    // IME表示エリアの登録(画面下部)
1643                    let y = screen.height.saturating_sub(44);
1644                    wm.add_dirty_rect(kernel::window_mgr::Rect::new(
1645                        0,
1646                        y as i32 - 250,
1647                        screen.width,
1648                        300u32,
1649                    ));
1650                }
1651                if power_modal_visible {
1652                    let cx = screen.width / 2;
1653                    let cy = screen.height / 2;
1654                    wm.add_dirty_rect(kernel::window_mgr::Rect::new(
1655                        cx as i32 - 180,
1656                        cy as i32 - 110,
1657                        360u32,
1658                        220u32,
1659                    ));
1660                }
1661            }
1662
1663            if first_render {
1664                wm.add_dirty_rect(kernel::window_mgr::Rect::new(
1665                    0,
1666                    0,
1667                    screen.width,
1668                    screen.height,
1669                ));
1670            }
1671            first_render = false;
1672
1673            // 包含バウンディングボックスの算出
1674            //
1675            // 【2026-08-01計測】ブラウザのゲート `needs_render()` は一度も
1676            // true にならないのに全画面描画が 100 回走っている。
1677            // ここで登録されているダーティ矩形が原因なので、
1678            // **何個の矩形がどれだけの面積を要求しているか**を記録する。
1679            // 合体後の矩形の中は `screen.clear` されるため、
1680            // 個別矩形でウィンドウを間引くと窓が消える。まず現状を測る。
1681            {
1682                static LAST: core::sync::atomic::AtomicUsize =
1683                    core::sync::atomic::AtomicUsize::new(0);
1684                let n = wm.dirty_rects.len();
1685                if n > 0 {
1686                    let c = LAST.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1687                    // 前半だけ見ると「HTML 取得中のスピナー」しか映らない。
1688                    // 高価な全画面描画は読み込み完了後に起きるので、
1689                    // 間引いて後半まで満遍なく記録する。
1690                    if c < 4 || c.is_multiple_of(25) {
1691                        let mut s = alloc::string::String::new();
1692                        for r in wm.dirty_rects.iter().take(6) {
1693                            s.push_str(&alloc::format!("({},{} {}x{}) ", r.x, r.y, r.w, r.h));
1694                        }
1695                    }
1696                }
1697            }
1698            let dirty_bb = wm.get_dirty_bounding_box(screen.width, screen.height);
1699
1700            if let Some(rect) = dirty_bb {
1701                // クリップ領域を設定して描画を最小化
1702                screen.set_clip(
1703                    rect.x as u32,
1704                    rect.y as u32,
1705                    (rect.x + rect.w as i32) as u32,
1706                    (rect.y + rect.h as i32) as u32,
1707                );
1708
1709                if !login_mgr.is_unlocked() {
1710                    screen.clear(kernel::draw::Color::BLACK);
1711                    login_mgr.draw(screen);
1712                } else {
1713                    let config = crate::kernel::config::get_config();
1714                    screen.clear(kernel::draw::Color(config.theme.desktop_bg));
1715                    screen.boxfill(
1716                        sysmon_x,
1717                        0,
1718                        sysmon_x + 1,
1719                        content_h - 1,
1720                        kernel::draw::Color(config.theme.active_window_border),
1721                    );
1722                    terminal.draw(screen, tab_w as i32, 0, center_w, content_h);
1723
1724                    // ターミナル用タブの描画(背景レイヤーの左端・バーと重ならない高さ)
1725                    screen.boxfill(0, 0, 100, content_h, kernel::draw::Color(config.theme.tab_area_bg));
1726                    screen.boxfill(99, 0, 100, content_h, kernel::draw::Color(config.theme.tab_area_border)); // 境界線
1727                    screen.draw_string_vector(10, 15, "TABS", kernel::draw::Color(config.theme.tab_inactive_fg), 14);
1728                    screen.boxfill(10, 35, 90, 36, kernel::draw::Color(config.theme.tab_area_border));
1729
1730                    let tab_bg = kernel::draw::Color(config.theme.tab_active_bg); // Active
1731                    let tab_fg = kernel::draw::Color(config.theme.tab_active_fg); // Active
1732                    screen.boxfill(5, 45, 95, 73, tab_bg);
1733                    screen.draw_string_vector(10, 51, "[1]", tab_fg, 12);
1734                    screen.draw_string_vector(35, 51, "Shell", tab_fg, 10);
1735                    sysmon_pane.draw(screen);
1736                    wm.draw_all(screen);
1737
1738                    if ime_state.mode == crate::kernel::ime::ImeMode::Japanese
1739                        && ime_state.composing
1740                    {
1741                        let text = if ime_state.converting {
1742                            alloc::format!(" {} ", ime_state.get_display_text())
1743                        } else {
1744                            alloc::format!(" {}_{} ", ime_state.kana_buf, ime_state.romaji_buf)
1745                        };
1746                        // 文字幅を推定: CJK文字 = 16px, ASCII = 9px (size16ベクトルフォント基準)
1747                        let text_px: u32 = text
1748                            .chars()
1749                            .map(|c| if (c as u32) > 127 { 16u32 } else { 9u32 })
1750                            .sum();
1751                        let w = (text_px + 24).clamp(60, sysmon_x.saturating_sub(20));
1752                        let h = 30u32;
1753                        // テキストボックスを画面右端に合わせて配置 (sysmon の左端より左)
1754                        let x = sysmon_x.saturating_sub(w + 20);
1755                        let y = screen.height.saturating_sub(44);
1756                        screen.boxfill(x, y, x + w, y + h, kernel::draw::Color(config.theme.ime_bg));
1757
1758                        let line_color = kernel::draw::Color(config.theme.ime_border);
1759                        screen.boxfill(x, y, x + w, y + 2, line_color);
1760                        screen.draw_string_vector(
1761                            x + 10,
1762                            y + 7,
1763                            &text,
1764                            kernel::draw::Color(config.theme.ime_text),
1765                            16,
1766                        );
1767
1768                        // 候補ウィンドウ (変換中のみ)
1769                        if ime_state.converting && !ime_state.candidates.is_empty() {
1770                            let c_w = 220u32;
1771                            let c_h = ime_state.candidates.len() as u32 * 25 + 10;
1772                            let c_x_right = x + w;
1773                            let c_x = c_x_right.saturating_sub(c_w);
1774                            let c_y = if y >= c_h + 5 { y - c_h - 5 } else { 0 };
1775                            let max_rows = ((y.saturating_sub(c_y)) / 25).max(1) as usize;
1776                            let visible_cands = ime_state.candidates.len().min(max_rows);
1777
1778                            screen.boxfill(
1779                                c_x,
1780                                c_y,
1781                                c_x + c_w,
1782                                c_y + visible_cands as u32 * 25 + 10,
1783                                kernel::draw::Color(config.theme.ime_bg),
1784                            );
1785                            screen.boxfill(
1786                                c_x,
1787                                c_y,
1788                                c_x + c_w,
1789                                c_y + 2,
1790                                kernel::draw::Color(config.theme.ime_border),
1791                            );
1792
1793                            for (i, cand) in
1794                                ime_state.candidates.iter().enumerate().take(visible_cands)
1795                            {
1796                                let item_y = c_y + 5 + (i as u32 * 25);
1797                                if i == ime_state.selected_idx {
1798                                    screen.boxfill(
1799                                        c_x + 2,
1800                                        item_y,
1801                                        c_x + c_w - 2,
1802                                        item_y + 24,
1803                                        kernel::draw::Color(config.theme.tab_area_border),
1804                                    );
1805                                }
1806                                let disp = alloc::format!("{}. {}", i + 1, cand);
1807                                screen.draw_string_vector(
1808                                    c_x + 12,
1809                                    item_y + 5,
1810                                    &disp,
1811                                    kernel::draw::Color(config.theme.ime_text),
1812                                    14,
1813                                );
1814                            }
1815                        }
1816                    }
1817
1818                    if sysmon_needs_update {
1819                        last_sysmon_ticks = now_ticks;
1820                    }
1821
1822                    if power_modal_visible {
1823                        let cx = screen.width / 2;
1824                        let cy = screen.height / 2;
1825
1826                        // モーダル背景
1827                        let mw = 360;
1828                        let mh = 220;
1829                        let mx0 = cx - mw / 2;
1830                        let my0 = cy - mh / 2;
1831                        screen.boxfill(
1832                            mx0,
1833                            my0,
1834                            mx0 + mw,
1835                            my0 + mh,
1836                            kernel::draw::Color(config.theme.login_dialog_bg),
1837                        );
1838                        screen.boxfill(
1839                            mx0,
1840                            my0,
1841                            mx0 + mw,
1842                            my0 + 3,
1843                            kernel::draw::Color(config.theme.focus_highlight),
1844                        ); // 上部ライン
1845                        screen.draw_string_vector(
1846                            mx0 + 20,
1847                            my0 + 15,
1848                            "Power Options",
1849                            kernel::draw::Color(config.theme.login_text_fg),
1850                            20,
1851                        );
1852
1853                        // Shutdown button
1854                        screen.boxfill(
1855                            cx - 150,
1856                            cy - 60,
1857                            cx + 150,
1858                            cy - 20,
1859                            kernel::draw::Color(config.theme.focus_highlight),
1860                        );
1861                        screen.draw_string_vector(
1862                            cx - 45,
1863                            cy - 52,
1864                            "Shutdown",
1865                            kernel::draw::Color::WHITE,
1866                            16,
1867                        );
1868
1869                        // Reboot button
1870                        screen.boxfill(
1871                            cx - 150,
1872                            cy,
1873                            cx + 150,
1874                            cy + 40,
1875                            kernel::draw::Color(config.theme.button_active_bg),
1876                        );
1877                        screen.draw_string_vector(
1878                            cx - 35,
1879                            cy + 8,
1880                            "Reboot",
1881                            kernel::draw::Color::WHITE,
1882                            16,
1883                        );
1884
1885                        // Cancel button
1886                        screen.boxfill(
1887                            cx - 150,
1888                            cy + 60,
1889                            cx + 150,
1890                            cy + 100,
1891                            kernel::draw::Color(config.theme.button_bg),
1892                        );
1893                        screen.draw_string_vector(
1894                            cx - 35,
1895                            cy + 68,
1896                            "Cancel",
1897                            kernel::draw::Color(config.theme.button_fg),
1898                            16,
1899                        );
1900                    }
1901                }
1902
1903                // 画面最下部のファンクションキー・ガイドバー(全幅・常時表示)
1904                let mods = kernel::fn_bar::ModState {
1905                    ctrl: kbd.ctrl_pressed,
1906                    alt: kbd.alt_pressed,
1907                    shift: kbd.shift_pressed,
1908                };
1909                kernel::fn_bar::draw(screen, mods);
1910
1911                // クリップ領域をクリア
1912                screen.clear_clip();
1913            }
1914
1915            // --- マウスカーソル更新 + VRAM転送 ---
1916            // コンテンツ変更(dirty_bb)とマウスカーソル更新を独立して処理する。
1917            // マウスだけ動いた場合でも全コンポーネント再描画を回避できる。
1918            let has_content_change = dirty_bb.is_some();
1919            let need_vsync_sync = screen.vsync_enabled() && screen.has_pending_vsync_sync();
1920            if has_content_change || mouse_moved || mouse_animating || need_vsync_sync {
1921                if screen.vsync_enabled() {
1922                    // VSync ページフリップ: カーソル合成→ダーティ領域だけ部分フリップ。
1923                    if mouse_moved || mouse_animating {
1924                        mouse_obj.move_to(screen, mx, my);
1925                    }
1926                    let content = dirty_bb.map(|r| {
1927                        (
1928                            r.x.max(0) as u32,
1929                            r.y.max(0) as u32,
1930                            (r.x + r.w as i32).max(0) as u32,
1931                            (r.y + r.h as i32).max(0) as u32,
1932                        )
1933                    });
1934                    screen.flush_dirty_pageflip(content, &mouse_obj);
1935                } else {
1936                    // シングルバッファ: 部分転送で最小限の VRAM 書き込み。
1937
1938                    // 1. マウスが動いた、またはアニメーション中の場合、古い位置のカーソル残像を消す
1939                    //    (バックバッファにはカーソルがないので、flush_rect で復元される)
1940                    if mouse_moved || mouse_animating {
1941                        // アニメーション中はスパークが広がるため、少し広めにクリアする
1942                        let clear_pad = 8;
1943                        screen.flush_rect(
1944                            mouse_obj.x.saturating_sub(clear_pad),
1945                            mouse_obj.y.saturating_sub(clear_pad),
1946                            mouse_obj.x + kernel::draw::Mouse::SIZE + clear_pad * 2,
1947                            mouse_obj.y + kernel::draw::Mouse::SIZE + clear_pad * 2,
1948                        );
1949                    }
1950
1951                    // 2. コンテンツが変更された領域を VRAM に部分転送
1952                    if let Some(rect) = dirty_bb {
1953                        screen.flush_rect(
1954                            rect.x as u32,
1955                            rect.y as u32,
1956                            (rect.x + rect.w as i32) as u32,
1957                            (rect.y + rect.h as i32) as u32,
1958                        );
1959                    }
1960
1961                    // 3. マウス位置を更新してカーソルを直接 VRAM に描画
1962                    if mouse_moved {
1963                        mouse_obj.move_to(screen, mx, my);
1964                    }
1965                    screen.draw_mouse_direct(&mouse_obj);
1966                }
1967            }
1968
1969            // ダーティ矩形のクリア
1970            wm.clear_dirty_rects();
1971        }
1972
1973        // フェアネス: 入力が連続している間(processed_any=true で上の yield を通らない)でも、
1974        // 毎反復で必ず一度スケジューラへ譲渡する。これをしないと連続入力中にメインループが
1975        // ビジースピンしてバックグラウンドスレッド(画像/ページ取得など)を飢えさせ、CPU を
1976        // 食い潰してフリーズ感の原因になる。
1977        crate::kernel::scheduler::yield_now();
1978    }
1979}
1980
1981fn process_key_event(
1982    ev: kernel::keyboard::KeyEvent,
1983    repeat_count: u32,
1984    terminal: &mut crate::apps::terminal::Terminal,
1985    kbd: &mut crate::kernel::keyboard::Keyboard,
1986    ime_state: &mut crate::kernel::ime::ImeState,
1987    login_mgr: &mut crate::apps::login::LoginManager,
1988    sysmon_pane: &mut kernel::window::SystemMonitorPane,
1989    _power_modal_visible: &mut bool,
1990    screen: &kernel::draw::Screen,
1991    shortcut_mgr: &crate::kernel::shortcut_manager::ShortcutManager,
1992) {
1993    for _ in 0..repeat_count {
1994        let mut wm = kernel::window_mgr::get_instance();
1995        let active_ws = wm.active_workspace;
1996
1997        // 0. ESC キーによるプロセスリスト/ターミナルのフォーカス絶対切り替え
1998        if ev.keycode == 0x01 && ev.pressed {
1999            if ime_state.composing {
2000                ime_state.clear();
2001                kernel::window_mgr::get_instance().dirty = true;
2002                continue;
2003            }
2004            if !apps::browser::is_active() {
2005                if wm.active_workspace != 1 {
2006                    wm.active_workspace = 1;
2007                    sysmon_pane.focused = true;
2008                    wm.terminal_focused = false;
2009                } else {
2010                    if sysmon_pane.focused {
2011                        sysmon_pane.focused = false;
2012                        wm.terminal_focused = true;
2013                    } else {
2014                        sysmon_pane.focused = true;
2015                        wm.terminal_focused = false;
2016                    }
2017                }
2018                wm.dirty = true;
2019                crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
2020                continue;
2021            }
2022        }
2023
2024        // 1. Altキー (LAlt=0x38) 自体の押下・離鍵判定による遅延ジャンプ制御
2025        if ev.keycode == 0x38 {
2026            if kbd.ctrl_pressed {
2027                // Ctrlが同時に押されている場合は、絶対に遅延ジャンプモードにしない
2028                wm.alt_hold_mode = false;
2029                wm.alt_input_buffer.clear();
2030            } else {
2031                if ev.pressed {
2032                    wm.alt_hold_mode = true;
2033                    wm.alt_input_buffer.clear();
2034                    wm.dirty = true;
2035                } else {
2036                    wm.alt_hold_mode = false;
2037                    if !wm.alt_input_buffer.is_empty() {
2038                        if let Ok(target_tab_1based) = wm.alt_input_buffer.parse::<usize>() {
2039                            let target_idx = target_tab_1based.saturating_sub(1);
2040                            if let Some(win) = wm.windows.last_mut() {
2041                                if win.workspace == active_ws && !win.is_minimized {
2042                                    win.app.switch_tab(target_idx);
2043                                    wm.dirty = true;
2044                                }
2045                            }
2046                        }
2047                        wm.alt_input_buffer.clear();
2048                    }
2049                    wm.dirty = true;
2050                }
2051            }
2052            if !kbd.ctrl_pressed {
2053                continue; // イベントを消費
2054            }
2055        }
2056
2057        // 2. Altホールド遅延実行モード中のキーフック
2058        if wm.alt_hold_mode && ev.pressed {
2059            if ev.keycode == 0x0E {
2060                // Backspace
2061                wm.alt_input_buffer.pop();
2062                wm.dirty = true;
2063                continue;
2064            } else if ev.keycode == 0x01 {
2065                // Esc
2066                wm.alt_input_buffer.clear();
2067                wm.dirty = true;
2068                continue;
2069            } else if let Some(c) = kbd.get_ascii(ev.keycode) {
2070                if c.is_ascii_digit() {
2071                    if wm.alt_input_buffer.len() < 2 {
2072                        wm.alt_input_buffer.push(c);
2073                        wm.dirty = true;
2074                    }
2075                    continue;
2076                }
2077            }
2078        }
2079
2080        // 3. ショートカット評価エンジンによる処理
2081        if ev.pressed {
2082            let mods = crate::kernel::shortcut_manager::Modifiers::new(
2083                kbd.ctrl_pressed,
2084                kbd.alt_pressed,
2085                kbd.shift_pressed,
2086            );
2087            let action = shortcut_mgr.evaluate(ev.keycode, mods);
2088
2089            match action {
2090                crate::kernel::shortcut_manager::InputAction::SwitchTabRelative(offset) => {
2091                    if let Some(win) = wm.windows.last_mut() {
2092                        if win.workspace == active_ws && !win.is_minimized {
2093                            let total_tabs = win.app.tabs().len();
2094                            if total_tabs > 0 {
2095                                let cur_tab = win.app.active_tab() as isize;
2096                                let next_tab =
2097                                    (cur_tab + offset).rem_euclid(total_tabs as isize) as usize;
2098                                win.app.switch_tab(next_tab);
2099                                wm.dirty = true;
2100                            }
2101                        }
2102                    }
2103                    continue;
2104                }
2105                crate::kernel::shortcut_manager::InputAction::NewTab => {
2106                    if !wm.terminal_focused {
2107                        if let Some(win) = wm.windows.last_mut() {
2108                            if win.workspace == active_ws && !win.is_minimized {
2109                                let new_tab_idx = win.app.tabs().len();
2110                                win.app.add_tab();
2111                                win.spawn_tab_thread(new_tab_idx);
2112                                wm.dirty = true;
2113                                crate::kernel::audio::play_system_se(
2114                                    crate::kernel::audio::SeType::Apply,
2115                                );
2116                            }
2117                        }
2118                    }
2119                    continue;
2120                }
2121                crate::kernel::shortcut_manager::InputAction::CloseTab => {
2122                    if !wm.terminal_focused {
2123                        if let Some(win) = wm.windows.last_mut() {
2124                            if win.workspace == active_ws && !win.is_minimized {
2125                                let active_idx = win.app.active_tab();
2126                                let should_close_window = win.app.close_tab(active_idx);
2127
2128                                if should_close_window {
2129                                    wm.windows.pop();
2130                                    crate::kernel::audio::play_system_se(
2131                                        crate::kernel::audio::SeType::Close,
2132                                    );
2133                                    if wm.windows.is_empty() {
2134                                        wm.terminal_focused = true;
2135                                    }
2136                                } else {
2137                                    crate::kernel::audio::play_system_se(
2138                                        crate::kernel::audio::SeType::Apply,
2139                                    );
2140                                }
2141                                wm.dirty = true;
2142                            }
2143                        }
2144                    }
2145                    continue;
2146                }
2147
2148                crate::kernel::shortcut_manager::InputAction::OpenFileDialog => {
2149                    if !wm.terminal_focused {
2150                        if let Some(win) = wm.windows.last_mut() {
2151                            if win.workspace == active_ws && !win.is_minimized {
2152                                win.app.open_file_dialog();
2153                                wm.dirty = true;
2154                            }
2155                        }
2156                    }
2157                    continue;
2158                }
2159
2160                crate::kernel::shortcut_manager::InputAction::SaveFile => {
2161                    if !wm.terminal_focused {
2162                        if let Some(win) = wm.windows.last_mut() {
2163                            if win.workspace == active_ws && !win.is_minimized {
2164                                win.app.save_file();
2165                                wm.dirty = true;
2166                            }
2167                        }
2168                    }
2169                    continue;
2170                }
2171
2172                crate::kernel::shortcut_manager::InputAction::ToggleFocusSysmonTerminal => {
2173                    if sysmon_pane.focused {
2174                        sysmon_pane.focused = false;
2175                        wm.terminal_focused = true;
2176                    } else {
2177                        sysmon_pane.focused = true;
2178                        wm.terminal_focused = false;
2179                    }
2180                    wm.dirty = true;
2181                    crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
2182                    continue;
2183                }
2184                _ => {}
2185            }
2186        }
2187        // 4. IME トグルと入力処理
2188        if ev.pressed {
2189            // 【2026-09-08】`0x70`(かな)を追加。
2190            //
2191            // QEMU の cocoa 表示は、Mac の かなキーを `lang1`(HID 0x90 →
2192            // PS/2 0x79 変換)と `katakanahiragana`(HID 0x88 → PS/2 0x70)の
2193            // どちらで送るかがバージョンによって違う。QEMU 11.1.1 は両方の
2194            // qcode を持つ。0x70 側に倒れると、こちらのコードが無変更でも
2195            // かなキーが死ぬ(2026-08-28 の QEMU 更新以降の症状)。
2196            // 送出側を選べないので、受け側で両方受け付ける。
2197            // Ctrl+Space: IME の ON/OFF をトグルする。
2198            //
2199            // 【2026-09-08 追加】macOS + QEMU 11.1.1 では**かなキーがゲストへ
2200            // 到達しない**ため、変換/無変換しか無いと日本語入力に入れない。
2201            // cocoa 表示はかなキーのイベントを送らず、VNC 表示は keysym
2202            // `0xFF25`(Hiragana) を送るものの QEMU 側にスキャンコードが無く
2203            // 捨てられる(`ja` キーマップに `Hiragana 0x77` の行はあるが、
2204            // QEMU 内部の keysym 名テーブルが解決できていない)。
2205            // Mac の JIS キーボードには変換/無変換キー自体が無いので、
2206            // 確実に届く組み合わせへ退避させる。
2207            // Windows 環境の変換/無変換は従来どおり残してある。
2208            if ev.keycode == 0x39 && kbd.ctrl_pressed {
2209                let to_japanese = ime_state.mode != crate::kernel::ime::ImeMode::Japanese;
2210                ime_state.toggle_mode(to_japanese);
2211                kernel::window_mgr::get_instance().dirty = true;
2212                continue;
2213            }
2214            // かなキー。PS/2 では 0x70(カタカナひらがな) と 0x77(ひらがな) の
2215            // 二系統があり、どちらで届くかは QEMU のキーマップ次第。
2216            if ev.keycode == 0x79 || ev.keycode == 0x70 || ev.keycode == 0x77 {
2217                // 変換キー / かなキー
2218                ime_state.toggle_mode(true);
2219                kernel::window_mgr::get_instance().dirty = true;
2220                continue;
2221            } else if ev.keycode == 0x7B {
2222                // 無変換キー
2223                ime_state.toggle_mode(false);
2224                kernel::window_mgr::get_instance().dirty = true;
2225                continue;
2226            }
2227
2228            if ime_state.mode == crate::kernel::ime::ImeMode::Japanese {
2229                let mut wm = kernel::window_mgr::get_instance();
2230                if ev.keycode == 0x0E {
2231                    // Backspace
2232                    if ime_state.composing {
2233                        ime_state.backspace();
2234                        wm.dirty = true;
2235                        continue;
2236                    }
2237                } else if ev.keycode == 0x1C {
2238                    // Enter
2239                    if let Some(text) = ime_state.handle_enter() {
2240                        for c in text.chars() {
2241                            wm.handle_key(0, true, Some(c), false, screen);
2242                        }
2243                        wm.dirty = true;
2244                        continue;
2245                    }
2246                } else if ev.keycode == 0x39 {
2247                    // Space
2248                    if ime_state.handle_space() {
2249                        wm.dirty = true;
2250                        continue;
2251                    }
2252                    // 未変換(composing でない)の全角モードでは全角スペースを入力
2253                    wm.handle_key(0, true, Some('\u{3000}'), false, screen);
2254                    wm.dirty = true;
2255                    continue;
2256                } else if ev.keycode == 0x01 && ime_state.composing {
2257                    // Escape: 変換キャンセル
2258                    ime_state.cancel();
2259                    wm.dirty = true;
2260                    continue;
2261                } else if ev.keycode == 0x48 && ime_state.converting {
2262                    // Up Arrow
2263                    if !ime_state.candidates.is_empty() {
2264                        if ime_state.selected_idx > 0 {
2265                            ime_state.selected_idx -= 1;
2266                        } else {
2267                            ime_state.selected_idx = ime_state.candidates.len() - 1;
2268                        }
2269                    }
2270                    wm.dirty = true;
2271                    continue;
2272                } else if ev.keycode == 0x50 && ime_state.converting {
2273                    // Down Arrow
2274                    if !ime_state.candidates.is_empty() {
2275                        ime_state.selected_idx =
2276                            (ime_state.selected_idx + 1) % ime_state.candidates.len();
2277                    }
2278                    wm.dirty = true;
2279                    continue;
2280                } else if ime_state.converting {
2281                    // 数字キー 1〜9 で候補を直接選択して確定
2282                    if let Some(c) = kbd.get_ascii(ev.keycode) {
2283                        if ('1'..='9').contains(&c) {
2284                            let n = (c as u8 - b'0') as usize;
2285                            if let Some(text) = ime_state.select_candidate_by_number(n) {
2286                                for ch in text.chars() {
2287                                    wm.handle_key(0, true, Some(ch), false, screen);
2288                                }
2289                                wm.dirty = true;
2290                                continue;
2291                            }
2292                        }
2293                    }
2294                    // 変換中に英字が来たら確定してから入力
2295                    if let Some(c) = kbd.get_ascii(ev.keycode) {
2296                        if c.is_ascii_alphabetic() {
2297                            if let Some(text) = ime_state.handle_enter() {
2298                                for ch in text.chars() {
2299                                    wm.handle_key(0, true, Some(ch), false, screen);
2300                                }
2301                            }
2302                            ime_state.input_char(c);
2303                            wm.dirty = true;
2304                            continue;
2305                        }
2306                    }
2307                } else if let Some(c) = kbd.get_ascii(ev.keycode) {
2308                    if !kbd.ctrl_pressed
2309                        && !kbd.alt_pressed
2310                        && (c.is_ascii_alphabetic() || c.is_ascii_punctuation())
2311                    {
2312                        ime_state.input_char(c);
2313                        wm.dirty = true;
2314                        continue;
2315                    }
2316                }
2317            }
2318        }
2319
2320        if ev.pressed {
2321            if !login_mgr.is_unlocked() {
2322                if let Some(c) = kbd.get_ascii(ev.keycode) {
2323                    login_mgr.handle_key_input(c);
2324                }
2325            } else if apps::browser::is_active() {
2326                let b = apps::browser::get_instance();
2327                if ev.keycode == 0x01 {
2328                    // Escape key
2329
2330                    b.engine.active = false;
2331                    b.engine.dirty = true;
2332                    terminal.dirty = true;
2333
2334                    break;
2335                } else if ev.keycode == 0x48 {
2336                    // Up Arrow
2337
2338                    b.engine.scroll(-24);
2339                } else if ev.keycode == 0x50 {
2340                    // Down Arrow
2341
2342                    b.engine.scroll(24);
2343                } else if ev.keycode == 0x49 {
2344                    // Page Up: 1ページ分上スクロール
2345                    b.engine.scroll(-240);
2346                } else if ev.keycode == 0x51 {
2347                    // Page Down: 1ページ分下スクロール
2348                    b.engine.scroll(240);
2349                }
2350            } else {
2351                let mut key_handled = false;
2352                if sysmon_pane.focused {
2353                    key_handled = sysmon_pane.handle_key(ev.keycode, kbd.get_ascii(ev.keycode));
2354                }
2355
2356                if !key_handled {
2357                    let mut wm = kernel::window_mgr::get_instance();
2358                    if wm.handle_key(
2359                        ev.keycode,
2360                        ev.pressed,
2361                        kbd.get_ascii(ev.keycode),
2362                        kbd.alt_pressed,
2363                        screen,
2364                    ) {
2365                        continue;
2366                    }
2367
2368                    // 通常のキー入力 (常にターミナルへ送る)
2369                    if let Some(c) = kbd.get_ascii(ev.keycode) {
2370                        if c == '\n' {
2371                            if kbd.shift_pressed {
2372                                terminal.input_char('\n');
2373                            } else {
2374                                if let Some(cmd_str) = terminal.submit() {
2375                                    crate::info!("[SYS] === Executing command: {} ===", cmd_str);
2376                                    crate::apps::terminal::shell::execute_shell_command(
2377                                        &cmd_str, terminal, kbd,
2378                                    );
2379                                    crate::info!("[SYS] === Command execution finished. ===");
2380                                }
2381                                break; // 改行確定時はリピート連打を終了
2382                            }
2383                        } else if kbd.ctrl_pressed && ev.keycode != 0x0E {
2384                            // Ctrl押下時は対応するASCII制御文字に変換して送る
2385                            let ctrl_char = match c.to_ascii_lowercase() {
2386                                'a' => Some('\x01'),
2387                                'b' => Some('\x02'),
2388                                'c' => Some('\x03'),
2389                                'd' => Some('\x04'),
2390                                'e' => Some('\x05'),
2391                                'f' => Some('\x06'),
2392                                'g' => Some('\x07'),
2393                                'h' => Some('\x08'),
2394                                'i' => Some('\t'),
2395                                'j' => Some('\n'),
2396                                'k' => Some('\x0B'),
2397                                'l' => Some('\x0C'),
2398                                'm' => Some('\r'),
2399                                'n' => Some('\x0E'),
2400                                'o' => Some('\x0F'),
2401                                'p' => Some('\x10'),
2402                                'q' => Some('\x11'),
2403                                'r' => Some('\x12'),
2404                                's' => Some('\x13'),
2405                                't' => Some('\x14'),
2406                                'u' => Some('\x15'),
2407                                'v' => Some('\x16'),
2408                                'w' => Some('\x17'),
2409                                'x' => Some('\x18'),
2410                                'y' => Some('\x19'),
2411                                'z' => Some('\x1A'),
2412                                _ => None,
2413                            };
2414                            if let Some(cc) = ctrl_char {
2415                                terminal.input_char(cc);
2416                            }
2417                        } else {
2418                            terminal.input_char(c);
2419                        }
2420                    } else {
2421                        // 特殊キーの処理 (Up/Down/Left/Right/Delete/Home/End)
2422                        if ev.keycode == 0x48 {
2423                            // Up Arrow
2424                            terminal.history_up();
2425                        } else if ev.keycode == 0x50 {
2426                            // Down Arrow
2427                            terminal.history_down();
2428                        } else if ev.keycode == 0x4B {
2429                            // Left Arrow
2430                            terminal.move_cursor_left();
2431                        } else if ev.keycode == 0x4D {
2432                            // Right Arrow
2433                            terminal.move_cursor_right();
2434                        } else if ev.keycode == 0x53 {
2435                            // Delete
2436                            terminal.delete_char();
2437                        } else if ev.keycode == 0x47 {
2438                            // Home
2439                            terminal.move_cursor_to_start();
2440                        } else if ev.keycode == 0x4F {
2441                            // End
2442                            terminal.move_cursor_to_end();
2443                        } else if ev.keycode == 0x49 {
2444                            // Page Up: 画面を上方向へスクロール
2445                            terminal.scroll(-10);
2446                        } else if ev.keycode == 0x51 {
2447                            // Page Down: 画面を下方向へスクロール
2448                            terminal.scroll(10);
2449                        } else if ev.keycode == 0x0E {
2450                            // Backspace (Ctrl+H)
2451                            terminal.input_char('\x08');
2452                        } else if ev.keycode == crate::kernel::keyboard::KC_KILL_LINE {
2453                            // Ctrl+K
2454                            terminal.kill_line();
2455                        }
2456                    }
2457                }
2458            }
2459        } else {
2460            // 離鍵(keyup): ブラウザがアクティブでアドレスバー非フォーカス時のみ、
2461            // フォーカス要素へ keyup を発火する(JS の keyup リスナ用)。
2462            if login_mgr.is_unlocked() && apps::browser::is_active() {
2463                let b = apps::browser::get_instance();
2464                if !b.address_bar_focused {
2465                    b.engine.on_key_up(ev.keycode, kbd.get_ascii(ev.keycode));
2466                }
2467            }
2468        }
2469    }
2470}
2471
2472#[cfg(not(test))]
2473#[panic_handler]
2474fn panic(_info: &PanicInfo) -> ! {
2475    // パニック回復の最重要前処理: このコアが保持中のクリティカルロックを強制解放する。
2476    // 特にヒープロックは直後の println!(=メモリ確保) が取りに行くため、保持したままだと
2477    // ハンドラ自身がデッドロックし OS 全体が確保不能になる。NET_LOCK/USB_LOCK は保持したまま
2478    // プロセスが死ぬとネットワーク/USB が以後デッドロックする。
2479    let cid = kernel::scheduler::core_id() as i16;
2480    kernel::allocator::force_release_lock(cid);
2481    kernel::net::force_release_locks(cid);
2482    kernel::usb::force_release_locks(cid);
2483    kernel::fs::force_release_locks(cid);
2484
2485    let is_critical = kernel::scheduler::is_current_process_critical();
2486    let name = kernel::scheduler::get_current_process_name().unwrap_or("unknown");
2487    let pid = kernel::scheduler::get_current_process_id().unwrap_or(0);
2488
2489    if !is_critical {
2490        // 【2026-08-25 バグ修正】死ぬ前に取得枠を返す。
2491        //
2492        // ここから `scheduler::exit()` で即座に殺すため、
2493        // ワーカーの `fetch_limit::release()` は**実行されない**。
2494        // 枠は全体で 8 本しかなく、`os_lib/webp` は
2495        // 「panic 禁止ポリシーの対象外」と明記されたデコーダなので、
2496        // 壊れた画像が 8 枚あれば以後フォントも画像も
2497        // 二度と読み込まれなくなる(しかも黙って止まる)。
2498        //
2499        // この関数はロックを取らない(原子変数だけを触る)。
2500        // 取ると、そのロックを握ったまま死んだ場合に回収自体が固まる。
2501        os_lib::web_engine::fetch_limit::on_process_died(pid);
2502        println!(
2503            "\n=== PROCESS PANIC: Process '{}' (PID: {}) panicked ===",
2504            name, pid
2505        );
2506        println!("{}", _info);
2507        println!("=====================================================");
2508        println!(
2509            "Recovering from process panic: Terminating PID {} and yielding to other processes...",
2510            pid
2511        );
2512        kernel::scheduler::exit();
2513    } else {
2514        println!("\n==================================================");
2515        println!("!!! KERNEL PANIC: CRITICAL PROCESS / KERNEL PANIC !!!");
2516        println!("==================================================");
2517        println!("Critical Process '{}' (PID: {}) failed.", name, pid);
2518        println!("{}", _info);
2519        loop {
2520            unsafe {
2521                core::arch::asm!("wfe");
2522            }
2523        }
2524    }
2525}
2526
2527fn mmu_test_process_a() {
2528    let pt_addr = kernel::scheduler::get_current_page_table().unwrap_or(0);
2529    if pt_addr != 0 {
2530        let phys_mem = alloc::vec![0u8; 16384];
2531        let phys_addr = phys_mem.as_ptr() as u64;
2532        kernel::mmu::map_page(pt_addr, 0x4000_0000, phys_addr);
2533        kernel::scheduler::yield_now();
2534
2535        unsafe {
2536            let ptr = 0x4000_0000 as *mut u64;
2537            ptr.write_volatile(0x1111_1111_1111_1111);
2538        }
2539
2540        println!("Process A wrote 0x1111... to 0x4000_0000. Sleeping...");
2541        kernel::scheduler::sleep(100);
2542
2543        let val = unsafe { (0x4000_0000 as *mut u64).read_volatile() };
2544        println!("Process A read from 0x4000_0000: {:#X}", val);
2545    }
2546    kernel::scheduler::exit();
2547}
2548
2549fn mmu_test_process_b() {
2550    let pt_addr = kernel::scheduler::get_current_page_table().unwrap_or(0);
2551    if pt_addr != 0 {
2552        let phys_mem = alloc::vec![0u8; 16384];
2553        let phys_addr = phys_mem.as_ptr() as u64;
2554        kernel::mmu::map_page(pt_addr, 0x4000_0000, phys_addr);
2555        kernel::scheduler::yield_now();
2556
2557        unsafe {
2558            let ptr = 0x4000_0000 as *mut u64;
2559            ptr.write_volatile(0x2222_2222_2222_2222);
2560        }
2561
2562        println!("Process B wrote 0x2222... to 0x4000_0000. Sleeping...");
2563        kernel::scheduler::sleep(50);
2564
2565        let val = unsafe { (0x4000_0000 as *mut u64).read_volatile() };
2566        println!("Process B read from 0x4000_0000: {:#X}", val);
2567    }
2568    kernel::scheduler::exit();
2569}
2570
2571// --------------------------------------------------------------------
2572// EL0 ユーザープロセス テスト用システムコールラッパーと関数
2573// --------------------------------------------------------------------
2574#[no_mangle]
2575#[link_section = ".user_text"]
2576fn syscall_print(msg: &str) {
2577    let ptr = msg.as_ptr() as usize;
2578    let len = msg.len();
2579    unsafe {
2580        core::arch::asm!(
2581            "svc #0",
2582            in("x8") 2, // SYS_PRINT
2583            in("x0") ptr,
2584            in("x1") len,
2585            lateout("x0") _, // x0 is modified as return value
2586        );
2587    }
2588}
2589
2590#[no_mangle]
2591#[link_section = ".user_text"]
2592fn syscall_yield() {
2593    unsafe {
2594        core::arch::asm!(
2595            "svc #0",
2596            in("x8") 0, // SYS_YIELD
2597            lateout("x0") _,
2598        );
2599    }
2600}
2601
2602#[no_mangle]
2603#[link_section = ".user_text"]
2604fn syscall_exit(code: usize) -> ! {
2605    unsafe {
2606        core::arch::asm!(
2607            "svc #0",
2608            in("x8") 1, // SYS_EXIT
2609            in("x0") code,
2610            options(noreturn)
2611        );
2612    }
2613}
2614
2615#[no_mangle]
2616#[link_section = ".user_text"]
2617fn user_test_normal() {
2618    syscall_print("Hello from EL0 user space!\n");
2619    syscall_yield();
2620    syscall_print("Back to EL0 user space after yield!\n");
2621    syscall_exit(42);
2622}
2623
2624// --- EL0 GUI ウィンドウ ABI スタブ (Phase 4) ---
2625
2626#[no_mangle]
2627#[link_section = ".user_text"]
2628fn syscall3(nr: usize, x0: usize, x1: usize) -> usize {
2629    let ret: usize;
2630    unsafe {
2631        core::arch::asm!(
2632            "svc #0",
2633            in("x8") nr,
2634            inout("x0") x0 => ret,
2635            in("x1") x1,
2636        );
2637    }
2638    ret
2639}
2640
2641/// EL0 パイロット GUI アプリ: 全描画・入力処理を EL0 で行うピクセルデモ。
2642/// このプロセスが暴走・フォルトしても落ちるのは本プロセスのみで、OS は継続する。
2643#[no_mangle]
2644#[link_section = ".user_text"]
2645pub fn user_pixel_demo() {
2646    const W: usize = 160;
2647    const H: usize = 120;
2648
2649    // ウィンドウ生成(リクエスト構造体はユーザスタック上)
2650    let title = "EL0 Pixel Demo";
2651    let req: [usize; 4] = [title.as_ptr() as usize, title.len(), W, H];
2652    let handle = syscall3(5 /*SYS_WIN_CREATE*/, req.as_ptr() as usize, 0);
2653    if handle == 0 {
2654        syscall_print("EL0 demo: window create failed\n");
2655        syscall_exit(1);
2656    }
2657
2658    // ピクセルバッファ(ユーザスタック上・約75KB)
2659    let mut pixels = [0u32; W * H];
2660    let mut tick: u32 = 0;
2661    let mut color_shift: u32 = 0;
2662
2663    loop {
2664        // 入力イベントを処理
2665        let mut ev: u64 = 0;
2666        while syscall3(
2667            7, /*SYS_WIN_POLL*/
2668            handle,
2669            &mut ev as *mut u64 as usize,
2670        ) == 1
2671        {
2672            let kind = ev >> 56;
2673            if kind == 3 {
2674                // ウィンドウが閉じられた → レジストリを掃除してプロセス終了
2675                syscall3(8 /*SYS_WIN_CLOSE*/, handle, 0);
2676                syscall_print("EL0 demo: window closed, exiting\n");
2677                syscall_exit(0);
2678            }
2679            if kind == 1 && (ev >> 8) & 1 == 1 {
2680                // キー押下で配色を切り替え
2681                color_shift = color_shift.wrapping_add(64);
2682            }
2683        }
2684
2685        // グラデーション + 動くバーを描画(全て EL0 の計算)
2686        for y in 0..H {
2687            for x in 0..W {
2688                let r = ((x * 255 / W) as u32 + color_shift) & 0xFF;
2689                let g = ((y * 255 / H) as u32) & 0xFF;
2690                let b = (tick * 2) & 0xFF;
2691                pixels[y * W + x] = 0xFF00_0000 | (r << 16) | (g << 8) | b;
2692            }
2693        }
2694        let bar_x = (tick as usize) % W;
2695        for y in 0..H {
2696            pixels[y * W + bar_x] = 0xFFFFFFFF;
2697        }
2698
2699        // カーネルへ提出
2700        syscall3(6 /*SYS_WIN_BLIT*/, handle, pixels.as_ptr() as usize);
2701
2702        tick = tick.wrapping_add(1);
2703        // フレームレート調整(数回 yield して CPU を譲る)
2704        for _ in 0..3 {
2705            syscall_yield();
2706        }
2707    }
2708}
2709
2710#[no_mangle]
2711#[link_section = ".user_text"]
2712fn user_test_abort() {
2713    syscall_print("Starting abort test process in EL0...\n");
2714
2715    // MMIO領域(0x3F200008: GPIOレジスタなど)への書き込みを試みて Data Abort を発生させる
2716    // AP設定により EL0 からのアクセスは禁止されているため、Data Abort が発生して
2717    // rust_data_abort_handler により強制終了されるはず。
2718    let mmio_ptr = 0x3F20_0008 as *mut u32;
2719    unsafe {
2720        core::ptr::write_volatile(mmio_ptr, 0);
2721    }
2722
2723    // ここには到達しないはず
2724    syscall_print("ERROR: Accessed MMIO without abort!\n");
2725    syscall_exit(99);
2726}