use alacritty_terminal::term::TermMode;
use winit::keyboard::ModifiersState;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Button {
Left,
Middle,
Right,
None,
WheelUp,
WheelDown,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Action {
Press,
Release,
Motion,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct MouseMode {
pub click: bool,
pub drag: bool,
pub motion: bool,
pub sgr: bool,
pub utf8: bool,
}
impl MouseMode {
pub fn from_term(mode: TermMode) -> Self {
Self {
click: mode.contains(TermMode::MOUSE_REPORT_CLICK),
drag: mode.contains(TermMode::MOUSE_DRAG),
motion: mode.contains(TermMode::MOUSE_MOTION),
sgr: mode.contains(TermMode::SGR_MOUSE),
utf8: mode.contains(TermMode::UTF8_MOUSE),
}
}
pub fn active(&self) -> bool {
self.click || self.drag || self.motion
}
pub fn reports_motion(&self, button_held: bool) -> bool {
self.motion || (self.drag && button_held)
}
}
pub fn encode(
button: Button,
action: Action,
mods: ModifiersState,
col: usize,
row: usize,
mode: MouseMode,
) -> Option<Vec<u8>> {
let wheel = matches!(button, Button::WheelUp | Button::WheelDown);
if wheel && action == Action::Release {
return None;
}
let mut code: u32 = match button {
Button::Left => 0,
Button::Middle => 1,
Button::Right => 2,
Button::None => 3,
Button::WheelUp => 64,
Button::WheelDown => 65,
};
if action == Action::Motion {
code += 32;
}
if mods.shift_key() {
code += 4;
}
if mods.alt_key() {
code += 8;
}
if mods.control_key() {
code += 16;
}
if mode.sgr {
let fin = if action == Action::Release { 'm' } else { 'M' };
return Some(format!("\x1b[<{code};{};{}{fin}", col + 1, row + 1).into_bytes());
}
if action == Action::Release {
code = (code & !0b11) | 3;
}
let mut out = b"\x1b[M".to_vec();
out.push(32 + code as u8);
for coord in [col, row] {
let value = 32 + 1 + coord as u32;
if mode.utf8 {
if value > 2047 {
return None;
}
let ch = char::from_u32(value)?;
let mut buf = [0u8; 4];
out.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
} else {
if value > 255 {
return None;
}
out.push(value as u8);
}
}
Some(out)
}
#[cfg(test)]
mod tests {
use super::*;
const SGR: MouseMode = MouseMode {
click: true,
drag: false,
motion: false,
sgr: true,
utf8: false,
};
const NORMAL: MouseMode = MouseMode {
click: true,
drag: false,
motion: false,
sgr: false,
utf8: false,
};
#[test]
fn sgr_press_and_release() {
let none = ModifiersState::empty();
assert_eq!(
encode(Button::Left, Action::Press, none, 0, 0, SGR),
Some(b"\x1b[<0;1;1M".to_vec())
);
assert_eq!(
encode(Button::Right, Action::Release, none, 9, 4, SGR),
Some(b"\x1b[<2;10;5m".to_vec())
);
}
#[test]
fn sgr_wheel_motion_and_modifiers() {
assert_eq!(
encode(
Button::WheelDown,
Action::Press,
ModifiersState::CONTROL,
2,
3,
SGR
),
Some(b"\x1b[<81;3;4M".to_vec())
);
assert_eq!(
encode(
Button::Left,
Action::Motion,
ModifiersState::empty(),
5,
6,
SGR
),
Some(b"\x1b[<32;6;7M".to_vec())
);
assert_eq!(
encode(
Button::WheelUp,
Action::Release,
ModifiersState::empty(),
0,
0,
SGR
),
None
);
}
#[test]
fn normal_encoding_offsets_by_32_and_hides_release_button() {
let none = ModifiersState::empty();
assert_eq!(
encode(Button::Middle, Action::Press, none, 0, 0, NORMAL),
Some(vec![0x1b, b'[', b'M', 33, 33, 33])
);
assert_eq!(
encode(Button::Middle, Action::Release, none, 1, 2, NORMAL),
Some(vec![0x1b, b'[', b'M', 35, 34, 35])
);
assert_eq!(
encode(Button::Left, Action::Press, none, 300, 0, NORMAL),
None
);
}
#[test]
fn utf8_mode_extends_coordinates() {
let mode = MouseMode {
utf8: true,
..NORMAL
};
let out = encode(
Button::Left,
Action::Press,
ModifiersState::empty(),
300,
0,
mode,
)
.unwrap();
assert_eq!(&out[..4], &[0x1b, b'[', b'M', 32]);
assert_eq!(std::str::from_utf8(&out[4..]).unwrap(), "\u{14d}!");
}
#[test]
fn motion_reporting_rules() {
let drag = MouseMode { drag: true, ..SGR };
assert!(drag.reports_motion(true));
assert!(!drag.reports_motion(false));
let any = MouseMode {
motion: true,
..SGR
};
assert!(any.reports_motion(false));
}
}