brewery

beer

public

A terminal worth pouring time into

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/beer.git
crates/beer-protocols/src/mouse.rsRust107 lines3.1 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 = (u8::from(mods.shift) << 2)
23
    | (u8::from(mods.alt) << 3)
24
    | (u8::from(mods.ctrl) << 4);
25
  let motion_bit = if motion { 32 } else { 0 };
26
  let mut out = Vec::new();
27
  match encoding {
28
    MouseEncoding::Sgr => {
29
      let cb = u32::from(button) + u32::from(motion_bit) + u32::from(mod_bits);
30
      let final_byte = if pressed { 'M' } else { 'm' };
31
      let _ = write!(out, "\x1b[<{cb};{};{}{final_byte}", col + 1, row + 1);
32
    },
33
    enc => {
34
      // Legacy form collapses every button release to code 3.
35
      let base = if !pressed && button <= 2 { 3 } else { button };
36
      let cb = 32 + base + motion_bit + mod_bits;
37
      out.extend_from_slice(b"\x1b[M");
38
      push_coord(&mut out, u32::from(cb), false);
39
      let utf8 = enc == MouseEncoding::Utf8;
40
      push_coord(&mut out, col as u32 + 33, utf8);
41
      push_coord(&mut out, row as u32 + 33, utf8);
42
    },
43
  }
44
  out
45
}
46
47
/// Push one legacy mouse coordinate byte, UTF-8 encoding values above 127 when
48
/// the extended (1005) mode is active, and clamping otherwise.
49
fn push_coord(out: &mut Vec<u8>, value: u32, utf8: bool) {
50
  if utf8 {
51
    let v = value.min(0x7FF);
52
    if v >= 0x80 {
53
      out.push(0xC0 | (v >> 6) as u8);
54
      out.push(0x80 | (v & 0x3F) as u8);
55
    } else {
56
      out.push(v as u8);
57
    }
58
  } else {
59
    out.push(value.min(255) as u8);
60
  }
61
}
62
63
#[cfg(test)]
64
mod tests {
65
  use super::*;
66
67
  const NONE: Modifiers = Modifiers {
68
    ctrl:      false,
69
    alt:       false,
70
    shift:     false,
71
    caps_lock: false,
72
    logo:      false,
73
    num_lock:  false,
74
  };
75
76
  #[test]
77
  fn mouse_sgr_press_and_release() {
78
    // Left press at column 3, row 5 (0-based) -> SGR with 1-based coords.
79
    assert_eq!(
80
      encode_mouse(MouseEncoding::Sgr, 0, 3, 5, true, false, NONE),
81
      b"\x1b[<0;4;6M".to_vec()
82
    );
83
    assert_eq!(
84
      encode_mouse(MouseEncoding::Sgr, 0, 3, 5, false, false, NONE),
85
      b"\x1b[<0;4;6m".to_vec()
86
    );
87
  }
88
89
  #[test]
90
  fn mouse_legacy_release_collapses_to_three() {
91
    // Legacy release: button bits become 3; values offset by 32.
92
    assert_eq!(
93
      encode_mouse(MouseEncoding::X10, 0, 0, 0, false, false, NONE),
94
      vec![0x1B, b'[', b'M', 32 + 3, 33, 33]
95
    );
96
  }
97
98
  #[test]
99
  fn mouse_motion_and_modifiers_set_bits() {
100
    let mods = Modifiers { ctrl: true, ..NONE };
101
    // Drag with the left button and ctrl held: 0 + 32(motion) + 16(ctrl).
102
    assert_eq!(
103
      encode_mouse(MouseEncoding::Sgr, 0, 0, 0, true, true, mods),
104
      b"\x1b[<48;1;1M".to_vec()
105
    );
106
  }
107
}