Skip to main content

strat9_kernel/shell/
mouse.rs

1//! Mouse input and rendering are independent of shell command execution.
2//! Dequeue only after acquiring the writer, so contention never loses an update.
3pub extern "C" fn mouse_task_main() -> ! {
4    // Mouse state
5    let mut prev_left = false;
6    let mut selecting = false;
7    let mut scrollbar_dragging = false;
8    let mut last_scrollbar_drag_tick = 0u64;
9    let mut pending_scrollbar_drag_y: Option<usize> = None;
10    let mut pending_selection_pos: Option<(usize, usize)> = None;
11    let mut pending_mouse_cursor: Option<(i32, i32)> = None;
12    let mut pending_scroll_delta: i32 = 0;
13    let mut mouse_x: i32 = 0;
14    let mut mouse_y: i32 = 0;
15
16    // Cap per-loop mouse work to avoid starving timer ticks when dragging.
17    const MAX_MOUSE_EVENTS_PER_TURN: usize = 16;
18    const SCROLLBAR_DRAG_MIN_TICKS: u64 = 1;
19    const MOUSE_RENDER_MIN_TICKS: u64 = 1;
20    let mut last_mouse_render_tick = 0u64;
21
22    loop {
23        let ticks = crate::process::scheduler::ticks();
24        if crate::hardware::usb::hid::is_available() {
25            crate::hardware::usb::hid::poll_all();
26        }
27        crate::arch::vga::try_with_writer(|writer| {
28            if crate::arch::mouse::MOUSE_READY.load(core::sync::atomic::Ordering::Relaxed) {
29                let mut scroll_delta: i32 = 0;
30                let mut left_pressed = false;
31                let mut left_released = false;
32                let mut left_held = false;
33                let mut had_events = false;
34
35                let mut mouse_events_seen = 0usize;
36                while let Some(ev) = crate::arch::mouse::read_event() {
37                    had_events = true;
38                    scroll_delta += ev.dz as i32;
39                    if ev.left && !prev_left {
40                        left_pressed = true;
41                    }
42                    if !ev.left && prev_left {
43                        left_released = true;
44                    }
45                    if ev.left && prev_left {
46                        left_held = true;
47                    }
48                    prev_left = ev.left;
49                    mouse_events_seen += 1;
50                    if mouse_events_seen >= MAX_MOUSE_EVENTS_PER_TURN {
51                        // Prevent monopolizing the CPU under heavy mouse input
52                        // (e.g. rapid drag on scrollbar). Remaining events are
53                        // processed on next loop iteration after yield_task().
54                        break;
55                    }
56                }
57
58                let has_pending_visual = pending_scroll_delta != 0
59                    || pending_scrollbar_drag_y.is_some()
60                    || pending_selection_pos.is_some()
61                    || pending_mouse_cursor.is_some();
62
63                if had_events || left_held || has_pending_visual {
64                    let (new_mx, new_my) = crate::arch::mouse::mouse_pos();
65                    let moved = new_mx != mouse_x || new_my != mouse_y;
66                    mouse_x = new_mx;
67                    mouse_y = new_my;
68                    if had_events {
69                        pending_scroll_delta += scroll_delta;
70                    }
71
72                    if left_pressed {
73                        let (mx, my) = (new_mx as usize, new_my as usize);
74                        if writer.scrollbar_hit_test(mx, my) {
75                            writer.scrollbar_click(mx, my);
76                            writer.clear_selection();
77                            selecting = false;
78                            scrollbar_dragging = true;
79                            pending_scrollbar_drag_y = None;
80                        } else {
81                            writer.start_selection(mx, my);
82                            selecting = true;
83                            scrollbar_dragging = false;
84                            pending_selection_pos = None;
85                        }
86                        last_mouse_render_tick = ticks;
87                    } else if left_held && scrollbar_dragging && moved {
88                        pending_scrollbar_drag_y = Some(new_my as usize);
89                    } else if left_held && selecting && moved {
90                        pending_selection_pos = Some((new_mx as usize, new_my as usize));
91                    }
92                    // A press and release may both occur in this bounded batch.
93                    // Complete the release even if a press was also observed.
94                    if left_released && !prev_left {
95                        if selecting {
96                            writer.end_selection();
97                            selecting = false;
98                            pending_selection_pos = None;
99                        }
100                        if scrollbar_dragging {
101                            if let Some(py) = pending_scrollbar_drag_y.take() {
102                                writer.scrollbar_drag_to(py);
103                            }
104                        }
105                        scrollbar_dragging = false;
106                        last_mouse_render_tick = ticks;
107                    }
108
109                    if moved {
110                        pending_mouse_cursor = Some((new_mx, new_my));
111                    }
112
113                    let render_due =
114                        ticks.saturating_sub(last_mouse_render_tick) >= MOUSE_RENDER_MIN_TICKS;
115                    let drag_due =
116                        ticks.saturating_sub(last_scrollbar_drag_tick) >= SCROLLBAR_DRAG_MIN_TICKS;
117                    let has_pending_visual = pending_scroll_delta != 0
118                        || pending_scrollbar_drag_y.is_some()
119                        || pending_selection_pos.is_some()
120                        || pending_mouse_cursor.is_some();
121                    if has_pending_visual && (render_due || left_pressed || left_released) {
122                        let mut rendered = false;
123
124                        // Inverted wheel: wheel up (dz>0) -> scroll down (history forward)
125                        if pending_scroll_delta > 0 {
126                            writer.scroll_view_down((pending_scroll_delta as usize) * 3);
127                            pending_scroll_delta = 0;
128                            rendered = true;
129                        } else if pending_scroll_delta < 0 {
130                            writer.scroll_view_up(((-pending_scroll_delta) as usize) * 3);
131                            pending_scroll_delta = 0;
132                            rendered = true;
133                        }
134
135                        if drag_due {
136                            if selecting {
137                                if let Some((sx, sy)) = pending_selection_pos.take() {
138                                    writer.update_selection(sx, sy);
139                                    rendered = true;
140                                }
141                            }
142                            if scrollbar_dragging {
143                                if let Some(py) = pending_scrollbar_drag_y.take() {
144                                    writer.scrollbar_drag_to(py);
145                                    last_scrollbar_drag_tick = ticks;
146                                    rendered = true;
147                                }
148                            }
149                        }
150
151                        if let Some((cx, cy)) = pending_mouse_cursor.take() {
152                            writer.update_mouse_cursor(cx, cy);
153                            rendered = true;
154                        }
155
156                        if rendered {
157                            last_mouse_render_tick = ticks;
158                        }
159                    }
160                }
161            }
162        });
163        crate::process::yield_task();
164    }
165}