brewery
notashelf /
47757576d06ff80a1aca716003ec0f877d7b55b7

beer

public

A terminal worth pouring time into

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/beer.git
crates/beer-protocols/src/mouse.rsRust106 lines3.3 KB
1
//! Mouse-event encoding: frame a button/motion report in the wire form the
2
//! application selected (legacy byte form, UTF-8, or SGR).
3
4
use std::io::Write as _;
5
6
use smithay_client_toolkit::seat::keyboard::Modifiers;
7
8
use crate::style::MouseEncoding;
9
10
/// Encode a mouse event for the application. `button` is the base code (0/1/2
11
/// for left/middle/right, 64/65 for wheel up/down); `col`/`row` are 0-based
12
/// screen cells; `motion` marks a drag/move report. Returns the bytes to send.
13
pub fn encode_mouse(
14
    encoding: MouseEncoding,
15
    button: u8,
16
    col: usize,
17
    row: usize,
18
    pressed: bool,
19
    motion: bool,
20
    mods: Modifiers,
21
) -> Vec<u8> {
22
    let mod_bits =
23
        (u8::from(mods.shift) << 2) | (u8::from(mods.alt) << 3) | (u8::from(mods.ctrl) << 4);
24
    let motion_bit = if motion { 32 } else { 0 };
25
    let mut out = Vec::new();
26
    match encoding {
27
        MouseEncoding::Sgr => {
28
            let cb = u32::from(button) + u32::from(motion_bit) + u32::from(mod_bits);
29
            let final_byte = if pressed { 'M' } else { 'm' };
30
            let _ = write!(out, "\x1b[<{cb};{};{}{final_byte}", col + 1, row + 1);
31
        }
32
        enc => {
33
            // Legacy form collapses every button release to code 3.
34
            let base = if !pressed && button <= 2 { 3 } else { button };
35
            let cb = 32 + base + motion_bit + mod_bits;
36
            out.extend_from_slice(b"\x1b[M");
37
            push_coord(&mut out, u32::from(cb), false);
38
            let utf8 = enc == MouseEncoding::Utf8;
39
            push_coord(&mut out, col as u32 + 33, utf8);
40
            push_coord(&mut out, row as u32 + 33, utf8);
41
        }
42
    }
43
    out
44
}
45
46
/// Push one legacy mouse coordinate byte, UTF-8 encoding values above 127 when
47
/// the extended (1005) mode is active, and clamping otherwise.
48
fn push_coord(out: &mut Vec<u8>, value: u32, utf8: bool) {
49
    if utf8 {
50
        let v = value.min(0x7ff);
51
        if v >= 0x80 {
52
            out.push(0xc0 | (v >> 6) as u8);
53
            out.push(0x80 | (v & 0x3f) as u8);
54
        } else {
55
            out.push(v as u8);
56
        }
57
    } else {
58
        out.push(value.min(255) as u8);
59
    }
60
}
61
62
#[cfg(test)]
63
mod tests {
64
    use super::*;
65
66
    const NONE: Modifiers = Modifiers {
67
        ctrl: false,
68
        alt: false,
69
        shift: false,
70
        caps_lock: false,
71
        logo: false,
72
        num_lock: false,
73
    };
74
75
    #[test]
76
    fn mouse_sgr_press_and_release() {
77
        // Left press at column 3, row 5 (0-based) -> SGR with 1-based coords.
78
        assert_eq!(
79
            encode_mouse(MouseEncoding::Sgr, 0, 3, 5, true, false, NONE),
80
            b"\x1b[<0;4;6M".to_vec()
81
        );
82
        assert_eq!(
83
            encode_mouse(MouseEncoding::Sgr, 0, 3, 5, false, false, NONE),
84
            b"\x1b[<0;4;6m".to_vec()
85
        );
86
    }
87
88
    #[test]
89
    fn mouse_legacy_release_collapses_to_three() {
90
        // Legacy release: button bits become 3; values offset by 32.
91
        assert_eq!(
92
            encode_mouse(MouseEncoding::X10, 0, 0, 0, false, false, NONE),
93
            vec![0x1b, b'[', b'M', 32 + 3, 33, 33]
94
        );
95
    }
96
97
    #[test]
98
    fn mouse_motion_and_modifiers_set_bits() {
99
        let mods = Modifiers { ctrl: true, ..NONE };
100
        // Drag with the left button and ctrl held: 0 + 32(motion) + 16(ctrl).
101
        assert_eq!(
102
            encode_mouse(MouseEncoding::Sgr, 0, 0, 0, true, true, mods),
103
            b"\x1b[<48;1;1M".to_vec()
104
        );
105
    }
106
}