1#![allow(dead_code)]
9
10extern crate alloc;
11use alloc::boxed::Box;
12use alloc::string::String;
13use alloc::vec::Vec;
14
15use crate::kernel::draw::{Color, Screen};
16
17pub trait App {
19 fn name(&self) -> &str;
20
21 fn is_proxy(&self) -> bool {
23 false
24 }
25
26 fn get_shared_buffer(&self) -> Option<alloc::sync::Arc<spin::Mutex<alloc::vec::Vec<u32>>>> {
28 None
29 }
30
31 fn instance_info(&self) -> alloc::string::String {
33 alloc::string::String::new()
34 }
35
36 fn draw(&mut self, screen: &Screen, win_x: u32, win_y: u32, win_w: u32, win_h: u32);
38
39 fn on_mouse(&mut self, local_x: i32, local_y: i32, btn_left: bool, btn_right: bool, wheel: i32);
41
42 fn on_key(&mut self, keycode: u8, pressed: bool, ascii: Option<char>);
44
45 fn tabs(&self) -> alloc::vec::Vec<alloc::string::String> {
51 alloc::vec![alloc::string::String::from(self.name())]
52 }
53 fn active_tab(&self) -> usize {
54 0
55 }
56 fn switch_tab(&mut self, _idx: usize) {}
57 fn add_tab(&mut self) {}
58 fn close_tab(&mut self, _idx: usize) -> bool {
60 true
61 }
62
63 fn open_file_dialog(&mut self) {}
65 fn save_file(&mut self) {}
67
68 fn tab_thread_fn(&self) -> Option<fn()> {
72 None
73 }
74
75 fn get_dom_value(&self, _id: &str) -> Option<alloc::string::String> {
77 None
78 }
79 fn set_dom_value(&mut self, _id: &str, _val: &str) {}
80
81 fn needs_timer_redraw(&self) -> bool {
83 false
84 }
85
86 fn get_buffer_updated_flag(&self) -> Option<alloc::sync::Arc<core::sync::atomic::AtomicBool>> {
90 None
91 }
92
93 fn get_dom_content(&self, _id: &str) -> Option<alloc::string::String> {
95 None
96 }
97 fn set_dom_content(&mut self, _id: &str, _val: &str) {}
98
99 fn on_resize(&mut self, _width: u32, _height: u32) {}
101}
102
103use alloc::sync::Arc;
104use spin::Mutex;
105use core::sync::atomic::AtomicBool;
106
107#[derive(Clone, Debug)]
108pub enum AppEvent {
109 Mouse {
110 local_x: i32,
111 local_y: i32,
112 btn_left: bool,
113 btn_right: bool,
114 wheel: i32,
115 },
116 Key {
117 keycode: u8,
118 pressed: bool,
119 ascii: Option<char>,
120 },
121 SwitchTab(usize),
122 AddTab,
123 CloseTab(usize),
124 Resize {
125 width: u32,
126 height: u32,
127 },
128}
129
130pub struct ProxyApp {
131 pub name: String,
132 pub shared_buffer: Arc<Mutex<Vec<u32>>>,
133 pub event_queue: Arc<Mutex<Vec<AppEvent>>>,
134 pub is_alive: Arc<AtomicBool>,
135 pub active_tab: Arc<Mutex<usize>>,
136 pub tabs: Arc<Mutex<Vec<String>>>,
137 pub needs_timer_redraw: Arc<AtomicBool>,
138 pub buffer_updated: Arc<AtomicBool>,
141}
142
143impl ProxyApp {
144 pub fn new(
145 name: &str,
146 shared_buffer: Arc<Mutex<Vec<u32>>>,
147 event_queue: Arc<Mutex<Vec<AppEvent>>>,
148 is_alive: Arc<AtomicBool>,
149 active_tab: Arc<Mutex<usize>>,
150 tabs: Arc<Mutex<Vec<String>>>,
151 needs_timer_redraw: Arc<AtomicBool>,
152 buffer_updated: Arc<AtomicBool>,
153 ) -> Self {
154 Self {
155 name: String::from(name),
156 shared_buffer,
157 event_queue,
158 is_alive,
159 active_tab,
160 tabs,
161 needs_timer_redraw,
162 buffer_updated,
163 }
164 }
165}
166
167impl App for ProxyApp {
168 fn name(&self) -> &str {
169 &self.name
170 }
171
172 fn is_proxy(&self) -> bool {
173 true
174 }
175
176 fn get_shared_buffer(&self) -> Option<alloc::sync::Arc<spin::Mutex<alloc::vec::Vec<u32>>>> {
177 Some(self.shared_buffer.clone())
178 }
179
180 fn draw(&mut self, _screen: &Screen, _win_x: u32, _win_y: u32, _win_w: u32, _win_h: u32) {
181 }
184
185 fn on_mouse(&mut self, local_x: i32, local_y: i32, btn_left: bool, btn_right: bool, wheel: i32) {
186 let mut q = self.event_queue.lock();
187 q.push(AppEvent::Mouse {
188 local_x,
189 local_y,
190 btn_left,
191 btn_right,
192 wheel,
193 });
194 }
195
196 fn on_key(&mut self, keycode: u8, pressed: bool, ascii: Option<char>) {
197 let mut q = self.event_queue.lock();
198 q.push(AppEvent::Key {
199 keycode,
200 pressed,
201 ascii,
202 });
203 }
204
205 fn tabs(&self) -> alloc::vec::Vec<alloc::string::String> {
206 let t = self.tabs.lock();
207 t.clone()
208 }
209
210 fn active_tab(&self) -> usize {
211 let idx = self.active_tab.lock();
212 *idx
213 }
214
215 fn switch_tab(&mut self, idx: usize) {
216 let mut a = self.active_tab.lock();
217 *a = idx;
218 let mut q = self.event_queue.lock();
219 q.push(AppEvent::SwitchTab(idx));
220 }
221
222 fn add_tab(&mut self) {
223 let mut q = self.event_queue.lock();
224 q.push(AppEvent::AddTab);
225 }
226
227 fn close_tab(&mut self, idx: usize) -> bool {
228 let mut q = self.event_queue.lock();
229 q.push(AppEvent::CloseTab(idx));
230 let t = self.tabs.lock();
231 t.len() <= 1
232 }
233
234 fn needs_timer_redraw(&self) -> bool {
235 self.needs_timer_redraw.load(Ordering::Relaxed)
236 }
237
238 fn get_buffer_updated_flag(&self) -> Option<alloc::sync::Arc<core::sync::atomic::AtomicBool>> {
239 Some(self.buffer_updated.clone())
240 }
241
242 fn on_resize(&mut self, width: u32, height: u32) {
243 let mut q = self.event_queue.lock();
244 q.retain(|e| !matches!(e, AppEvent::Resize { .. }));
245 q.push(AppEvent::Resize { width, height });
246 }
247}
248
249impl Drop for ProxyApp {
250 fn drop(&mut self) {
251 self.is_alive.store(false, Ordering::Relaxed);
252 }
253}
254
255pub struct AppRunnerParams {
256 pub app: Box<dyn App>,
257 pub shared_buffer: Arc<Mutex<Vec<u32>>>,
258 pub event_queue: Arc<Mutex<Vec<AppEvent>>>,
259 pub is_alive: Arc<AtomicBool>,
260 pub active_tab: Arc<Mutex<usize>>,
261 pub tabs: Arc<Mutex<Vec<String>>>,
262 pub needs_timer_redraw: Arc<AtomicBool>,
263 pub buffer_updated: Arc<AtomicBool>,
265 pub width: u32,
266 pub height: u32,
267}
268
269#[derive(Clone)]
273pub struct DrawTarget {
274 pub buffer: Arc<Mutex<Vec<u32>>>,
275 pub width: u32,
276 pub height: u32,
277 pub updated: Arc<AtomicBool>,
278}
279
280static DRAW_TARGETS: Mutex<alloc::collections::BTreeMap<usize, DrawTarget>> =
282 Mutex::new(alloc::collections::BTreeMap::new());
283
284pub fn current_draw_target() -> Option<DrawTarget> {
286 let pid = crate::kernel::scheduler::get_current_process_id()?;
287 DRAW_TARGETS.lock().get(&pid).cloned()
288}
289
290fn register_draw_target(params: &AppRunnerParams, w: u32, h: u32) {
291 if let Some(pid) = crate::kernel::scheduler::get_current_process_id() {
292 DRAW_TARGETS.lock().insert(
293 pid,
294 DrawTarget { buffer: params.shared_buffer.clone(), width: w, height: h, updated: params.buffer_updated.clone() },
295 );
296 }
297}
298
299pub fn app_runner_entry(arg: usize) {
300 let params_ptr = arg as *mut AppRunnerParams;
301 let mut params = unsafe { Box::from_raw(params_ptr) };
302
303 let mut w = params.width;
304 let mut h = params.height;
305
306 let mut local_buf = alloc::vec![0u32; (w * h) as usize];
308 register_draw_target(¶ms, w, h);
309
310 let mut last_draw_time = 0;
311
312 while params.is_alive.load(Ordering::Relaxed) {
313 let current_time = crate::kernel::timer::get_ticks();
314
315 let app_needs_timer = params.app.needs_timer_redraw();
317 params.needs_timer_redraw.store(app_needs_timer, Ordering::Relaxed);
318
319 let events = {
321 let mut q = params.event_queue.lock();
322 let evts = q.clone();
323 q.clear();
324 evts
325 };
326
327 let mut need_redraw = false;
328
329 for evt in events {
330 match evt {
331 AppEvent::Mouse { local_x, local_y, btn_left, btn_right, wheel } => {
332 params.app.on_mouse(local_x, local_y, btn_left, btn_right, wheel);
333 if btn_left || btn_right || wheel != 0 {
334 need_redraw = true;
335 }
336 }
337 AppEvent::Key { keycode, pressed, ascii } => {
338 params.app.on_key(keycode, pressed, ascii);
339 need_redraw = true;
340 }
341 AppEvent::SwitchTab(idx) => {
342 params.app.switch_tab(idx);
343 need_redraw = true;
344 }
345 AppEvent::AddTab => {
346 params.app.add_tab();
347 need_redraw = true;
348 }
349 AppEvent::CloseTab(idx) => {
350 let _ = params.app.close_tab(idx);
351 need_redraw = true;
352 }
353 AppEvent::Resize { width, height } => {
354 w = width;
355 h = height;
356 local_buf = alloc::vec![0u32; (w * h) as usize];
357 {
358 let mut shared = params.shared_buffer.lock();
359 *shared = alloc::vec![0u32; (w * h) as usize];
360 }
361 params.app.on_resize(w, h);
362 register_draw_target(¶ms, w, h);
363 need_redraw = true;
364 }
365 }
366 }
367
368 {
370 let mut t = params.tabs.lock();
371 *t = params.app.tabs();
372 }
373 {
374 let mut a = params.active_tab.lock();
375 *a = params.app.active_tab();
376 }
377
378 if params.app.needs_timer_redraw() {
379 need_redraw = true;
380 }
381
382 let time_since_last_draw = current_time.saturating_sub(last_draw_time);
383
384 if need_redraw || time_since_last_draw >= 33 {
386 {
388 let screen = Screen::new(local_buf.as_mut_ptr(), w, h, w * 4);
389 let tab_list = params.app.tabs();
390 let has_tabs = !tab_list.is_empty();
391 let tab_w = if has_tabs { 100 } else { 0 };
392 let app_x = tab_w;
393 let app_y = 31u32;
394 let app_w = w.saturating_sub(tab_w);
395 let app_h = h.saturating_sub(31);
396
397 screen.set_clip(app_x, app_y, w, h);
398 params.app.draw(&screen, app_x, app_y, app_w, app_h);
399 screen.clear_clip();
400 }
401
402 {
404 let mut buf_guard = params.shared_buffer.lock();
405 if buf_guard.len() == local_buf.len() {
406 buf_guard.copy_from_slice(&local_buf);
407 params.buffer_updated.store(true, Ordering::Release);
410 } else {
411 static SKIP_DIAG: core::sync::atomic::AtomicU32 =
418 core::sync::atomic::AtomicU32::new(0);
419 let n = SKIP_DIAG.fetch_add(1, Ordering::Relaxed);
420 if n < 4 || n.is_multiple_of(64) {
421 crate::warn!(
422 "[WM][DIAG] shared_buffer copy SKIPPED (size mismatch): shared={} local={} w={} h={} count={}",
423 buf_guard.len(),
424 local_buf.len(),
425 w,
426 h,
427 n + 1
428 );
429 }
430 }
431 }
432
433 last_draw_time = current_time;
434 }
435
436 crate::kernel::scheduler::sleep(5);
438 }
439 if let Some(pid) = crate::kernel::scheduler::get_current_process_id() {
440 DRAW_TARGETS.lock().remove(&pid);
441 }
442}
443
444pub struct Window {
446 pub x: i32,
447 pub y: i32,
448 pub width: u32,
449 pub height: u32,
450 pub title: String,
451 pub app: Box<dyn App>,
452 pub is_dragging: bool,
453 pub drag_offset_x: i32,
454 pub drag_offset_y: i32,
455 pub buffer: Option<alloc::vec::Vec<u32>>,
456 pub workspace: u8,
458 pub is_minimized: bool,
459 pub is_fullscreen: bool,
460 pub saved_x: i32,
461 pub saved_y: i32,
462 pub saved_w: u32,
463 pub saved_h: u32,
464 pub saved_ws: u8,
465 pub tab_pids: alloc::vec::Vec<Option<usize>>,
467}
468
469impl Window {
470 pub fn new(x: i32, y: i32, w: u32, h: u32, title: &str, app: Box<dyn App>) -> Self {
471 let buffer = alloc::vec![0u32; (w * h) as usize];
472 let init_tabs = 1;
474 Self {
475 x,
476 y,
477 width: w,
478 height: h,
479 title: String::from(title),
480 app,
481 is_dragging: false,
482 drag_offset_x: 0,
483 drag_offset_y: 0,
484 buffer: Some(buffer),
485 workspace: 1,
486 is_minimized: false,
487 is_fullscreen: false,
488 saved_x: x,
489 saved_y: y,
490 saved_w: w,
491 saved_h: h,
492 saved_ws: 1,
493 tab_pids: alloc::vec![None; init_tabs],
494 }
495 }
496
497 pub fn resize(&mut self, new_w: u32, new_h: u32) -> bool {
503 if new_w == 0 || new_h == 0 {
504 return false;
505 }
506 let needed = (new_w as usize).saturating_mul(new_h as usize);
507 let have = self.buffer.as_ref().map(|b| b.len()).unwrap_or(0);
508
509 if have < needed {
510 let (used, total) = crate::kernel::allocator::get_heap_stats();
512 let free = total.saturating_sub(used);
513 let need_bytes = needed.saturating_mul(4).saturating_add(64 * 1024);
514 if free < need_bytes {
515 crate::println!(
516 "[WM] resize denied: insufficient heap (free {} KB, need {} KB) — keeping current size",
517 free / 1024, need_bytes / 1024
518 );
519 return false;
520 }
521 self.buffer = None;
523 self.buffer = Some(alloc::vec![0u32; needed]);
524 }
525
526 self.width = new_w;
527 self.height = new_h;
528 self.app.on_resize(new_w, new_h);
529 true
530 }
531
532 pub fn spawn_tab_thread(&mut self, tab_idx: usize) {
534 while self.tab_pids.len() <= tab_idx {
535 self.tab_pids.push(None);
536 }
537 if let Some(thread_fn) = self.app.tab_thread_fn() {
538 let pid = crate::kernel::scheduler::spawn_with_stack(
539 thread_fn,
540 crate::kernel::scheduler::Priority::Normal,
541 "app_tab",
542 crate::kernel::scheduler::LARGE_STACK_SIZE,
543 );
544 if pid != 0 {
545 self.tab_pids[tab_idx] = Some(pid);
546 crate::info!("[WM] tab {} thread spawned pid={}", tab_idx, pid);
547 } else {
548 crate::warn!(
554 "[WM] tab {} のスレッドを起動できません(ヒープ不足)",
555 tab_idx
556 );
557 }
558 }
559 }
560
561 pub fn clear_tab_thread(&mut self, tab_idx: usize) {
564 if tab_idx < self.tab_pids.len() {
565 if let Some(pid) = self.tab_pids[tab_idx] {
566 crate::info!("[WM] tab {} thread pid={} released", tab_idx, pid);
567 }
568 self.tab_pids[tab_idx] = None;
569 }
570 }
571}
572
573#[derive(Copy, Clone, Debug)]
574pub struct Rect {
575 pub x: i32,
576 pub y: i32,
577 pub w: u32,
578 pub h: u32,
579}
580
581impl Rect {
582 pub fn new(x: i32, y: i32, w: u32, h: u32) -> Self {
583 Self { x, y, w, h }
584 }
585
586 pub fn union(&self, other: &Self) -> Self {
587 let x0 = self.x.min(other.x);
588 let y0 = self.y.min(other.y);
589 let x1 = (self.x + self.w as i32).max(other.x + other.w as i32);
590 let y1 = (self.y + self.h as i32).max(other.y + other.h as i32);
591 Self {
592 x: x0,
593 y: y0,
594 w: (x1 - x0).max(0) as u32,
595 h: (y1 - y0).max(0) as u32,
596 }
597 }
598}
599
600pub struct WindowManager {
601 pub windows: Vec<Window>,
603 pub dirty: bool,
604 pub terminal_focused: bool,
605 pub active_workspace: u8,
606 pub slide_out: bool,
607 pub alt_hold_mode: bool,
608 pub alt_input_buffer: String,
609
610 pub dirty_rects: Vec<Rect>,
612
613 last_mouse_x: i32,
615 last_mouse_y: i32,
616 last_mouse_btn: bool,
617}
618
619static mut GLOBAL_WM: Option<ReentrantMutex<WindowManager>> = None;
621
622impl Default for WindowManager {
623 fn default() -> Self {
624 Self::new()
625 }
626}
627
628impl WindowManager {
629 pub fn new() -> Self {
630 Self {
631 windows: Vec::new(),
632 dirty: true,
633 terminal_focused: true,
634 active_workspace: 1,
635 slide_out: false,
636 alt_hold_mode: false,
637 alt_input_buffer: String::new(),
638 dirty_rects: Vec::new(),
639 last_mouse_x: 0,
640 last_mouse_y: 0,
641 last_mouse_btn: false,
642 }
643 }
644
645 pub fn add_dirty_rect(&mut self, rect: Rect) {
646 self.dirty_rects.push(rect);
647 }
648
649 pub fn clear_dirty_rects(&mut self) {
650 self.dirty_rects.clear();
651 }
652
653 pub fn get_dirty_bounding_box(&self, screen_w: u32, screen_h: u32) -> Option<Rect> {
654 if self.dirty_rects.is_empty() {
655 return None;
656 }
657 let mut r = self.dirty_rects[0];
658 for i in 1..self.dirty_rects.len() {
659 r = r.union(&self.dirty_rects[i]);
660 }
661
662 let x0 = r.x.clamp(0, screen_w as i32);
663 let y0 = r.y.clamp(0, screen_h as i32);
664 let x1 = (r.x + r.w as i32).clamp(0, screen_w as i32);
665 let y1 = (r.y + r.h as i32).clamp(0, screen_h as i32);
666
667 Some(Rect::new(x0, y0, (x1 - x0) as u32, (y1 - y0) as u32))
668 }
669
670 pub fn add_window(&mut self, mut win: Window) {
671 let app_name = win.app.name();
672 let mut existing_idx = None;
673 for (i, w) in self.windows.iter().enumerate() {
674 if w.app.name() == app_name {
675 existing_idx = Some(i);
676 break;
677 }
678 }
679
680 if let Some(idx) = existing_idx {
681 self.windows[idx].is_minimized = false;
683 let target_ws = self.windows[idx].workspace;
684 self.active_workspace = target_ws;
685 self.activate_window(idx);
686 self.dirty = true;
687 return;
688 }
689
690 win.workspace = self.active_workspace;
692 let name = win.title.clone();
693 win.spawn_tab_thread(0);
695 self.add_dirty_rect(Rect::new(win.x, win.y, win.width, win.height));
696 self.windows.push(win);
697 self.dirty = true;
698 self.terminal_focused = false;
699 let (used, total) = crate::kernel::allocator::get_heap_stats();
700 crate::debug!(
701 "[WM] open '{}' -> {} windows | heap {}/{} KB",
702 name,
703 self.windows.len(),
704 used / 1024,
705 total / 1024
706 );
707 }
708
709 pub fn remove_active_window(&mut self) {
710 if !self.windows.is_empty() {
711 let name = self
712 .windows
713 .last()
714 .map(|w| w.title.clone())
715 .unwrap_or_else(|| alloc::string::String::from("?"));
716 let info = self.windows.last().map(|w| {
718 (
719 Rect::new(w.x, w.y, w.width, w.height),
720 alloc::string::String::from(w.app.name()),
721 )
722 });
723 if let Some((rect, app_name)) = info {
724 self.add_dirty_rect(rect);
725 crate::kernel::watchdog::clear(&app_name);
726 }
727 self.windows.pop();
728 self.dirty = true;
729 let (used, total) = crate::kernel::allocator::get_heap_stats();
730 crate::debug!(
731 "[WM] close '{}' -> {} windows | heap {}/{} KB",
732 name,
733 self.windows.len(),
734 used / 1024,
735 total / 1024
736 );
737 }
738 }
739
740 pub fn activate_window(&mut self, idx: usize) {
742 if idx < self.windows.len() {
745 let target_ws = self.windows[idx].workspace;
746 self.active_workspace = target_ws;
747 self.terminal_focused = false;
748 self.slide_out = false;
751 let win = self.windows.remove(idx);
752 let rect = Rect::new(win.x, win.y, win.width, win.height);
753 self.add_dirty_rect(rect);
754 self.windows.push(win);
755 self.add_dirty_rect(rect);
756 self.dirty = true;
757 }
758 }
759
760 pub fn any_window_covers_rect(&self, x0: i32, y0: i32, x1: i32, y1: i32) -> bool {
768 if self.active_workspace == 1 && self.slide_out {
770 return false;
771 }
772 for win in self.windows.iter() {
773 if win.workspace != self.active_workspace || win.is_minimized {
774 continue;
775 }
776 let wx1 = win.x + win.width as i32;
777 let wy1 = win.y + win.height as i32;
778 if win.x < x1 && wx1 > x0 && win.y < y1 && wy1 > y0 {
780 return true;
781 }
782 }
783 false
784 }
785
786 pub fn draw_all(&mut self, screen: &Screen) {
787 self.dirty = false;
788 let config = crate::kernel::config::get_config();
789 let base_frame_color = Color(config.theme.non_active_window_border);
790 let title_bar_color = Color(config.theme.window_title_bar);
791 let title_fg_color = Color(config.theme.window_title_fg);
792 let active_frame_color = Color(config.theme.active_window_border);
793
794 let num_windows = self.windows.len();
795 for (i, win) in self.windows.iter_mut().enumerate() {
796 if win.workspace != self.active_workspace || win.is_minimized {
798 continue;
799 }
800 let is_active = i == num_windows.saturating_sub(1) && !self.terminal_focused;
801 let current_frame_color = if is_active {
802 active_frame_color
803 } else {
804 base_frame_color
805 };
806 let border_w = 2; let w = win.width;
809 let h = win.height;
810
811 if let Some(ref mut win_buf) = win.buffer {
812 screen.set_render_target(win_buf.as_mut_ptr(), w, h);
814
815 let mut tab_list = win.app.tabs();
817 let active_idx = win.app.active_tab();
818 let is_terminal = win.app.name() == "Terminal";
819 if is_terminal && tab_list.is_empty() {
820 tab_list.push(alloc::string::String::from("Shell"));
821 }
822 let has_tabs = !tab_list.is_empty();
823 let tab_w = if has_tabs { 100 } else { 0 };
824 let app_x = tab_w;
825 let app_y = 31u32;
826 let app_w = w.saturating_sub(tab_w);
827 let _app_h = h.saturating_sub(31);
828
829 let app_name = win.app.name();
830
831 {
835 let f = crate::kernel::draw::FRAME_NO
836 .fetch_add(1, Ordering::Relaxed)
837 .wrapping_add(1);
838 if f.is_multiple_of(64) {
839 crate::os_lib::web_engine::fetch_limit::idle_line();
848 let ht = crate::kernel::draw::HERO_TEXT_FRAME.load(Ordering::Relaxed);
849 crate::warn!(
850 "[WM][FRAME] present_frame={} last_hero_text_frame={}",
851 f,
852 if ht == u32::MAX { 0 } else { ht }
853 );
854 }
855 }
856
857 if win.app.is_proxy() {
858 if let Some(shared_buf_arc) = win.app.get_shared_buffer() {
863 if let Some(shared_buf) = shared_buf_arc.try_lock() {
864 let copy_len = (w * h) as usize;
866 if shared_buf.len() >= copy_len && win_buf.len() >= copy_len {
867 unsafe {
868 core::ptr::copy_nonoverlapping(
869 shared_buf.as_ptr(),
870 win_buf.as_mut_ptr(),
871 copy_len,
872 );
873 }
874 } else {
875 static CSKIP: core::sync::atomic::AtomicU32 =
880 core::sync::atomic::AtomicU32::new(0);
881 let n = CSKIP.fetch_add(1, Ordering::Relaxed);
882 if n < 4 || n.is_multiple_of(128) {
883 crate::warn!(
884 "[WM][DIAG] compositor copy SKIPPED: shared={} win_buf={} need={} (w={} h={}) count={}",
885 shared_buf.len(),
886 win_buf.len(),
887 copy_len,
888 w,
889 h,
890 n + 1
891 );
892 }
893 }
894 } else {
895 static TRYLOCK_FAIL: core::sync::atomic::AtomicU32 =
904 core::sync::atomic::AtomicU32::new(0);
905 let n = TRYLOCK_FAIL.fetch_add(1, Ordering::Relaxed);
906 if n < 4 || n.is_multiple_of(128) {
907 crate::warn!(
908 "[WM][DIAG] compositor try_lock FAILED (using stale frame) count={}",
909 n + 1
910 );
911 }
912 self.dirty = true;
913 }
914 }
915 } else {
916 screen.boxfill(0, 0, w, h, Color(config.theme.app_bg)); screen.set_clip(app_x, app_y, w, h);
921 if crate::kernel::watchdog::is_not_responding(app_name) {
922 screen.boxfill(app_x, app_y, w, h, Color(config.theme.ui_overlay_bg));
923 screen.draw_string_vector(
924 app_x + 16,
925 app_y + 16,
926 "Application Not Responding",
927 Color(config.theme.error_fg),
928 16,
929 );
930 screen.draw_string_vector(
931 app_x + 16,
932 app_y + 40,
933 "Close the window (Alt+W) to recover.",
934 Color(config.theme.terminal_fg),
935 13,
936 );
937 } else {
938 crate::kernel::watchdog::enter(app_name);
939 win.app.draw(screen, app_x, app_y, app_w, _app_h);
940 crate::kernel::watchdog::exit();
941 }
942 screen.clear_clip();
943 }
944
945 screen.boxfill(0, 0, w, 30, title_bar_color);
948
949 let shadow_color = Color(0xFF111217);
951 screen.boxfill(0, 0, w, 1, shadow_color); screen.boxfill(0, 0, 1, h, shadow_color); screen.boxfill(w - 1, 0, w, h, shadow_color); screen.boxfill(0, h - 1, w, h, shadow_color); screen.boxfill(1, 1, w - 1, 1 + border_w, current_frame_color); screen.boxfill(1, 1, 1 + border_w, h - 1, current_frame_color); screen.boxfill(w - 1 - border_w, 1, w - 1, h - 1, current_frame_color); screen.boxfill(1, h - 1 - border_w, w - 1, h - 1, current_frame_color); screen.boxfill(1, 30, w - 1, 30 + border_w, current_frame_color); screen.draw_string_vector(10, 5, &win.title, title_fg_color, 16);
964
965 static ICON_CLOSE: &[u8] =
967 include_bytes!("../../icons/ic_fluent_dismiss_circle_20_regular.svg");
968 static ICON_FS: &[u8] =
969 include_bytes!("../../icons/ic_fluent_full_screen_maximize_20_regular.svg");
970 static ICON_MAX: &[u8] =
971 include_bytes!("../../icons/ic_fluent_maximize_20_regular.svg");
972 static ICON_NORM: &[u8] =
973 include_bytes!("../../icons/ic_fluent_full_screen_minimize_20_regular.svg");
974 static ICON_MIN: &[u8] =
975 include_bytes!("../../icons/ic_fluent_subtract_circle_20_regular.svg");
976
977 let btn_w = 20u32;
978 let _btn_h = 20u32;
979 let btn_y = 5u32;
980 let gap = 5i32;
981
982 let bx_close = w - 10 - btn_w;
984 screen.draw_svg_icon(bx_close, btn_y, ICON_CLOSE, btn_w, Color(config.theme.win_btn_close));
985
986 let bx_fs = (bx_close as i32 - btn_w as i32 - gap) as u32;
988 screen.draw_svg_icon(bx_fs, btn_y, ICON_FS, btn_w, Color(config.theme.win_btn_fullscreen));
989
990 let bx_max = (bx_fs as i32 - btn_w as i32 - gap) as u32;
992 screen.draw_svg_icon(bx_max, btn_y, ICON_MAX, btn_w, Color(config.theme.win_btn_maximize));
993
994 let bx_norm = (bx_max as i32 - btn_w as i32 - gap) as u32;
996 screen.draw_svg_icon(bx_norm, btn_y, ICON_NORM, btn_w, Color(config.theme.win_btn_restore));
997
998 let bx_min = (bx_norm as i32 - btn_w as i32 - gap) as u32;
1000 screen.draw_svg_icon(bx_min, btn_y, ICON_MIN, btn_w, Color(config.theme.win_btn_minimize));
1001
1002 if has_tabs {
1004 screen.boxfill(0, app_y, tab_w, h, Color(config.theme.tab_area_bg));
1005 screen.boxfill(tab_w - 1, app_y, tab_w, h, Color(config.theme.tab_area_border)); screen.draw_string_vector(10, app_y + 10, "TABS", Color(config.theme.tab_inactive_fg), 14);
1008
1009 screen.boxfill(75, app_y + 8, 92, app_y + 25, Color(config.theme.tab_area_border));
1011 screen.draw_string_vector(80, app_y + 10, "+", Color(config.theme.tab_active_fg), 14);
1012
1013 screen.boxfill(10, app_y + 30, 90, app_y + 31, Color(config.theme.tab_area_border));
1014
1015 let mut tab_y = app_y + 40;
1016 for (idx, tab_title) in tab_list.iter().enumerate() {
1017 if tab_y + 42 > h {
1018 break;
1019 }
1020
1021 let is_active_tab = idx == active_idx;
1022 let bg_color = if is_active_tab {
1023 Color(config.theme.tab_active_bg)
1024 } else {
1025 Color(config.theme.tab_area_bg)
1026 };
1027 let fg_color = if is_active_tab {
1028 Color(config.theme.tab_active_fg)
1029 } else {
1030 Color(config.theme.tab_inactive_fg)
1031 };
1032
1033 screen.boxfill(5, tab_y, 95, tab_y + 36, bg_color);
1034
1035 let label = alloc::format!("{})", idx + 1);
1036 screen.draw_string_vector(10, tab_y + 12, &label, fg_color, 12);
1037
1038 let mut lines = alloc::vec![];
1040 if tab_title.contains('\n') {
1041 for (count, part) in tab_title.split('\n').enumerate() {
1042 if count >= 2 {
1043 break;
1044 }
1045 lines.push(alloc::string::String::from(part));
1046 }
1047 } else {
1048 let chars: alloc::vec::Vec<char> = tab_title.chars().collect();
1049 if chars.len() <= 6 {
1050 lines.push(tab_title.clone());
1051 } else {
1052 let first: alloc::string::String = chars.iter().take(6).collect();
1053 let mut second: alloc::string::String =
1054 chars.iter().skip(6).collect();
1055 if second.chars().count() > 6 {
1056 let mut sec_trunc: alloc::string::String =
1057 second.chars().take(4).collect();
1058 sec_trunc.push_str("..");
1059 second = sec_trunc;
1060 }
1061 lines.push(first);
1062 lines.push(second);
1063 }
1064 }
1065
1066 if lines.len() == 1 {
1067 screen.draw_string_vector(22, tab_y + 12, &lines[0], fg_color, 10);
1068 } else if lines.len() >= 2 {
1069 screen.draw_string_vector(22, tab_y + 5, &lines[0], fg_color, 10);
1070 screen.draw_string_vector(22, tab_y + 19, &lines[1], fg_color, 10);
1071 }
1072
1073 screen.boxfill(78, tab_y + 10, 92, tab_y + 24, Color(config.theme.tab_area_border));
1075 screen.draw_string_vector(82, tab_y + 12, "x", Color(config.theme.win_btn_close), 10);
1076
1077 tab_y += 42;
1078 }
1079 }
1080
1081 screen.reset_render_target();
1083
1084 let screen_w = screen.width;
1086 let screen_h = screen.height;
1087 let main_ptr = if screen.use_back_buffer.get() && !screen.back_buffer.is_null() {
1088 screen.back_buffer
1089 } else {
1090 screen.vram
1091 };
1092
1093 let win_x = if self.active_workspace == 1 && self.slide_out {
1094 win.x + screen.width as i32
1095 } else {
1096 win.x
1097 };
1098 let win_y = win.y;
1099
1100 let src_x0 = if win_x < 0 { (-win_x) as u32 } else { 0 };
1102 let src_y0 = if win_y < 0 { (-win_y) as u32 } else { 0 };
1103 let dst_x0 = win_x.max(0) as u32;
1104 let dst_y0 = win_y.max(0) as u32;
1105 let copy_w = w.saturating_sub(src_x0).min(screen_w.saturating_sub(dst_x0));
1106 let copy_h = h.saturating_sub(src_y0).min(screen_h.saturating_sub(dst_y0));
1107
1108 let (clip_x0, clip_y0, clip_x1, clip_y1) = if let Some(c) = screen.clip.get() {
1109 c
1110 } else {
1111 (0, 0, screen_w, screen_h)
1112 };
1113 let eff_dst_x0 = dst_x0.max(clip_x0);
1114 let eff_dst_y0 = dst_y0.max(clip_y0);
1115 let eff_dst_x1 = (dst_x0 + copy_w).min(clip_x1);
1116 let eff_dst_y1 = (dst_y0 + copy_h).min(clip_y1);
1117
1118 if eff_dst_x1 > eff_dst_x0 && eff_dst_y1 > eff_dst_y0 {
1119 let eff_w = eff_dst_x1 - eff_dst_x0;
1120 let eff_h = eff_dst_y1 - eff_dst_y0;
1121 let eff_src_x0 = src_x0 + (eff_dst_x0 - dst_x0);
1122 let eff_src_y0 = src_y0 + (eff_dst_y0 - dst_y0);
1123 unsafe {
1124 for row in 0..eff_h {
1125 let src_ptr = win_buf.as_ptr().add(((eff_src_y0 + row) * w + eff_src_x0) as usize);
1126 let dst_ptr = main_ptr.add(((eff_dst_y0 + row) * screen_w + eff_dst_x0) as usize);
1127 core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, eff_w as usize);
1128 }
1129 }
1130 }
1131 }
1132 }
1133
1134 if self.alt_hold_mode {
1136 let overlay_w = 200u32;
1137 let overlay_h = 100u32;
1138 let overlay_x = (screen.width - overlay_w) / 2;
1139 let overlay_y = (screen.height - overlay_h) / 2;
1140
1141 let overlay_bg_semi = Color(0xEE000000 | (config.theme.ui_overlay_bg & 0x00FFFFFF));
1142 screen.boxfill(
1143 overlay_x,
1144 overlay_y,
1145 overlay_x + overlay_w,
1146 overlay_y + overlay_h,
1147 overlay_bg_semi,
1148 );
1149 screen.boxfill(
1150 overlay_x,
1151 overlay_y,
1152 overlay_x + overlay_w,
1153 overlay_y + 2,
1154 Color(config.theme.ui_overlay_accent),
1155 );
1156 screen.draw_string_vector(
1157 overlay_x + 15,
1158 overlay_y + 10,
1159 "STATELESS JUMP",
1160 Color(config.theme.terminal_fg),
1161 14,
1162 );
1163
1164 let val_str = if self.alt_input_buffer.is_empty() {
1165 alloc::string::String::from("_")
1166 } else {
1167 alloc::format!("{}_", self.alt_input_buffer)
1168 };
1169 screen.draw_string_vector(
1170 overlay_x + 80,
1171 overlay_y + 40,
1172 &val_str,
1173 Color(config.theme.success_fg),
1174 32,
1175 );
1176 }
1177 }
1178
1179 pub fn handle_mouse(
1181 &mut self,
1182 mx: i32,
1183 my: i32,
1184 btn_left: bool,
1185 btn_right: bool,
1186 wheel: i32,
1187 screen: &Screen,
1188 ) -> bool {
1189 let mut handled = false;
1190
1191 let dx = mx - self.last_mouse_x;
1192 let dy = my - self.last_mouse_y;
1193
1194 let is_dragging = if let Some(win) = self.windows.last() {
1197 win.is_dragging && win.workspace == self.active_workspace && !win.is_minimized
1198 } else {
1199 false
1200 };
1201
1202 if is_dragging {
1203 if btn_left {
1204 let old_rect = if let Some(win) = self.windows.last() {
1206 Rect::new(win.x, win.y, win.width, win.height)
1207 } else {
1208 Rect::new(0, 0, 0, 0)
1209 };
1210 self.add_dirty_rect(old_rect);
1211
1212 if let Some(win) = self.windows.last_mut() {
1213 win.x += dx;
1214 win.y += dy;
1215 }
1216 self.dirty = true;
1217
1218 let new_rect = if let Some(win) = self.windows.last() {
1220 Rect::new(win.x, win.y, win.width, win.height)
1221 } else {
1222 Rect::new(0, 0, 0, 0)
1223 };
1224 self.add_dirty_rect(new_rect);
1225
1226 self.last_mouse_x = mx;
1227 self.last_mouse_y = my;
1228 self.last_mouse_btn = btn_left;
1229 return true;
1230 } else {
1231 if let Some(win) = self.windows.last_mut() {
1232 win.is_dragging = false;
1233 }
1234 }
1235 }
1236
1237 let btn_just_pressed = btn_left && !self.last_mouse_btn;
1239 let mut target_index = None;
1240 let mut close_clicked = false;
1241 let mut fs_clicked = false;
1242 let mut max_clicked = false;
1243 let mut norm_clicked = false;
1244 let mut min_clicked = false;
1245
1246 for (i, win) in self.windows.iter_mut().enumerate().rev() {
1247 if win.workspace != self.active_workspace || win.is_minimized {
1249 continue;
1250 }
1251 let win_x = if self.active_workspace == 1 && self.slide_out {
1252 win.x + 2000 } else {
1254 win.x
1255 };
1256 if mx >= win_x
1257 && mx <= win_x + win.width as i32
1258 && my >= win.y
1259 && my <= win.y + win.height as i32
1260 {
1261 target_index = Some(i);
1262
1263 if my < win.y + 30 {
1265 let btn_w = 20;
1266 let gap = 5;
1267 let bx_close = win_x + win.width as i32 - 10 - btn_w;
1268 let bx_fs = bx_close - btn_w - gap;
1269 let bx_max = bx_fs - btn_w - gap;
1270 let bx_norm = bx_max - btn_w - gap;
1271 let bx_min = bx_norm - btn_w - gap;
1272
1273 if btn_just_pressed {
1274 if mx >= bx_close && mx <= bx_close + btn_w {
1275 close_clicked = true;
1276 } else if mx >= bx_fs && mx <= bx_fs + btn_w {
1277 fs_clicked = true;
1278 } else if mx >= bx_max && mx <= bx_max + btn_w {
1279 max_clicked = true;
1280 } else if mx >= bx_norm && mx <= bx_norm + btn_w {
1281 norm_clicked = true;
1282 } else if mx >= bx_min && mx <= bx_min + btn_w {
1283 min_clicked = true;
1284 } else {
1285 win.is_dragging = true;
1287 win.drag_offset_x = mx - win.x;
1288 win.drag_offset_y = my - win.y;
1289 }
1290 }
1291 } else {
1292 let mut tab_list = win.app.tabs();
1293 let is_terminal = win.app.name() == "Terminal";
1294 if is_terminal && tab_list.is_empty() {
1295 tab_list.push(alloc::string::String::from("Shell"));
1296 }
1297 let has_tabs = !tab_list.is_empty();
1298 let tab_w = if has_tabs { 100 } else { 0 };
1299
1300 let local_x = mx - win_x;
1301 let local_y = my - (win.y + 31);
1302
1303 if has_tabs && local_x < tab_w {
1304 if btn_just_pressed {
1306 let tab_y_click = local_y as u32;
1307 if (8..=25).contains(&tab_y_click) && (75..=92).contains(&local_x) {
1308 let new_tab_idx = win.app.tabs().len();
1310 win.app.add_tab();
1311 win.spawn_tab_thread(new_tab_idx);
1312 self.dirty = true;
1313 crate::kernel::audio::play_system_se(
1314 crate::kernel::audio::SeType::Apply,
1315 );
1316 } else if tab_y_click >= 40 {
1317 let clicked_tab = ((tab_y_click - 40) / 42) as usize;
1318 if clicked_tab < tab_list.len() {
1319 let relative_y = (tab_y_click - 40) % 42;
1320 if relative_y <= 36 {
1321 if (78..=92).contains(&local_x)
1322 && (10..=24).contains(&relative_y)
1323 {
1324 win.clear_tab_thread(clicked_tab);
1326 let should_close = win.app.close_tab(clicked_tab);
1327 if should_close {
1328 close_clicked = true;
1329 } else {
1330 crate::kernel::audio::play_system_se(
1331 crate::kernel::audio::SeType::Apply,
1332 );
1333 }
1334 } else {
1335 win.app.switch_tab(clicked_tab);
1336 }
1337 self.dirty = true;
1338 }
1339 }
1340 }
1341 }
1342 } else {
1343 win.app
1345 .on_mouse(local_x - tab_w, local_y, btn_left, btn_right, wheel);
1346 if btn_just_pressed || wheel != 0 || (btn_left != self.last_mouse_btn) {
1348 self.dirty = true;
1349 }
1350 }
1351 }
1352
1353 handled = true;
1354 break;
1355 }
1356 }
1357
1358 if let Some(idx) = target_index {
1360 if btn_just_pressed {
1361 self.activate_window(idx);
1362 }
1363 if close_clicked {
1364 if let Some(w) = self.windows.last() {
1365 self.add_dirty_rect(Rect::new(w.x, w.y, w.width, w.height));
1366 }
1367 self.windows.pop();
1368 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1369 self.dirty = true;
1370 if self.windows.is_empty() {
1371 self.terminal_focused = true;
1372 }
1373 } else if fs_clicked {
1374 let mut target_ws = None;
1376 for ws in 2..=8 {
1377 let mut used = false;
1378 for w in &self.windows {
1379 if w.workspace == ws && !w.is_minimized {
1380 used = true;
1381 break;
1382 }
1383 }
1384 if !used {
1385 target_ws = Some(ws);
1386 break;
1387 }
1388 }
1389 let target_ws = target_ws.unwrap_or(self.active_workspace);
1390
1391 let old_rect = self
1392 .windows
1393 .last()
1394 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1395 if let Some(r) = old_rect {
1396 self.add_dirty_rect(r);
1397 }
1398
1399 if let Some(win) = self.windows.last_mut() {
1400 win.is_fullscreen = true;
1401 win.saved_x = win.x;
1402 win.saved_y = win.y;
1403 win.saved_w = win.width;
1404 win.saved_h = win.height;
1405 win.saved_ws = win.workspace;
1406 let sw = screen.width;
1407 let sh = screen.height;
1408 win.x = 0;
1409 win.y = 0;
1410 win.resize(sw, sh);
1411 win.workspace = target_ws;
1412 self.active_workspace = target_ws;
1413 self.dirty = true;
1414 }
1415 let sw = screen.width;
1416 let sh = screen.height;
1417 self.add_dirty_rect(Rect::new(0, 0, sw, sh));
1418 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1419 } else if max_clicked {
1420 let old_rect = self
1421 .windows
1422 .last()
1423 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1424 if let Some(r) = old_rect {
1425 self.add_dirty_rect(r);
1426 }
1427
1428 if let Some(win) = self.windows.last_mut() {
1429 win.is_fullscreen = false;
1430 win.saved_x = win.x;
1431 win.saved_y = win.y;
1432 win.saved_w = win.width;
1433 win.saved_h = win.height;
1434 let sh = screen.height.saturating_sub(crate::kernel::fn_bar::BAR_HEIGHT);
1435 let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1436 let max_w = screen.width.saturating_sub(sysmon_w);
1437 win.x = 0;
1438 win.y = 0;
1439 win.resize(max_w, sh);
1440 self.dirty = true;
1441 }
1442
1443 let new_rect = self
1444 .windows
1445 .last()
1446 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1447 if let Some(r) = new_rect {
1448 self.add_dirty_rect(r);
1449 }
1450 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1451 } else if norm_clicked {
1452 let old_rect = self
1453 .windows
1454 .last()
1455 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1456 if let Some(r) = old_rect {
1457 self.add_dirty_rect(r);
1458 }
1459
1460 if let Some(win) = self.windows.last_mut() {
1461 win.is_fullscreen = false;
1462 win.x = win.saved_x;
1463 win.y = win.saved_y;
1464 let (rw, rh) = (win.saved_w, win.saved_h);
1465
1466 self.active_workspace = win.saved_ws;
1467 win.workspace = win.saved_ws;
1468
1469 win.resize(rw, rh);
1470 self.dirty = true;
1471 }
1472
1473 let new_rect = self
1474 .windows
1475 .last()
1476 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1477 if let Some(r) = new_rect {
1478 self.add_dirty_rect(r);
1479 }
1480 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1481 } else if min_clicked {
1482 let old_rect = self
1483 .windows
1484 .last()
1485 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1486 if let Some(r) = old_rect {
1487 self.add_dirty_rect(r);
1488 }
1489
1490 if let Some(win) = self.windows.last_mut() {
1491 win.is_minimized = true;
1492 }
1493 self.dirty = true;
1494 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1495 }
1496 } else if btn_just_pressed {
1497 let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1500 let sysmon_x = screen.width - sysmon_w;
1501 if mx < sysmon_x as i32 {
1502 self.terminal_focused = true;
1503 }
1504 }
1505
1506 self.last_mouse_x = mx;
1507 self.last_mouse_y = my;
1508 self.last_mouse_btn = btn_left;
1509
1510 handled
1511 }
1512
1513 pub fn handle_key(
1515 &mut self,
1516 keycode: u8,
1517 pressed: bool,
1518 ascii: Option<char>,
1519 alt_pressed: bool,
1520 screen: &Screen,
1521 ) -> bool {
1522 if !pressed {
1523 return false;
1524 }
1525
1526 if keycode == 0x3B && !alt_pressed {
1530 if self.terminal_focused && !self.slide_out {
1531 self.slide_out = true;
1533 self.dirty = true;
1534 } else {
1535 self.terminal_focused = true;
1537 self.slide_out = false;
1538 self.dirty = true;
1539 }
1540 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1541 return true;
1542 }
1543
1544 if alt_pressed && (0x3B..=0x42).contains(&keycode) {
1546 let target_ws = if keycode == 0x3B {
1547 1
1548 } else {
1549 (keycode - 0x3C) + 2
1550 };
1551 self.active_workspace = target_ws;
1552 self.dirty = true;
1553
1554 let mut last_win_idx = None;
1556 for (i, win) in self.windows.iter().enumerate() {
1557 if win.workspace == target_ws && !win.is_minimized {
1558 last_win_idx = Some(i);
1559 }
1560 }
1561 if let Some(idx) = last_win_idx {
1562 self.activate_window(idx);
1563 } else {
1564 self.terminal_focused = true;
1565 }
1566 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1567 return true;
1568 }
1569
1570 if alt_pressed && keycode == 0x43 {
1572 let active_is_matching = if let Some(win) = self.windows.last() {
1573 win.workspace == self.active_workspace && !win.is_minimized
1574 } else {
1575 false
1576 };
1577
1578 if active_is_matching {
1579 let old_rect = self
1580 .windows
1581 .last()
1582 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1583 if let Some(r) = old_rect {
1584 self.add_dirty_rect(r);
1585 }
1586
1587 if let Some(win) = self.windows.last_mut() {
1588 win.is_minimized = true;
1589 }
1590 self.dirty = true;
1591
1592 let mut found_next = false;
1594 let len = self.windows.len();
1595 if len > 1 {
1596 for i in (0..len - 1).rev() {
1597 let is_candidate = {
1598 let w = &self.windows[i];
1599 w.workspace == self.active_workspace && !w.is_minimized
1600 };
1601 if is_candidate {
1602 self.activate_window(i);
1603 found_next = true;
1604 break;
1605 }
1606 }
1607 }
1608 if !found_next {
1609 self.terminal_focused = true;
1610 }
1611 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1612 }
1614 return true;
1615 }
1616
1617 if alt_pressed && keycode == 0x44 {
1619 let matches_ws = if let Some(win) = self.windows.last() {
1620 win.workspace == self.active_workspace
1621 } else {
1622 false
1623 };
1624
1625 if matches_ws {
1626 let old_rect = self
1627 .windows
1628 .last()
1629 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1630 if let Some(r) = old_rect {
1631 self.add_dirty_rect(r);
1632 }
1633
1634 if let Some(win) = self.windows.last_mut() {
1635 win.is_fullscreen = false;
1636 win.x = win.saved_x;
1637 win.y = win.saved_y;
1638 let (rw, rh) = (win.saved_w, win.saved_h);
1639 win.resize(rw, rh);
1640 }
1641 self.dirty = true;
1642
1643 let new_rect = self
1644 .windows
1645 .last()
1646 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1647 if let Some(r) = new_rect {
1648 self.add_dirty_rect(r);
1649 }
1650 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1651 }
1652 return true;
1653 }
1654
1655 if alt_pressed && keycode == 0x57 {
1657 let matches_ws = if let Some(win) = self.windows.last() {
1658 win.workspace == self.active_workspace
1659 } else {
1660 false
1661 };
1662
1663 if matches_ws {
1664 let old_rect = self
1665 .windows
1666 .last()
1667 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1668 if let Some(r) = old_rect {
1669 self.add_dirty_rect(r);
1670 }
1671
1672 if let Some(win) = self.windows.last_mut() {
1673 if !win.is_fullscreen {
1674 win.saved_x = win.x;
1675 win.saved_y = win.y;
1676 win.saved_w = win.width;
1677 win.saved_h = win.height;
1678 }
1679
1680 let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1682 let avail_w = screen.width.saturating_sub(100 + sysmon_w);
1683 win.x = 100;
1684 win.y = 0;
1685 let sh = screen.height.saturating_sub(crate::kernel::fn_bar::BAR_HEIGHT);
1686 win.resize(avail_w, sh);
1687 }
1688 self.dirty = true;
1689
1690 let new_rect = self
1691 .windows
1692 .last()
1693 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1694 if let Some(r) = new_rect {
1695 self.add_dirty_rect(r);
1696 }
1697 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1698 }
1699 return true;
1700 }
1701
1702 if alt_pressed && keycode == 0x58 {
1704 let mut target_ws = None;
1706 for ws in 2..=8 {
1707 let mut occupied = false;
1708 for w in &self.windows {
1709 if w.workspace == ws && !w.is_minimized {
1710 occupied = true;
1711 break;
1712 }
1713 }
1714 if !occupied {
1715 target_ws = Some(ws);
1716 break;
1717 }
1718 }
1719
1720 if let Some(ws) = target_ws {
1721 let old_rect = self
1722 .windows
1723 .last()
1724 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1725 if let Some(r) = old_rect {
1726 self.add_dirty_rect(r);
1727 }
1728
1729 if let Some(win) = self.windows.last_mut() {
1730 if !win.is_fullscreen {
1731 win.saved_x = win.x;
1732 win.saved_y = win.y;
1733 win.saved_w = win.width;
1734 win.saved_h = win.height;
1735 }
1736
1737 win.is_fullscreen = true;
1738 win.workspace = ws;
1739 win.x = 0;
1740 win.y = 0;
1741 let (sw, sh) = (screen.width, screen.height);
1742 win.resize(sw, sh);
1743 }
1744 self.active_workspace = ws;
1745 self.dirty = true;
1746 let (sw, sh) = (screen.width, screen.height);
1747 self.add_dirty_rect(Rect::new(0, 0, sw, sh));
1748 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1749 } else {
1750 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1752 }
1753 return true;
1754 }
1755
1756 if self.terminal_focused {
1758 return false;
1759 }
1760 let is_active = if let Some(win) = self.windows.last() {
1761 win.workspace == self.active_workspace && !win.is_minimized
1762 } else {
1763 false
1764 };
1765
1766 if is_active {
1767 if let Some(win) = self.windows.last_mut() {
1768 win.app.on_key(keycode, pressed, ascii);
1769 }
1770
1771 let rect = self
1772 .windows
1773 .last()
1774 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1775 if let Some(r) = rect {
1776 self.add_dirty_rect(r);
1777 }
1778 self.dirty = true;
1779 return true;
1780 }
1781 false
1782 }
1783
1784 pub fn needs_timer_redraw(&self) -> bool {
1786 for win in &self.windows {
1787 if win.workspace == self.active_workspace
1788 && !win.is_minimized
1789 && win.app.needs_timer_redraw()
1790 {
1791 return true;
1792 }
1793 }
1794 false
1795 }
1796}
1797
1798pub fn init() {
1799 unsafe {
1800 GLOBAL_WM = Some(ReentrantMutex::new(WindowManager::new()));
1801 }
1802}
1803
1804pub fn get_instance() -> ReentrantMutexGuard<'static, WindowManager> {
1805 unsafe {
1807 GLOBAL_WM
1808 .get_or_insert_with(|| ReentrantMutex::new(WindowManager::new()))
1809 .lock()
1810 }
1811}
1812
1813use core::cell::UnsafeCell;
1814use core::sync::atomic::{AtomicUsize, Ordering};
1815
1816pub struct ReentrantMutex<T> {
1817 inner: UnsafeCell<T>,
1818 owner: AtomicUsize,
1819 recursion: AtomicUsize,
1820}
1821
1822unsafe impl<T: Send> Send for ReentrantMutex<T> {}
1823unsafe impl<T: Send> Sync for ReentrantMutex<T> {}
1824
1825impl<T> ReentrantMutex<T> {
1826 pub const fn new(value: T) -> Self {
1827 Self {
1828 inner: UnsafeCell::new(value),
1829 owner: AtomicUsize::new(usize::MAX),
1830 recursion: AtomicUsize::new(0),
1831 }
1832 }
1833
1834 pub fn lock(&self) -> ReentrantMutexGuard<'_, T> {
1835 let cid = crate::kernel::scheduler::core_id();
1836 loop {
1837 if self.owner.load(Ordering::Relaxed) == cid {
1838 self.recursion.fetch_add(1, Ordering::Relaxed);
1839 return ReentrantMutexGuard { mutex: self };
1840 }
1841 if self
1842 .owner
1843 .compare_exchange_weak(usize::MAX, cid, Ordering::Acquire, Ordering::Relaxed)
1844 .is_ok()
1845 {
1846 self.recursion.store(1, Ordering::Relaxed);
1847 return ReentrantMutexGuard { mutex: self };
1848 }
1849 core::hint::spin_loop();
1850 }
1851 }
1852}
1853
1854pub struct ReentrantMutexGuard<'a, T> {
1855 mutex: &'a ReentrantMutex<T>,
1856}
1857
1858impl<'a, T> core::ops::Deref for ReentrantMutexGuard<'a, T> {
1859 type Target = T;
1860 fn deref(&self) -> &Self::Target {
1861 unsafe { &*self.mutex.inner.get() }
1862 }
1863}
1864
1865impl<'a, T> core::ops::DerefMut for ReentrantMutexGuard<'a, T> {
1866 fn deref_mut(&mut self) -> &mut Self::Target {
1867 unsafe { &mut *self.mutex.inner.get() }
1868 }
1869}
1870
1871impl<'a, T> Drop for ReentrantMutexGuard<'a, T> {
1872 fn drop(&mut self) {
1873 let rec = self.mutex.recursion.fetch_sub(1, Ordering::Relaxed);
1874 if rec == 1 {
1875 self.mutex.owner.store(usize::MAX, Ordering::Release);
1876 }
1877 }
1878}