use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use vt100::{MouseProtocolEncoding, MouseProtocolMode};
pub fn key_to_bytes(key: &KeyEvent) -> Vec<u8> {
match (key.code, key.modifiers) {
(KeyCode::Char(c), m) if m == KeyModifiers::NONE || m == KeyModifiers::SHIFT => {
let ch = if m == KeyModifiers::SHIFT {
c.to_ascii_uppercase()
} else {
c
};
ch.to_string().into_bytes()
}
(KeyCode::Char(c), KeyModifiers::CONTROL) => match ctrl_char(c) {
Some(byte) => vec![byte],
None => Vec::new(),
},
(KeyCode::Char(c), KeyModifiers::ALT) => {
let mut b = vec![0x1b];
b.extend(c.to_string().into_bytes());
b
}
(KeyCode::Enter, _) => vec![b'\r'],
(KeyCode::Backspace, _) => vec![0x7f],
(KeyCode::Esc, _) => vec![0x1b],
(KeyCode::Tab, _) => vec![b'\t'],
(KeyCode::BackTab, _) => b"\x1b[Z".to_vec(),
(KeyCode::Up, _) => b"\x1b[A".to_vec(),
(KeyCode::Down, _) => b"\x1b[B".to_vec(),
(KeyCode::Right, _) => b"\x1b[C".to_vec(),
(KeyCode::Left, _) => b"\x1b[D".to_vec(),
(KeyCode::Home, _) => b"\x1b[H".to_vec(),
(KeyCode::End, _) => b"\x1b[F".to_vec(),
(KeyCode::PageUp, _) => b"\x1b[5~".to_vec(),
(KeyCode::PageDown, _) => b"\x1b[6~".to_vec(),
(KeyCode::Delete, _) => b"\x1b[3~".to_vec(),
(KeyCode::Insert, _) => b"\x1b[2~".to_vec(),
(KeyCode::F(1), _) => b"\x1bOP".to_vec(),
(KeyCode::F(2), _) => b"\x1bOQ".to_vec(),
(KeyCode::F(3), _) => b"\x1bOR".to_vec(),
(KeyCode::F(4), _) => b"\x1bOS".to_vec(),
(KeyCode::F(n), _) if (5..=15).contains(&n) => {
let code: &[u8] = match n {
5 => b"15~",
6 => b"17~",
7 => b"18~",
8 => b"19~",
9 => b"20~",
10 => b"21~",
11 => b"23~",
12 => b"24~",
13 => b"25~",
14 => b"26~",
15 => b"28~",
_ => unreachable!(),
};
let mut b = vec![0x1b, b'['];
b.extend_from_slice(code);
b
}
_ => Vec::new(),
}
}
pub fn ctrl_char(c: char) -> Option<u8> {
let c = c.to_ascii_lowercase();
if c.is_ascii_lowercase() {
Some((c as u8) - b'a' + 1)
} else {
None
}
}
pub fn mouse_event_allowed(mode: MouseProtocolMode, kind: MouseEventKind) -> bool {
use MouseEventKind::*;
match mode {
MouseProtocolMode::None => false,
MouseProtocolMode::Press => matches!(kind, Down(_)),
MouseProtocolMode::PressRelease => matches!(kind, Down(_) | Up(_)),
MouseProtocolMode::ButtonMotion => matches!(kind, Down(_) | Up(_) | Drag(_)),
MouseProtocolMode::AnyMotion => true,
}
}
pub fn mouse_event_to_bytes(mouse: &MouseEvent, encoding: MouseProtocolEncoding) -> Vec<u8> {
let (mut code, release): (u8, bool) = match mouse.kind {
MouseEventKind::Down(button) => (
match button {
MouseButton::Left => 0,
MouseButton::Middle => 1,
MouseButton::Right => 2,
},
false,
),
MouseEventKind::Up(button) => (
match button {
MouseButton::Left => 0,
MouseButton::Middle => 1,
MouseButton::Right => 2,
},
true,
),
MouseEventKind::Drag(button) => (
32 + match button {
MouseButton::Left => 0,
MouseButton::Middle => 1,
MouseButton::Right => 2,
},
false,
),
MouseEventKind::Moved => (35, false),
MouseEventKind::ScrollUp => (64, false),
MouseEventKind::ScrollDown => (65, false),
MouseEventKind::ScrollLeft => (66, false),
MouseEventKind::ScrollRight => (67, false),
};
if mouse.modifiers.contains(KeyModifiers::SHIFT) {
code |= 4;
}
if mouse.modifiers.contains(KeyModifiers::ALT) {
code |= 8;
}
if mouse.modifiers.contains(KeyModifiers::CONTROL) {
code |= 16;
}
let col = mouse.column.saturating_add(1);
let row = mouse.row.saturating_add(1);
match encoding {
MouseProtocolEncoding::Sgr => {
let action = if release { 'm' } else { 'M' };
format!("\x1b[<{};{};{}{}", code, col, row, action).into_bytes()
}
MouseProtocolEncoding::Default => {
let x11_code = if release {
let mods = code & (4 | 8 | 16);
3 | mods
} else {
code
};
let cb = x11_code.saturating_add(32);
let cx = mouse.column.saturating_add(33);
let cy = mouse.row.saturating_add(33);
if cx > 255 || cy > 255 {
return Vec::new();
}
vec![0x1b, b'[', b'M', cb, cx as u8, cy as u8]
}
_ => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
fn key(k: KeyCode, mods: KeyModifiers) -> KeyEvent {
let mut ev = KeyEvent::new(k, mods);
ev.kind = KeyEventKind::Press;
ev
}
#[test]
fn key_to_bytes_char_and_controls() {
let b = key_to_bytes(&key(KeyCode::Char('x'), KeyModifiers::NONE));
assert_eq!(b, b"x".to_vec());
let enter = key_to_bytes(&key(KeyCode::Enter, KeyModifiers::NONE));
assert_eq!(enter, vec![b'\r']);
let back = key_to_bytes(&key(KeyCode::Backspace, KeyModifiers::NONE));
assert_eq!(back, vec![0x7f]);
let ctrl_a = key_to_bytes(&key(KeyCode::Char('a'), KeyModifiers::CONTROL));
assert_eq!(ctrl_a, vec![1u8]);
}
#[test]
fn key_to_bytes_alt() {
let alt_x = key_to_bytes(&key(KeyCode::Char('x'), KeyModifiers::ALT));
assert_eq!(alt_x, vec![0x1b, b'x']);
}
#[test]
fn key_to_bytes_delete_insert() {
let del = key_to_bytes(&key(KeyCode::Delete, KeyModifiers::NONE));
assert_eq!(del, b"\x1b[3~");
let ins = key_to_bytes(&key(KeyCode::Insert, KeyModifiers::NONE));
assert_eq!(ins, b"\x1b[2~");
}
#[test]
fn key_to_bytes_fkeys() {
let f1 = key_to_bytes(&key(KeyCode::F(1), KeyModifiers::NONE));
assert_eq!(f1, b"\x1bOP");
let f5 = key_to_bytes(&key(KeyCode::F(5), KeyModifiers::NONE));
assert_eq!(f5, b"\x1b[15~");
}
#[test]
fn key_to_bytes_backtab() {
let bt = key_to_bytes(&key(KeyCode::BackTab, KeyModifiers::NONE));
assert_eq!(bt, b"\x1b[Z");
}
#[test]
fn key_to_bytes_unicode() {
let u = key_to_bytes(&key(KeyCode::Char('é'), KeyModifiers::NONE));
assert_eq!(u, "é".as_bytes());
}
#[test]
fn key_to_bytes_shift_uppercase() {
let s = key_to_bytes(&key(KeyCode::Char('a'), KeyModifiers::SHIFT));
assert_eq!(s, b"A");
}
#[test]
fn ctrl_char_edges() {
assert_eq!(ctrl_char('a'), Some(1));
assert_eq!(ctrl_char('z'), Some(26));
assert_eq!(ctrl_char('A'), Some(1));
assert_eq!(ctrl_char('1'), None);
}
#[test]
fn mouse_event_allowed_modes() {
use MouseEventKind::*;
assert!(!mouse_event_allowed(
MouseProtocolMode::None,
Down(MouseButton::Left)
));
assert!(mouse_event_allowed(
MouseProtocolMode::Press,
Down(MouseButton::Left)
));
assert!(!mouse_event_allowed(
MouseProtocolMode::Press,
Up(MouseButton::Left)
));
assert!(mouse_event_allowed(
MouseProtocolMode::PressRelease,
Up(MouseButton::Left)
));
assert!(mouse_event_allowed(
MouseProtocolMode::ButtonMotion,
MouseEventKind::Drag(MouseButton::Left)
));
assert!(mouse_event_allowed(
MouseProtocolMode::AnyMotion,
MouseEventKind::Moved
));
}
#[test]
fn mouse_event_to_bytes_format_and_mods() {
let m = MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: 2,
row: 3,
modifiers: KeyModifiers::NONE,
};
let bytes = mouse_event_to_bytes(&m, MouseProtocolEncoding::Sgr);
let s = String::from_utf8(bytes).unwrap();
assert!(s.starts_with("\x1b[<0;3;4M"));
let bytes_def = mouse_event_to_bytes(&m, MouseProtocolEncoding::Default);
assert_eq!(bytes_def, vec![0x1b, b'[', b'M', 32, 35, 36]);
let m2 = MouseEvent {
kind: MouseEventKind::Up(MouseButton::Right),
column: 0,
row: 0,
modifiers: KeyModifiers::SHIFT | KeyModifiers::ALT,
};
let s2 = String::from_utf8(mouse_event_to_bytes(&m2, MouseProtocolEncoding::Sgr)).unwrap();
assert!(s2.contains(';'));
assert!(s2.ends_with('m'));
}
#[test]
fn mouse_event_x11_release_and_modifiers() {
let m_up = MouseEvent {
kind: MouseEventKind::Up(MouseButton::Left),
column: 0,
row: 0,
modifiers: KeyModifiers::NONE,
};
let bytes = mouse_event_to_bytes(&m_up, MouseProtocolEncoding::Default);
assert_eq!(bytes, vec![0x1b, b'[', b'M', 35, 33, 33]);
let m_up_shift = MouseEvent {
kind: MouseEventKind::Up(MouseButton::Left),
column: 0,
row: 0,
modifiers: KeyModifiers::SHIFT,
};
let bytes = mouse_event_to_bytes(&m_up_shift, MouseProtocolEncoding::Default);
assert_eq!(bytes, vec![0x1b, b'[', b'M', 39, 33, 33]);
let m_down_ctrl = MouseEvent {
kind: MouseEventKind::Down(MouseButton::Right),
column: 0,
row: 0,
modifiers: KeyModifiers::CONTROL,
};
let bytes = mouse_event_to_bytes(&m_down_ctrl, MouseProtocolEncoding::Default);
assert_eq!(bytes, vec![0x1b, b'[', b'M', 50, 33, 33]);
}
}