// --------------------------------------------------------------------------- // cursor — the mouse cursor task. // // One background task owns the PS/2 mouse: it polls the packet ring every // 10 ms, moves the on-screen cursor (gop's display overlay), tracks the // button state, and optionally *forwards* every event to another task's // message queue (`mouse ` sets the target) — the input-routing half // of the future window manager. // // The cursor itself never touches the text surface: `gop::push_rect` // composites it on top of every display write, so text scrolling and // cursor movement cannot corrupt each other (see gop.rs). // --------------------------------------------------------------------------- use core::sync::atomic::{AtomicUsize, Ordering}; use crate::ps2::MouseEvent; use crate::sched::TaskMsg; /// Forward target task id, or `usize::MAX` = none. static FORWARD_TARGET: AtomicUsize = AtomicUsize::new(usize::MAX); /// Spawn the cursor task. Call after `sched::init`; only makes sense /// when the PS/2 mouse came up at boot. The cursor starts centred. pub fn spawn() -> Option { let (w, h) = crate::gop::pixel_dims(); crate::gop::cursor_set(w / 2 - 6, h / 2 - 8); // Draw it immediately: the vt diff only re-blits changed cells, so an // idle centre cell would never push (and thus never composite) it. crate::gop::cursor_redraw(); let id = crate::sched::spawn(task_main, "cursor")?; crate::serial_println!( "cursor: task {} — mouse poll + display overlay (forward target: none)", id ); Some(id) } /// Route decoded mouse events to a task's queue (the shell's `mouse /// `). `None` stops forwarding. pub fn set_forward(target: Option) { FORWARD_TARGET.store(target.unwrap_or(usize::MAX), Ordering::Relaxed); } fn forward_target() -> Option { match FORWARD_TARGET.load(Ordering::Relaxed) { usize::MAX => None, id => Some(id), } } fn task_main() { loop { while let Some(ev) = crate::usb::poll_mouse().or_else(crate::ps2::try_poll_mouse) { crate::gop::cursor_move(ev.dx, ev.dy); crate::gop::cursor_set_buttons(btn_bits(&ev)); if let Some(id) = forward_target() && crate::sched::send(id, TaskMsg::Mouse(ev)).is_err() { // The listener died mid-stream: stop routing (the // cursor keeps tracking) rather than spam errors. set_forward(None); crate::serial_println!("cursor: forward target task {} gone — stopped routing", id); } } crate::sched::sleep_ms(10); } } fn btn_bits(ev: &MouseEvent) -> u8 { (ev.left as u8) | ((ev.right as u8) << 1) | ((ev.middle as u8) << 2) }