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
269pub fn app_runner_entry(arg: usize) {
270 let params_ptr = arg as *mut AppRunnerParams;
271 let mut params = unsafe { Box::from_raw(params_ptr) };
272
273 let mut w = params.width;
274 let mut h = params.height;
275
276 let mut local_buf = alloc::vec![0u32; (w * h) as usize];
278
279 let mut last_draw_time = 0;
280
281 while params.is_alive.load(Ordering::Relaxed) {
282 let current_time = crate::kernel::timer::get_ticks();
283
284 let app_needs_timer = params.app.needs_timer_redraw();
286 params.needs_timer_redraw.store(app_needs_timer, Ordering::Relaxed);
287
288 let events = {
290 let mut q = params.event_queue.lock();
291 let evts = q.clone();
292 q.clear();
293 evts
294 };
295
296 let mut need_redraw = false;
297
298 for evt in events {
299 match evt {
300 AppEvent::Mouse { local_x, local_y, btn_left, btn_right, wheel } => {
301 params.app.on_mouse(local_x, local_y, btn_left, btn_right, wheel);
302 if btn_left || btn_right || wheel != 0 {
303 need_redraw = true;
304 }
305 }
306 AppEvent::Key { keycode, pressed, ascii } => {
307 params.app.on_key(keycode, pressed, ascii);
308 need_redraw = true;
309 }
310 AppEvent::SwitchTab(idx) => {
311 params.app.switch_tab(idx);
312 need_redraw = true;
313 }
314 AppEvent::AddTab => {
315 params.app.add_tab();
316 need_redraw = true;
317 }
318 AppEvent::CloseTab(idx) => {
319 let _ = params.app.close_tab(idx);
320 need_redraw = true;
321 }
322 AppEvent::Resize { width, height } => {
323 w = width;
324 h = height;
325 local_buf = alloc::vec![0u32; (w * h) as usize];
326 {
327 let mut shared = params.shared_buffer.lock();
328 *shared = alloc::vec![0u32; (w * h) as usize];
329 }
330 params.app.on_resize(w, h);
331 need_redraw = true;
332 }
333 }
334 }
335
336 {
338 let mut t = params.tabs.lock();
339 *t = params.app.tabs();
340 }
341 {
342 let mut a = params.active_tab.lock();
343 *a = params.app.active_tab();
344 }
345
346 if params.app.needs_timer_redraw() {
347 need_redraw = true;
348 }
349
350 let time_since_last_draw = current_time.saturating_sub(last_draw_time);
351
352 if need_redraw || time_since_last_draw >= 33 {
354 {
356 let screen = Screen::new(local_buf.as_mut_ptr(), w, h, w * 4);
357 let tab_list = params.app.tabs();
358 let has_tabs = !tab_list.is_empty();
359 let tab_w = if has_tabs { 100 } else { 0 };
360 let app_x = tab_w;
361 let app_y = 31u32;
362 let app_w = w.saturating_sub(tab_w);
363 let app_h = h.saturating_sub(31);
364
365 screen.set_clip(app_x, app_y, w, h);
366 params.app.draw(&screen, app_x, app_y, app_w, app_h);
367 screen.clear_clip();
368 }
369
370 {
372 let mut buf_guard = params.shared_buffer.lock();
373 if buf_guard.len() == local_buf.len() {
374 buf_guard.copy_from_slice(&local_buf);
375 params.buffer_updated.store(true, Ordering::Release);
378 } else {
379 static SKIP_DIAG: core::sync::atomic::AtomicU32 =
386 core::sync::atomic::AtomicU32::new(0);
387 let n = SKIP_DIAG.fetch_add(1, Ordering::Relaxed);
388 if n < 4 || n.is_multiple_of(64) {
389 crate::warn!(
390 "[WM][DIAG] shared_buffer copy SKIPPED (size mismatch): shared={} local={} w={} h={} count={}",
391 buf_guard.len(),
392 local_buf.len(),
393 w,
394 h,
395 n + 1
396 );
397 }
398 }
399 }
400
401 last_draw_time = current_time;
402 }
403
404 crate::kernel::scheduler::sleep(5);
406 }
407}
408
409pub struct Window {
411 pub x: i32,
412 pub y: i32,
413 pub width: u32,
414 pub height: u32,
415 pub title: String,
416 pub app: Box<dyn App>,
417 pub is_dragging: bool,
418 pub drag_offset_x: i32,
419 pub drag_offset_y: i32,
420 pub buffer: Option<alloc::vec::Vec<u32>>,
421 pub workspace: u8,
423 pub is_minimized: bool,
424 pub is_fullscreen: bool,
425 pub saved_x: i32,
426 pub saved_y: i32,
427 pub saved_w: u32,
428 pub saved_h: u32,
429 pub saved_ws: u8,
430 pub tab_pids: alloc::vec::Vec<Option<usize>>,
432}
433
434impl Window {
435 pub fn new(x: i32, y: i32, w: u32, h: u32, title: &str, app: Box<dyn App>) -> Self {
436 let buffer = alloc::vec![0u32; (w * h) as usize];
437 let init_tabs = 1;
439 Self {
440 x,
441 y,
442 width: w,
443 height: h,
444 title: String::from(title),
445 app,
446 is_dragging: false,
447 drag_offset_x: 0,
448 drag_offset_y: 0,
449 buffer: Some(buffer),
450 workspace: 1,
451 is_minimized: false,
452 is_fullscreen: false,
453 saved_x: x,
454 saved_y: y,
455 saved_w: w,
456 saved_h: h,
457 saved_ws: 1,
458 tab_pids: alloc::vec![None; init_tabs],
459 }
460 }
461
462 pub fn resize(&mut self, new_w: u32, new_h: u32) -> bool {
468 if new_w == 0 || new_h == 0 {
469 return false;
470 }
471 let needed = (new_w as usize).saturating_mul(new_h as usize);
472 let have = self.buffer.as_ref().map(|b| b.len()).unwrap_or(0);
473
474 if have < needed {
475 let (used, total) = crate::kernel::allocator::get_heap_stats();
477 let free = total.saturating_sub(used);
478 let need_bytes = needed.saturating_mul(4).saturating_add(64 * 1024);
479 if free < need_bytes {
480 crate::println!(
481 "[WM] resize denied: insufficient heap (free {} KB, need {} KB) — keeping current size",
482 free / 1024, need_bytes / 1024
483 );
484 return false;
485 }
486 self.buffer = None;
488 self.buffer = Some(alloc::vec![0u32; needed]);
489 }
490
491 self.width = new_w;
492 self.height = new_h;
493 self.app.on_resize(new_w, new_h);
494 true
495 }
496
497 pub fn spawn_tab_thread(&mut self, tab_idx: usize) {
499 while self.tab_pids.len() <= tab_idx {
500 self.tab_pids.push(None);
501 }
502 if let Some(thread_fn) = self.app.tab_thread_fn() {
503 let pid = crate::kernel::scheduler::spawn(
504 thread_fn,
505 crate::kernel::scheduler::Priority::Normal,
506 "app_tab",
507 );
508 if pid != 0 {
509 self.tab_pids[tab_idx] = Some(pid);
510 crate::info!("[WM] tab {} thread spawned pid={}", tab_idx, pid);
511 } else {
512 crate::warn!(
518 "[WM] tab {} のスレッドを起動できません(ヒープ不足)",
519 tab_idx
520 );
521 }
522 }
523 }
524
525 pub fn clear_tab_thread(&mut self, tab_idx: usize) {
528 if tab_idx < self.tab_pids.len() {
529 if let Some(pid) = self.tab_pids[tab_idx] {
530 crate::info!("[WM] tab {} thread pid={} released", tab_idx, pid);
531 }
532 self.tab_pids[tab_idx] = None;
533 }
534 }
535}
536
537#[derive(Copy, Clone, Debug)]
538pub struct Rect {
539 pub x: i32,
540 pub y: i32,
541 pub w: u32,
542 pub h: u32,
543}
544
545impl Rect {
546 pub fn new(x: i32, y: i32, w: u32, h: u32) -> Self {
547 Self { x, y, w, h }
548 }
549
550 pub fn union(&self, other: &Self) -> Self {
551 let x0 = self.x.min(other.x);
552 let y0 = self.y.min(other.y);
553 let x1 = (self.x + self.w as i32).max(other.x + other.w as i32);
554 let y1 = (self.y + self.h as i32).max(other.y + other.h as i32);
555 Self {
556 x: x0,
557 y: y0,
558 w: (x1 - x0).max(0) as u32,
559 h: (y1 - y0).max(0) as u32,
560 }
561 }
562}
563
564pub struct WindowManager {
565 pub windows: Vec<Window>,
567 pub dirty: bool,
568 pub terminal_focused: bool,
569 pub active_workspace: u8,
570 pub slide_out: bool,
571 pub alt_hold_mode: bool,
572 pub alt_input_buffer: String,
573
574 pub dirty_rects: Vec<Rect>,
576
577 last_mouse_x: i32,
579 last_mouse_y: i32,
580 last_mouse_btn: bool,
581}
582
583static mut GLOBAL_WM: Option<ReentrantMutex<WindowManager>> = None;
585
586impl Default for WindowManager {
587 fn default() -> Self {
588 Self::new()
589 }
590}
591
592impl WindowManager {
593 pub fn new() -> Self {
594 Self {
595 windows: Vec::new(),
596 dirty: true,
597 terminal_focused: true,
598 active_workspace: 1,
599 slide_out: false,
600 alt_hold_mode: false,
601 alt_input_buffer: String::new(),
602 dirty_rects: Vec::new(),
603 last_mouse_x: 0,
604 last_mouse_y: 0,
605 last_mouse_btn: false,
606 }
607 }
608
609 pub fn add_dirty_rect(&mut self, rect: Rect) {
610 self.dirty_rects.push(rect);
611 }
612
613 pub fn clear_dirty_rects(&mut self) {
614 self.dirty_rects.clear();
615 }
616
617 pub fn get_dirty_bounding_box(&self, screen_w: u32, screen_h: u32) -> Option<Rect> {
618 if self.dirty_rects.is_empty() {
619 return None;
620 }
621 let mut r = self.dirty_rects[0];
622 for i in 1..self.dirty_rects.len() {
623 r = r.union(&self.dirty_rects[i]);
624 }
625
626 let x0 = r.x.clamp(0, screen_w as i32);
627 let y0 = r.y.clamp(0, screen_h as i32);
628 let x1 = (r.x + r.w as i32).clamp(0, screen_w as i32);
629 let y1 = (r.y + r.h as i32).clamp(0, screen_h as i32);
630
631 Some(Rect::new(x0, y0, (x1 - x0) as u32, (y1 - y0) as u32))
632 }
633
634 pub fn add_window(&mut self, mut win: Window) {
635 let app_name = win.app.name();
636 let mut existing_idx = None;
637 for (i, w) in self.windows.iter().enumerate() {
638 if w.app.name() == app_name {
639 existing_idx = Some(i);
640 break;
641 }
642 }
643
644 if let Some(idx) = existing_idx {
645 self.windows[idx].is_minimized = false;
647 let target_ws = self.windows[idx].workspace;
648 self.active_workspace = target_ws;
649 self.activate_window(idx);
650 self.dirty = true;
651 return;
652 }
653
654 win.workspace = self.active_workspace;
656 let name = win.title.clone();
657 win.spawn_tab_thread(0);
659 self.add_dirty_rect(Rect::new(win.x, win.y, win.width, win.height));
660 self.windows.push(win);
661 self.dirty = true;
662 self.terminal_focused = false;
663 let (used, total) = crate::kernel::allocator::get_heap_stats();
664 crate::debug!(
665 "[WM] open '{}' -> {} windows | heap {}/{} KB",
666 name,
667 self.windows.len(),
668 used / 1024,
669 total / 1024
670 );
671 }
672
673 pub fn remove_active_window(&mut self) {
674 if !self.windows.is_empty() {
675 let name = self
676 .windows
677 .last()
678 .map(|w| w.title.clone())
679 .unwrap_or_else(|| alloc::string::String::from("?"));
680 let info = self.windows.last().map(|w| {
682 (
683 Rect::new(w.x, w.y, w.width, w.height),
684 alloc::string::String::from(w.app.name()),
685 )
686 });
687 if let Some((rect, app_name)) = info {
688 self.add_dirty_rect(rect);
689 crate::kernel::watchdog::clear(&app_name);
690 }
691 self.windows.pop();
692 self.dirty = true;
693 let (used, total) = crate::kernel::allocator::get_heap_stats();
694 crate::debug!(
695 "[WM] close '{}' -> {} windows | heap {}/{} KB",
696 name,
697 self.windows.len(),
698 used / 1024,
699 total / 1024
700 );
701 }
702 }
703
704 pub fn activate_window(&mut self, idx: usize) {
706 if idx < self.windows.len() {
709 let target_ws = self.windows[idx].workspace;
710 self.active_workspace = target_ws;
711 self.terminal_focused = false;
712 self.slide_out = false;
715 let win = self.windows.remove(idx);
716 let rect = Rect::new(win.x, win.y, win.width, win.height);
717 self.add_dirty_rect(rect);
718 self.windows.push(win);
719 self.add_dirty_rect(rect);
720 self.dirty = true;
721 }
722 }
723
724 pub fn any_window_covers_rect(&self, x0: i32, y0: i32, x1: i32, y1: i32) -> bool {
732 if self.active_workspace == 1 && self.slide_out {
734 return false;
735 }
736 for win in self.windows.iter() {
737 if win.workspace != self.active_workspace || win.is_minimized {
738 continue;
739 }
740 let wx1 = win.x + win.width as i32;
741 let wy1 = win.y + win.height as i32;
742 if win.x < x1 && wx1 > x0 && win.y < y1 && wy1 > y0 {
744 return true;
745 }
746 }
747 false
748 }
749
750 pub fn draw_all(&mut self, screen: &Screen) {
751 self.dirty = false;
752 let config = crate::kernel::config::get_config();
753 let base_frame_color = Color(config.theme.non_active_window_border);
754 let title_bar_color = Color(config.theme.window_title_bar);
755 let title_fg_color = Color(config.theme.window_title_fg);
756 let active_frame_color = Color(config.theme.active_window_border);
757
758 let num_windows = self.windows.len();
759 for (i, win) in self.windows.iter_mut().enumerate() {
760 if win.workspace != self.active_workspace || win.is_minimized {
762 continue;
763 }
764 let is_active = i == num_windows.saturating_sub(1) && !self.terminal_focused;
765 let current_frame_color = if is_active {
766 active_frame_color
767 } else {
768 base_frame_color
769 };
770 let border_w = 2; let w = win.width;
773 let h = win.height;
774
775 if let Some(ref mut win_buf) = win.buffer {
776 screen.set_render_target(win_buf.as_mut_ptr(), w, h);
778
779 let mut tab_list = win.app.tabs();
781 let active_idx = win.app.active_tab();
782 let is_terminal = win.app.name() == "Terminal";
783 if is_terminal && tab_list.is_empty() {
784 tab_list.push(alloc::string::String::from("Shell"));
785 }
786 let has_tabs = !tab_list.is_empty();
787 let tab_w = if has_tabs { 100 } else { 0 };
788 let app_x = tab_w;
789 let app_y = 31u32;
790 let app_w = w.saturating_sub(tab_w);
791 let _app_h = h.saturating_sub(31);
792
793 let app_name = win.app.name();
794
795 {
799 let f = crate::kernel::draw::FRAME_NO
800 .fetch_add(1, Ordering::Relaxed)
801 .wrapping_add(1);
802 if f.is_multiple_of(64) {
803 crate::os_lib::web_engine::fetch_limit::idle_line();
812 let ht = crate::kernel::draw::HERO_TEXT_FRAME.load(Ordering::Relaxed);
813 crate::warn!(
814 "[WM][FRAME] present_frame={} last_hero_text_frame={}",
815 f,
816 if ht == u32::MAX { 0 } else { ht }
817 );
818 }
819 }
820
821 if win.app.is_proxy() {
822 if let Some(shared_buf_arc) = win.app.get_shared_buffer() {
827 if let Some(shared_buf) = shared_buf_arc.try_lock() {
828 let copy_len = (w * h) as usize;
830 if shared_buf.len() >= copy_len && win_buf.len() >= copy_len {
831 unsafe {
832 core::ptr::copy_nonoverlapping(
833 shared_buf.as_ptr(),
834 win_buf.as_mut_ptr(),
835 copy_len,
836 );
837 }
838 } else {
839 static CSKIP: core::sync::atomic::AtomicU32 =
844 core::sync::atomic::AtomicU32::new(0);
845 let n = CSKIP.fetch_add(1, Ordering::Relaxed);
846 if n < 4 || n.is_multiple_of(128) {
847 crate::warn!(
848 "[WM][DIAG] compositor copy SKIPPED: shared={} win_buf={} need={} (w={} h={}) count={}",
849 shared_buf.len(),
850 win_buf.len(),
851 copy_len,
852 w,
853 h,
854 n + 1
855 );
856 }
857 }
858 } else {
859 static TRYLOCK_FAIL: core::sync::atomic::AtomicU32 =
868 core::sync::atomic::AtomicU32::new(0);
869 let n = TRYLOCK_FAIL.fetch_add(1, Ordering::Relaxed);
870 if n < 4 || n.is_multiple_of(128) {
871 crate::warn!(
872 "[WM][DIAG] compositor try_lock FAILED (using stale frame) count={}",
873 n + 1
874 );
875 }
876 self.dirty = true;
877 }
878 }
879 } else {
880 screen.boxfill(0, 0, w, h, Color(config.theme.app_bg)); screen.set_clip(app_x, app_y, w, h);
885 if crate::kernel::watchdog::is_not_responding(app_name) {
886 screen.boxfill(app_x, app_y, w, h, Color(config.theme.ui_overlay_bg));
887 screen.draw_string_vector(
888 app_x + 16,
889 app_y + 16,
890 "Application Not Responding",
891 Color(config.theme.error_fg),
892 16,
893 );
894 screen.draw_string_vector(
895 app_x + 16,
896 app_y + 40,
897 "Close the window (Alt+W) to recover.",
898 Color(config.theme.terminal_fg),
899 13,
900 );
901 } else {
902 crate::kernel::watchdog::enter(app_name);
903 win.app.draw(screen, app_x, app_y, app_w, _app_h);
904 crate::kernel::watchdog::exit();
905 }
906 screen.clear_clip();
907 }
908
909 screen.boxfill(0, 0, w, 30, title_bar_color);
912
913 let shadow_color = Color(0xFF111217);
915 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);
928
929 static ICON_CLOSE: &[u8] =
931 include_bytes!("../../icons/ic_fluent_dismiss_circle_20_regular.svg");
932 static ICON_FS: &[u8] =
933 include_bytes!("../../icons/ic_fluent_full_screen_maximize_20_regular.svg");
934 static ICON_MAX: &[u8] =
935 include_bytes!("../../icons/ic_fluent_maximize_20_regular.svg");
936 static ICON_NORM: &[u8] =
937 include_bytes!("../../icons/ic_fluent_full_screen_minimize_20_regular.svg");
938 static ICON_MIN: &[u8] =
939 include_bytes!("../../icons/ic_fluent_subtract_circle_20_regular.svg");
940
941 let btn_w = 20u32;
942 let _btn_h = 20u32;
943 let btn_y = 5u32;
944 let gap = 5i32;
945
946 let bx_close = w - 10 - btn_w;
948 screen.draw_svg_icon(bx_close, btn_y, ICON_CLOSE, btn_w, Color(config.theme.win_btn_close));
949
950 let bx_fs = (bx_close as i32 - btn_w as i32 - gap) as u32;
952 screen.draw_svg_icon(bx_fs, btn_y, ICON_FS, btn_w, Color(config.theme.win_btn_fullscreen));
953
954 let bx_max = (bx_fs as i32 - btn_w as i32 - gap) as u32;
956 screen.draw_svg_icon(bx_max, btn_y, ICON_MAX, btn_w, Color(config.theme.win_btn_maximize));
957
958 let bx_norm = (bx_max as i32 - btn_w as i32 - gap) as u32;
960 screen.draw_svg_icon(bx_norm, btn_y, ICON_NORM, btn_w, Color(config.theme.win_btn_restore));
961
962 let bx_min = (bx_norm as i32 - btn_w as i32 - gap) as u32;
964 screen.draw_svg_icon(bx_min, btn_y, ICON_MIN, btn_w, Color(config.theme.win_btn_minimize));
965
966 if has_tabs {
968 screen.boxfill(0, app_y, tab_w, h, Color(config.theme.tab_area_bg));
969 screen.boxfill(tab_w - 1, app_y, tab_w, h, Color(config.theme.tab_area_border)); screen.draw_string_vector(10, app_y + 10, "TABS", Color(config.theme.tab_inactive_fg), 14);
972
973 screen.boxfill(75, app_y + 8, 92, app_y + 25, Color(config.theme.tab_area_border));
975 screen.draw_string_vector(80, app_y + 10, "+", Color(config.theme.tab_active_fg), 14);
976
977 screen.boxfill(10, app_y + 30, 90, app_y + 31, Color(config.theme.tab_area_border));
978
979 let mut tab_y = app_y + 40;
980 for (idx, tab_title) in tab_list.iter().enumerate() {
981 if tab_y + 42 > h {
982 break;
983 }
984
985 let is_active_tab = idx == active_idx;
986 let bg_color = if is_active_tab {
987 Color(config.theme.tab_active_bg)
988 } else {
989 Color(config.theme.tab_area_bg)
990 };
991 let fg_color = if is_active_tab {
992 Color(config.theme.tab_active_fg)
993 } else {
994 Color(config.theme.tab_inactive_fg)
995 };
996
997 screen.boxfill(5, tab_y, 95, tab_y + 36, bg_color);
998
999 let label = alloc::format!("{})", idx + 1);
1000 screen.draw_string_vector(10, tab_y + 12, &label, fg_color, 12);
1001
1002 let mut lines = alloc::vec![];
1004 if tab_title.contains('\n') {
1005 for (count, part) in tab_title.split('\n').enumerate() {
1006 if count >= 2 {
1007 break;
1008 }
1009 lines.push(alloc::string::String::from(part));
1010 }
1011 } else {
1012 let chars: alloc::vec::Vec<char> = tab_title.chars().collect();
1013 if chars.len() <= 6 {
1014 lines.push(tab_title.clone());
1015 } else {
1016 let first: alloc::string::String = chars.iter().take(6).collect();
1017 let mut second: alloc::string::String =
1018 chars.iter().skip(6).collect();
1019 if second.chars().count() > 6 {
1020 let mut sec_trunc: alloc::string::String =
1021 second.chars().take(4).collect();
1022 sec_trunc.push_str("..");
1023 second = sec_trunc;
1024 }
1025 lines.push(first);
1026 lines.push(second);
1027 }
1028 }
1029
1030 if lines.len() == 1 {
1031 screen.draw_string_vector(22, tab_y + 12, &lines[0], fg_color, 10);
1032 } else if lines.len() >= 2 {
1033 screen.draw_string_vector(22, tab_y + 5, &lines[0], fg_color, 10);
1034 screen.draw_string_vector(22, tab_y + 19, &lines[1], fg_color, 10);
1035 }
1036
1037 screen.boxfill(78, tab_y + 10, 92, tab_y + 24, Color(config.theme.tab_area_border));
1039 screen.draw_string_vector(82, tab_y + 12, "x", Color(config.theme.win_btn_close), 10);
1040
1041 tab_y += 42;
1042 }
1043 }
1044
1045 screen.reset_render_target();
1047
1048 let screen_w = screen.width;
1050 let screen_h = screen.height;
1051 let main_ptr = if screen.use_back_buffer.get() && !screen.back_buffer.is_null() {
1052 screen.back_buffer
1053 } else {
1054 screen.vram
1055 };
1056
1057 let win_x = if self.active_workspace == 1 && self.slide_out {
1058 win.x + screen.width as i32
1059 } else {
1060 win.x
1061 };
1062 let win_y = win.y;
1063
1064 let src_x0 = if win_x < 0 { (-win_x) as u32 } else { 0 };
1066 let src_y0 = if win_y < 0 { (-win_y) as u32 } else { 0 };
1067 let dst_x0 = win_x.max(0) as u32;
1068 let dst_y0 = win_y.max(0) as u32;
1069 let copy_w = w.saturating_sub(src_x0).min(screen_w.saturating_sub(dst_x0));
1070 let copy_h = h.saturating_sub(src_y0).min(screen_h.saturating_sub(dst_y0));
1071
1072 if copy_w > 0 && copy_h > 0 {
1073 unsafe {
1074 for row in 0..copy_h {
1075 let src_ptr = win_buf.as_ptr().add(((src_y0 + row) * w + src_x0) as usize);
1076 let dst_ptr = main_ptr.add(((dst_y0 + row) * screen_w + dst_x0) as usize);
1077 core::ptr::copy_nonoverlapping(src_ptr, dst_ptr, copy_w as usize);
1078 }
1079 }
1080 }
1081 }
1082 }
1083
1084 if self.alt_hold_mode {
1086 let overlay_w = 200u32;
1087 let overlay_h = 100u32;
1088 let overlay_x = (screen.width - overlay_w) / 2;
1089 let overlay_y = (screen.height - overlay_h) / 2;
1090
1091 let overlay_bg_semi = Color(0xEE000000 | (config.theme.ui_overlay_bg & 0x00FFFFFF));
1092 screen.boxfill(
1093 overlay_x,
1094 overlay_y,
1095 overlay_x + overlay_w,
1096 overlay_y + overlay_h,
1097 overlay_bg_semi,
1098 );
1099 screen.boxfill(
1100 overlay_x,
1101 overlay_y,
1102 overlay_x + overlay_w,
1103 overlay_y + 2,
1104 Color(config.theme.ui_overlay_accent),
1105 );
1106 screen.draw_string_vector(
1107 overlay_x + 15,
1108 overlay_y + 10,
1109 "STATELESS JUMP",
1110 Color(config.theme.terminal_fg),
1111 14,
1112 );
1113
1114 let val_str = if self.alt_input_buffer.is_empty() {
1115 alloc::string::String::from("_")
1116 } else {
1117 alloc::format!("{}_", self.alt_input_buffer)
1118 };
1119 screen.draw_string_vector(
1120 overlay_x + 80,
1121 overlay_y + 40,
1122 &val_str,
1123 Color(config.theme.success_fg),
1124 32,
1125 );
1126 }
1127 }
1128
1129 pub fn handle_mouse(
1131 &mut self,
1132 mx: i32,
1133 my: i32,
1134 btn_left: bool,
1135 btn_right: bool,
1136 wheel: i32,
1137 screen: &Screen,
1138 ) -> bool {
1139 let mut handled = false;
1140
1141 let dx = mx - self.last_mouse_x;
1142 let dy = my - self.last_mouse_y;
1143
1144 let is_dragging = if let Some(win) = self.windows.last() {
1147 win.is_dragging && win.workspace == self.active_workspace && !win.is_minimized
1148 } else {
1149 false
1150 };
1151
1152 if is_dragging {
1153 if btn_left {
1154 let old_rect = if let Some(win) = self.windows.last() {
1156 Rect::new(win.x, win.y, win.width, win.height)
1157 } else {
1158 Rect::new(0, 0, 0, 0)
1159 };
1160 self.add_dirty_rect(old_rect);
1161
1162 if let Some(win) = self.windows.last_mut() {
1163 win.x += dx;
1164 win.y += dy;
1165 }
1166 self.dirty = true;
1167
1168 let new_rect = if let Some(win) = self.windows.last() {
1170 Rect::new(win.x, win.y, win.width, win.height)
1171 } else {
1172 Rect::new(0, 0, 0, 0)
1173 };
1174 self.add_dirty_rect(new_rect);
1175
1176 self.last_mouse_x = mx;
1177 self.last_mouse_y = my;
1178 self.last_mouse_btn = btn_left;
1179 return true;
1180 } else {
1181 if let Some(win) = self.windows.last_mut() {
1182 win.is_dragging = false;
1183 }
1184 }
1185 }
1186
1187 let btn_just_pressed = btn_left && !self.last_mouse_btn;
1189 let mut target_index = None;
1190 let mut close_clicked = false;
1191 let mut fs_clicked = false;
1192 let mut max_clicked = false;
1193 let mut norm_clicked = false;
1194 let mut min_clicked = false;
1195
1196 for (i, win) in self.windows.iter_mut().enumerate().rev() {
1197 if win.workspace != self.active_workspace || win.is_minimized {
1199 continue;
1200 }
1201 let win_x = if self.active_workspace == 1 && self.slide_out {
1202 win.x + 2000 } else {
1204 win.x
1205 };
1206 if mx >= win_x
1207 && mx <= win_x + win.width as i32
1208 && my >= win.y
1209 && my <= win.y + win.height as i32
1210 {
1211 target_index = Some(i);
1212
1213 if my < win.y + 30 {
1215 let btn_w = 20;
1216 let gap = 5;
1217 let bx_close = win_x + win.width as i32 - 10 - btn_w;
1218 let bx_fs = bx_close - btn_w - gap;
1219 let bx_max = bx_fs - btn_w - gap;
1220 let bx_norm = bx_max - btn_w - gap;
1221 let bx_min = bx_norm - btn_w - gap;
1222
1223 if btn_just_pressed {
1224 if mx >= bx_close && mx <= bx_close + btn_w {
1225 close_clicked = true;
1226 } else if mx >= bx_fs && mx <= bx_fs + btn_w {
1227 fs_clicked = true;
1228 } else if mx >= bx_max && mx <= bx_max + btn_w {
1229 max_clicked = true;
1230 } else if mx >= bx_norm && mx <= bx_norm + btn_w {
1231 norm_clicked = true;
1232 } else if mx >= bx_min && mx <= bx_min + btn_w {
1233 min_clicked = true;
1234 } else {
1235 win.is_dragging = true;
1237 win.drag_offset_x = mx - win.x;
1238 win.drag_offset_y = my - win.y;
1239 }
1240 }
1241 } else {
1242 let mut tab_list = win.app.tabs();
1243 let is_terminal = win.app.name() == "Terminal";
1244 if is_terminal && tab_list.is_empty() {
1245 tab_list.push(alloc::string::String::from("Shell"));
1246 }
1247 let has_tabs = !tab_list.is_empty();
1248 let tab_w = if has_tabs { 100 } else { 0 };
1249
1250 let local_x = mx - win_x;
1251 let local_y = my - (win.y + 31);
1252
1253 if has_tabs && local_x < tab_w {
1254 if btn_just_pressed {
1256 let tab_y_click = local_y as u32;
1257 if (8..=25).contains(&tab_y_click) && (75..=92).contains(&local_x) {
1258 let new_tab_idx = win.app.tabs().len();
1260 win.app.add_tab();
1261 win.spawn_tab_thread(new_tab_idx);
1262 self.dirty = true;
1263 crate::kernel::audio::play_system_se(
1264 crate::kernel::audio::SeType::Apply,
1265 );
1266 } else if tab_y_click >= 40 {
1267 let clicked_tab = ((tab_y_click - 40) / 42) as usize;
1268 if clicked_tab < tab_list.len() {
1269 let relative_y = (tab_y_click - 40) % 42;
1270 if relative_y <= 36 {
1271 if (78..=92).contains(&local_x)
1272 && (10..=24).contains(&relative_y)
1273 {
1274 win.clear_tab_thread(clicked_tab);
1276 let should_close = win.app.close_tab(clicked_tab);
1277 if should_close {
1278 close_clicked = true;
1279 } else {
1280 crate::kernel::audio::play_system_se(
1281 crate::kernel::audio::SeType::Apply,
1282 );
1283 }
1284 } else {
1285 win.app.switch_tab(clicked_tab);
1286 }
1287 self.dirty = true;
1288 }
1289 }
1290 }
1291 }
1292 } else {
1293 win.app
1295 .on_mouse(local_x - tab_w, local_y, btn_left, btn_right, wheel);
1296 if btn_just_pressed || wheel != 0 || (btn_left != self.last_mouse_btn) {
1298 self.dirty = true;
1299 }
1300 }
1301 }
1302
1303 handled = true;
1304 break;
1305 }
1306 }
1307
1308 if let Some(idx) = target_index {
1310 if btn_just_pressed {
1311 self.activate_window(idx);
1312 }
1313 if close_clicked {
1314 if let Some(w) = self.windows.last() {
1315 self.add_dirty_rect(Rect::new(w.x, w.y, w.width, w.height));
1316 }
1317 self.windows.pop();
1318 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1319 self.dirty = true;
1320 if self.windows.is_empty() {
1321 self.terminal_focused = true;
1322 }
1323 } else if fs_clicked {
1324 let mut target_ws = None;
1326 for ws in 2..=8 {
1327 let mut used = false;
1328 for w in &self.windows {
1329 if w.workspace == ws && !w.is_minimized {
1330 used = true;
1331 break;
1332 }
1333 }
1334 if !used {
1335 target_ws = Some(ws);
1336 break;
1337 }
1338 }
1339 let target_ws = target_ws.unwrap_or(self.active_workspace);
1340
1341 let old_rect = self
1342 .windows
1343 .last()
1344 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1345 if let Some(r) = old_rect {
1346 self.add_dirty_rect(r);
1347 }
1348
1349 if let Some(win) = self.windows.last_mut() {
1350 win.is_fullscreen = true;
1351 win.saved_x = win.x;
1352 win.saved_y = win.y;
1353 win.saved_w = win.width;
1354 win.saved_h = win.height;
1355 win.saved_ws = win.workspace;
1356 let sw = screen.width;
1357 let sh = screen.height;
1358 win.x = 0;
1359 win.y = 0;
1360 win.resize(sw, sh);
1361 win.workspace = target_ws;
1362 self.active_workspace = target_ws;
1363 self.dirty = true;
1364 }
1365 let sw = screen.width;
1366 let sh = screen.height;
1367 self.add_dirty_rect(Rect::new(0, 0, sw, sh));
1368 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1369 } else if max_clicked {
1370 let old_rect = self
1371 .windows
1372 .last()
1373 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1374 if let Some(r) = old_rect {
1375 self.add_dirty_rect(r);
1376 }
1377
1378 if let Some(win) = self.windows.last_mut() {
1379 win.is_fullscreen = false;
1380 win.saved_x = win.x;
1381 win.saved_y = win.y;
1382 win.saved_w = win.width;
1383 win.saved_h = win.height;
1384 let sh = screen.height;
1385 let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1386 let max_w = screen.width.saturating_sub(sysmon_w);
1387 win.x = 0;
1388 win.y = 0;
1389 win.resize(max_w, sh);
1390 self.dirty = true;
1391 }
1392
1393 let new_rect = self
1394 .windows
1395 .last()
1396 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1397 if let Some(r) = new_rect {
1398 self.add_dirty_rect(r);
1399 }
1400 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1401 } else if norm_clicked {
1402 let old_rect = self
1403 .windows
1404 .last()
1405 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1406 if let Some(r) = old_rect {
1407 self.add_dirty_rect(r);
1408 }
1409
1410 if let Some(win) = self.windows.last_mut() {
1411 win.is_fullscreen = false;
1412 win.x = win.saved_x;
1413 win.y = win.saved_y;
1414 let (rw, rh) = (win.saved_w, win.saved_h);
1415
1416 self.active_workspace = win.saved_ws;
1417 win.workspace = win.saved_ws;
1418
1419 win.resize(rw, rh);
1420 self.dirty = true;
1421 }
1422
1423 let new_rect = self
1424 .windows
1425 .last()
1426 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1427 if let Some(r) = new_rect {
1428 self.add_dirty_rect(r);
1429 }
1430 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1431 } else if min_clicked {
1432 let old_rect = self
1433 .windows
1434 .last()
1435 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1436 if let Some(r) = old_rect {
1437 self.add_dirty_rect(r);
1438 }
1439
1440 if let Some(win) = self.windows.last_mut() {
1441 win.is_minimized = true;
1442 }
1443 self.dirty = true;
1444 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1445 }
1446 } else if btn_just_pressed {
1447 let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1450 let sysmon_x = screen.width - sysmon_w;
1451 if mx < sysmon_x as i32 {
1452 self.terminal_focused = true;
1453 }
1454 }
1455
1456 self.last_mouse_x = mx;
1457 self.last_mouse_y = my;
1458 self.last_mouse_btn = btn_left;
1459
1460 handled
1461 }
1462
1463 pub fn handle_key(
1465 &mut self,
1466 keycode: u8,
1467 pressed: bool,
1468 ascii: Option<char>,
1469 alt_pressed: bool,
1470 screen: &Screen,
1471 ) -> bool {
1472 if !pressed {
1473 return false;
1474 }
1475
1476 if keycode == 0x3B && !alt_pressed {
1480 if self.terminal_focused && !self.slide_out {
1481 self.slide_out = true;
1483 self.dirty = true;
1484 } else {
1485 self.terminal_focused = true;
1487 self.slide_out = false;
1488 self.dirty = true;
1489 }
1490 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1491 return true;
1492 }
1493
1494 if alt_pressed && (0x3B..=0x42).contains(&keycode) {
1496 let target_ws = if keycode == 0x3B {
1497 1
1498 } else {
1499 (keycode - 0x3C) + 2
1500 };
1501 self.active_workspace = target_ws;
1502 self.dirty = true;
1503
1504 let mut last_win_idx = None;
1506 for (i, win) in self.windows.iter().enumerate() {
1507 if win.workspace == target_ws && !win.is_minimized {
1508 last_win_idx = Some(i);
1509 }
1510 }
1511 if let Some(idx) = last_win_idx {
1512 self.activate_window(idx);
1513 } else {
1514 self.terminal_focused = true;
1515 }
1516 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1517 return true;
1518 }
1519
1520 if alt_pressed && keycode == 0x43 {
1522 let active_is_matching = if let Some(win) = self.windows.last() {
1523 win.workspace == self.active_workspace && !win.is_minimized
1524 } else {
1525 false
1526 };
1527
1528 if active_is_matching {
1529 let old_rect = self
1530 .windows
1531 .last()
1532 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1533 if let Some(r) = old_rect {
1534 self.add_dirty_rect(r);
1535 }
1536
1537 if let Some(win) = self.windows.last_mut() {
1538 win.is_minimized = true;
1539 }
1540 self.dirty = true;
1541
1542 let mut found_next = false;
1544 let len = self.windows.len();
1545 if len > 1 {
1546 for i in (0..len - 1).rev() {
1547 let is_candidate = {
1548 let w = &self.windows[i];
1549 w.workspace == self.active_workspace && !w.is_minimized
1550 };
1551 if is_candidate {
1552 self.activate_window(i);
1553 found_next = true;
1554 break;
1555 }
1556 }
1557 }
1558 if !found_next {
1559 self.terminal_focused = true;
1560 }
1561 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1562 }
1564 return true;
1565 }
1566
1567 if alt_pressed && keycode == 0x44 {
1569 let matches_ws = if let Some(win) = self.windows.last() {
1570 win.workspace == self.active_workspace
1571 } else {
1572 false
1573 };
1574
1575 if matches_ws {
1576 let old_rect = self
1577 .windows
1578 .last()
1579 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1580 if let Some(r) = old_rect {
1581 self.add_dirty_rect(r);
1582 }
1583
1584 if let Some(win) = self.windows.last_mut() {
1585 win.is_fullscreen = false;
1586 win.x = win.saved_x;
1587 win.y = win.saved_y;
1588 let (rw, rh) = (win.saved_w, win.saved_h);
1589 win.resize(rw, rh);
1590 }
1591 self.dirty = true;
1592
1593 let new_rect = self
1594 .windows
1595 .last()
1596 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1597 if let Some(r) = new_rect {
1598 self.add_dirty_rect(r);
1599 }
1600 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1601 }
1602 return true;
1603 }
1604
1605 if alt_pressed && keycode == 0x57 {
1607 let matches_ws = if let Some(win) = self.windows.last() {
1608 win.workspace == self.active_workspace
1609 } else {
1610 false
1611 };
1612
1613 if matches_ws {
1614 let old_rect = self
1615 .windows
1616 .last()
1617 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1618 if let Some(r) = old_rect {
1619 self.add_dirty_rect(r);
1620 }
1621
1622 if let Some(win) = self.windows.last_mut() {
1623 if !win.is_fullscreen {
1624 win.saved_x = win.x;
1625 win.saved_y = win.y;
1626 win.saved_w = win.width;
1627 win.saved_h = win.height;
1628 }
1629
1630 let sysmon_w = if screen.width >= 1920 { 518 } else { 300 };
1632 let avail_w = screen.width.saturating_sub(100 + sysmon_w);
1633 win.x = 100;
1634 win.y = 0;
1635 let sh = screen.height;
1636 win.resize(avail_w, sh);
1637 }
1638 self.dirty = true;
1639
1640 let new_rect = self
1641 .windows
1642 .last()
1643 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1644 if let Some(r) = new_rect {
1645 self.add_dirty_rect(r);
1646 }
1647 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1648 }
1649 return true;
1650 }
1651
1652 if alt_pressed && keycode == 0x58 {
1654 let mut target_ws = None;
1656 for ws in 2..=8 {
1657 let mut occupied = false;
1658 for w in &self.windows {
1659 if w.workspace == ws && !w.is_minimized {
1660 occupied = true;
1661 break;
1662 }
1663 }
1664 if !occupied {
1665 target_ws = Some(ws);
1666 break;
1667 }
1668 }
1669
1670 if let Some(ws) = target_ws {
1671 let old_rect = self
1672 .windows
1673 .last()
1674 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1675 if let Some(r) = old_rect {
1676 self.add_dirty_rect(r);
1677 }
1678
1679 if let Some(win) = self.windows.last_mut() {
1680 if !win.is_fullscreen {
1681 win.saved_x = win.x;
1682 win.saved_y = win.y;
1683 win.saved_w = win.width;
1684 win.saved_h = win.height;
1685 }
1686
1687 win.is_fullscreen = true;
1688 win.workspace = ws;
1689 win.x = 0;
1690 win.y = 0;
1691 let (sw, sh) = (screen.width, screen.height);
1692 win.resize(sw, sh);
1693 }
1694 self.active_workspace = ws;
1695 self.dirty = true;
1696 let (sw, sh) = (screen.width, screen.height);
1697 self.add_dirty_rect(Rect::new(0, 0, sw, sh));
1698 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Apply);
1699 } else {
1700 crate::kernel::audio::play_system_se(crate::kernel::audio::SeType::Close);
1702 }
1703 return true;
1704 }
1705
1706 if self.terminal_focused {
1708 return false;
1709 }
1710 let is_active = if let Some(win) = self.windows.last() {
1711 win.workspace == self.active_workspace && !win.is_minimized
1712 } else {
1713 false
1714 };
1715
1716 if is_active {
1717 if let Some(win) = self.windows.last_mut() {
1718 win.app.on_key(keycode, pressed, ascii);
1719 }
1720
1721 let rect = self
1722 .windows
1723 .last()
1724 .map(|win| Rect::new(win.x, win.y, win.width, win.height));
1725 if let Some(r) = rect {
1726 self.add_dirty_rect(r);
1727 }
1728 self.dirty = true;
1729 return true;
1730 }
1731 false
1732 }
1733
1734 pub fn needs_timer_redraw(&self) -> bool {
1736 for win in &self.windows {
1737 if win.workspace == self.active_workspace
1738 && !win.is_minimized
1739 && win.app.needs_timer_redraw()
1740 {
1741 return true;
1742 }
1743 }
1744 false
1745 }
1746}
1747
1748pub fn init() {
1749 unsafe {
1750 GLOBAL_WM = Some(ReentrantMutex::new(WindowManager::new()));
1751 }
1752}
1753
1754pub fn get_instance() -> ReentrantMutexGuard<'static, WindowManager> {
1755 unsafe {
1757 GLOBAL_WM
1758 .get_or_insert_with(|| ReentrantMutex::new(WindowManager::new()))
1759 .lock()
1760 }
1761}
1762
1763use core::cell::UnsafeCell;
1764use core::sync::atomic::{AtomicUsize, Ordering};
1765
1766pub struct ReentrantMutex<T> {
1767 inner: UnsafeCell<T>,
1768 owner: AtomicUsize,
1769 recursion: AtomicUsize,
1770}
1771
1772unsafe impl<T: Send> Send for ReentrantMutex<T> {}
1773unsafe impl<T: Send> Sync for ReentrantMutex<T> {}
1774
1775impl<T> ReentrantMutex<T> {
1776 pub const fn new(value: T) -> Self {
1777 Self {
1778 inner: UnsafeCell::new(value),
1779 owner: AtomicUsize::new(usize::MAX),
1780 recursion: AtomicUsize::new(0),
1781 }
1782 }
1783
1784 pub fn lock(&self) -> ReentrantMutexGuard<'_, T> {
1785 let cid = crate::kernel::scheduler::core_id();
1786 loop {
1787 if self.owner.load(Ordering::Relaxed) == cid {
1788 self.recursion.fetch_add(1, Ordering::Relaxed);
1789 return ReentrantMutexGuard { mutex: self };
1790 }
1791 if self
1792 .owner
1793 .compare_exchange_weak(usize::MAX, cid, Ordering::Acquire, Ordering::Relaxed)
1794 .is_ok()
1795 {
1796 self.recursion.store(1, Ordering::Relaxed);
1797 return ReentrantMutexGuard { mutex: self };
1798 }
1799 core::hint::spin_loop();
1800 }
1801 }
1802}
1803
1804pub struct ReentrantMutexGuard<'a, T> {
1805 mutex: &'a ReentrantMutex<T>,
1806}
1807
1808impl<'a, T> core::ops::Deref for ReentrantMutexGuard<'a, T> {
1809 type Target = T;
1810 fn deref(&self) -> &Self::Target {
1811 unsafe { &*self.mutex.inner.get() }
1812 }
1813}
1814
1815impl<'a, T> core::ops::DerefMut for ReentrantMutexGuard<'a, T> {
1816 fn deref_mut(&mut self) -> &mut Self::Target {
1817 unsafe { &mut *self.mutex.inner.get() }
1818 }
1819}
1820
1821impl<'a, T> Drop for ReentrantMutexGuard<'a, T> {
1822 fn drop(&mut self) {
1823 let rec = self.mutex.recursion.fetch_sub(1, Ordering::Relaxed);
1824 if rec == 1 {
1825 self.mutex.owner.store(usize::MAX, Ordering::Release);
1826 }
1827 }
1828}