| 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 | } |