summaryrefslogtreecommitdiffstats
path: root/rsnix/src/cursor.rs
blob: 18c9a1d923563c93aebcbf24153f0c033a9ef797 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// ---------------------------------------------------------------------------
// 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 <id>` 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<usize> {
    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
/// <id>`).  `None` stops forwarding.
pub fn set_forward(target: Option<usize>) {
    FORWARD_TARGET.store(target.unwrap_or(usize::MAX), Ordering::Relaxed);
}

fn forward_target() -> Option<usize> {
    match FORWARD_TARGET.load(Ordering::Relaxed) {
        usize::MAX => None,
        id => Some(id),
    }
}

fn task_main() {
    loop {
        while let Some(ev) = 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)
}