use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifiers: u8 {
const SHIFT = 1;
const ALT = 2;
const CTRL = 4;
const SUPER = 8;
const HYPER = 16;
const META = 32;
const CAPS_LOCK = 64;
const NUM_LOCK = 128;
}
}
impl Modifiers {
fn csi_param(self) -> Option<u8> {
let mut bits = 0u8;
if self.contains(Modifiers::SHIFT) {
bits |= 1;
}
if self.contains(Modifiers::ALT) {
bits |= 2;
}
if self.contains(Modifiers::CTRL) {
bits |= 4;
}
if self.contains(Modifiers::META) {
bits |= 8;
}
if bits == 0 { None } else { Some(1 + bits) }
}
fn kitty_param(self) -> Option<u8> {
if self.is_empty() {
None
} else {
Some(1 + self.bits())
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeypadKey {
Digit(u8),
Decimal,
Enter,
Add,
Subtract,
Multiply,
Divide,
Equal,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Key {
Char(char),
Keypad(KeypadKey),
Up,
Down,
Right,
Left,
Home,
End,
PageUp,
PageDown,
Insert,
Delete,
Enter,
Tab,
Backspace,
Escape,
F(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum KeyAction {
#[default]
Press,
Repeat,
Release,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KeyEvent {
pub key: Key,
pub mods: Modifiers,
pub action: KeyAction,
pub shifted_key: Option<char>,
pub base_key: Option<char>,
pub text: Option<char>,
}
impl Default for KeyEvent {
fn default() -> Self {
KeyEvent {
key: Key::Char('\0'),
mods: Modifiers::empty(),
action: KeyAction::Press,
shifted_key: None,
base_key: None,
text: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseButton {
Left,
Middle,
Right,
WheelUp,
WheelDown,
WheelLeft,
WheelRight,
Back,
Forward,
Other(u8),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseAction {
Press,
Release,
Motion,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MouseEvent {
pub button: Option<MouseButton>,
pub action: MouseAction,
pub col: usize,
pub row: usize,
pub px: usize,
pub py: usize,
pub mods: Modifiers,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MouseProtocol {
#[default]
Off,
X10,
Normal,
ButtonEvent,
AnyEvent,
}
bitflags::bitflags! {
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub struct MouseEvents: u8 {
const DOWN = 1 << 0;
const UP = 1 << 1;
const WHEEL = 1 << 2;
const DRAG = 1 << 3;
const MOVE = 1 << 4;
}
}
impl MouseProtocol {
pub fn wanted_events(self) -> MouseEvents {
match self {
MouseProtocol::Off => MouseEvents::empty(),
MouseProtocol::X10 => MouseEvents::DOWN,
MouseProtocol::Normal => MouseEvents::DOWN | MouseEvents::UP | MouseEvents::WHEEL,
MouseProtocol::ButtonEvent => {
MouseEvents::DOWN | MouseEvents::UP | MouseEvents::WHEEL | MouseEvents::DRAG
}
MouseProtocol::AnyEvent => {
MouseEvents::DOWN
| MouseEvents::UP
| MouseEvents::WHEEL
| MouseEvents::DRAG
| MouseEvents::MOVE
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MouseEncoding {
#[default]
Default,
Sgr,
Urxvt,
Utf8,
SgrPixels,
}
const ESC: u8 = 0x1b;
const KITTY_DISAMBIGUATE: u8 = 0b1;
pub fn encode_key(
ev: &KeyEvent,
app_cursor: bool,
app_keypad: bool,
kitty_flags: u8,
) -> Option<Vec<u8>> {
if kitty_flags != 0
&& let Some(bytes) = kitty_encode(ev, kitty_flags)
{
return Some(bytes);
}
match ev.key {
Key::Char(c) => Some(encode_char(c, ev.mods)),
Key::Keypad(k) => Some(keypad_key(k, app_keypad)),
Key::Up => Some(cursor_key(b'A', ev.mods, app_cursor)),
Key::Down => Some(cursor_key(b'B', ev.mods, app_cursor)),
Key::Right => Some(cursor_key(b'C', ev.mods, app_cursor)),
Key::Left => Some(cursor_key(b'D', ev.mods, app_cursor)),
Key::Home => Some(cursor_key(b'H', ev.mods, app_cursor)),
Key::End => Some(cursor_key(b'F', ev.mods, app_cursor)),
Key::Insert => Some(tilde_key(2, ev.mods)),
Key::Delete => Some(tilde_key(3, ev.mods)),
Key::PageUp => Some(tilde_key(5, ev.mods)),
Key::PageDown => Some(tilde_key(6, ev.mods)),
Key::Enter => Some(vec![b'\r']),
Key::Backspace => Some(vec![0x7f]), Key::Escape => Some(vec![ESC]),
Key::Tab => {
if ev.mods.contains(Modifiers::SHIFT) {
Some(vec![ESC, b'[', b'Z']) } else {
Some(vec![b'\t'])
}
}
Key::F(n) => function_key(n, ev.mods),
}
}
const KITTY_REPORT_EVENTS: u8 = 0b10;
const KITTY_ALTERNATE_KEYS: u8 = 0b100;
const KITTY_ALL_AS_ESCAPE: u8 = 0b1000;
const KITTY_ASSOCIATED_TEXT: u8 = 0b10000;
fn kitty_encode(ev: &KeyEvent, flags: u8) -> Option<Vec<u8>> {
let event = if flags & KITTY_REPORT_EVENTS != 0 {
match ev.action {
KeyAction::Press => None,
KeyAction::Repeat => Some(2),
KeyAction::Release => Some(3),
}
} else {
None
};
let modified = ev.mods.kitty_param();
let disambiguate = flags & KITTY_DISAMBIGUATE != 0;
if let Some((number, terminator)) = functional_key(ev.key) {
if event.is_none() && modified.is_none() {
return None; }
return Some(kitty_seq(number, modified, event, terminator));
}
let codepoint = match ev.key {
Key::Escape => 27,
Key::Enter => 13,
Key::Tab => 9,
Key::Backspace => 127,
Key::Char(c) => c as u32,
_ => return None,
};
let all_as_escape = flags & KITTY_ALL_AS_ESCAPE != 0;
let always = (disambiguate && ev.key == Key::Escape) || all_as_escape;
if !always && event.is_none() && !(disambiguate && modified.is_some()) {
return None;
}
Some(kitty_csi_u(ev, codepoint, modified, event, flags))
}
fn kitty_csi_u(
ev: &KeyEvent,
codepoint: u32,
modified: Option<u8>,
event: Option<u8>,
flags: u8,
) -> Vec<u8> {
let mut s = format!("\x1b[{codepoint}");
if flags & KITTY_ALTERNATE_KEYS != 0 && (ev.shifted_key.is_some() || ev.base_key.is_some()) {
s.push(':');
if let Some(sh) = ev.shifted_key {
s.push_str(&(sh as u32).to_string());
}
if let Some(b) = ev.base_key {
s.push(':');
s.push_str(&(b as u32).to_string());
}
}
let text = if flags & KITTY_ASSOCIATED_TEXT != 0 {
ev.text
} else {
None
};
if modified.is_some() || event.is_some() || text.is_some() {
s.push(';');
s.push_str(&modified.unwrap_or(1).to_string());
if let Some(e) = event {
s.push(':');
s.push_str(&e.to_string());
}
}
if let Some(txt) = text {
s.push(';');
s.push_str(&(txt as u32).to_string());
}
s.push('u');
s.into_bytes()
}
fn functional_key(key: Key) -> Option<(u32, u8)> {
Some(match key {
Key::Up => (1, b'A'),
Key::Down => (1, b'B'),
Key::Right => (1, b'C'),
Key::Left => (1, b'D'),
Key::Home => (1, b'H'),
Key::End => (1, b'F'),
Key::Insert => (2, b'~'),
Key::Delete => (3, b'~'),
Key::PageUp => (5, b'~'),
Key::PageDown => (6, b'~'),
Key::F(1) => (1, b'P'),
Key::F(2) => (1, b'Q'),
Key::F(3) => (1, b'R'),
Key::F(4) => (1, b'S'),
Key::F(5) => (15, b'~'),
Key::F(6) => (17, b'~'),
Key::F(7) => (18, b'~'),
Key::F(8) => (19, b'~'),
Key::F(9) => (20, b'~'),
Key::F(10) => (21, b'~'),
Key::F(11) => (23, b'~'),
Key::F(12) => (24, b'~'),
_ => return None,
})
}
fn kitty_seq(number: u32, modified: Option<u8>, event: Option<u8>, terminator: u8) -> Vec<u8> {
let mut s = format!("\x1b[{number}");
if modified.is_some() || event.is_some() {
s.push(';');
s.push_str(&modified.unwrap_or(1).to_string());
if let Some(e) = event {
s.push(':');
s.push_str(&e.to_string());
}
}
let mut v = s.into_bytes();
v.push(terminator);
v
}
fn encode_char(c: char, mods: Modifiers) -> Vec<u8> {
let mut out = Vec::new();
if mods.contains(Modifiers::ALT) {
out.push(ESC);
}
if mods.contains(Modifiers::CTRL) {
let code = match c {
'a'..='z' => Some((c as u8 - b'a') + 1),
'A'..='Z' => Some((c as u8 - b'A') + 1),
'@' => Some(0),
'[' => Some(0x1b),
'\\' => Some(0x1c),
']' => Some(0x1d),
'^' => Some(0x1e),
'_' => Some(0x1f),
' ' => Some(0),
_ => None,
};
if let Some(b) = code {
out.push(b);
return out;
}
}
let mut buf = [0u8; 4];
out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
out
}
fn cursor_key(final_byte: u8, mods: Modifiers, app_cursor: bool) -> Vec<u8> {
match mods.csi_param() {
Some(param) => {
let mut v = vec![ESC, b'['];
v.extend_from_slice(b"1;");
v.extend_from_slice(param.to_string().as_bytes());
v.push(final_byte);
v
}
None if app_cursor => vec![ESC, b'O', final_byte],
None => vec![ESC, b'[', final_byte],
}
}
fn keypad_key(k: KeypadKey, app_keypad: bool) -> Vec<u8> {
if app_keypad {
let final_byte = match k {
KeypadKey::Digit(n) => b'p' + n.min(9), KeypadKey::Decimal => b'n',
KeypadKey::Enter => b'M',
KeypadKey::Add => b'k',
KeypadKey::Subtract => b'm',
KeypadKey::Multiply => b'j',
KeypadKey::Divide => b'o',
KeypadKey::Equal => b'X',
};
vec![ESC, b'O', final_byte]
} else {
let c = match k {
KeypadKey::Digit(n) => b'0' + n.min(9),
KeypadKey::Decimal => b'.',
KeypadKey::Enter => b'\r',
KeypadKey::Add => b'+',
KeypadKey::Subtract => b'-',
KeypadKey::Multiply => b'*',
KeypadKey::Divide => b'/',
KeypadKey::Equal => b'=',
};
vec![c]
}
}
fn tilde_key(n: u8, mods: Modifiers) -> Vec<u8> {
let mut v = vec![ESC, b'['];
v.extend_from_slice(n.to_string().as_bytes());
if let Some(param) = mods.csi_param() {
v.push(b';');
v.extend_from_slice(param.to_string().as_bytes());
}
v.push(b'~');
v
}
fn function_key(n: u8, mods: Modifiers) -> Option<Vec<u8>> {
match n {
1..=4 => {
let letter = b'P' + (n - 1); match mods.csi_param() {
Some(param) => {
let mut v = vec![ESC, b'[', b'1', b';'];
v.extend_from_slice(param.to_string().as_bytes());
v.push(letter);
Some(v)
}
None => Some(vec![ESC, b'O', letter]),
}
}
5 => Some(tilde_key(15, mods)),
6 => Some(tilde_key(17, mods)),
7 => Some(tilde_key(18, mods)),
8 => Some(tilde_key(19, mods)),
9 => Some(tilde_key(20, mods)),
10 => Some(tilde_key(21, mods)),
11 => Some(tilde_key(23, mods)),
12 => Some(tilde_key(24, mods)),
_ => None,
}
}
fn is_wheel(button: Option<MouseButton>) -> bool {
matches!(
button,
Some(
MouseButton::WheelUp
| MouseButton::WheelDown
| MouseButton::WheelLeft
| MouseButton::WheelRight
)
)
}
fn event_category(ev: &MouseEvent) -> MouseEvents {
match ev.action {
MouseAction::Press if is_wheel(ev.button) => MouseEvents::WHEEL,
MouseAction::Press => MouseEvents::DOWN,
MouseAction::Release => MouseEvents::UP,
MouseAction::Motion if ev.button.is_some() => MouseEvents::DRAG,
MouseAction::Motion => MouseEvents::MOVE,
}
}
pub fn encode_mouse(ev: &MouseEvent, proto: MouseProtocol, enc: MouseEncoding) -> Option<Vec<u8>> {
if ev.action == MouseAction::Release && is_wheel(ev.button) {
return None;
}
if !proto.wanted_events().contains(event_category(ev)) {
return None;
}
let x10 = proto == MouseProtocol::X10;
let button_bits = match ev.button {
Some(MouseButton::Left) => 0,
Some(MouseButton::Middle) => 1,
Some(MouseButton::Right) => 2,
Some(MouseButton::WheelUp) => 64,
Some(MouseButton::WheelDown) => 65,
Some(MouseButton::WheelLeft) => 66,
Some(MouseButton::WheelRight) => 67,
Some(MouseButton::Back) => 128,
Some(MouseButton::Forward) => 129,
Some(MouseButton::Other(n)) => {
let n = n as usize;
(n & 3) | (if n & 4 != 0 { 64 } else { 0 }) | (if n & 8 != 0 { 128 } else { 0 })
}
None => 3, };
let motion = if ev.action == MouseAction::Motion {
32
} else {
0
};
let mod_bits = if x10 {
0
} else {
(if ev.mods.contains(Modifiers::SHIFT) {
4
} else {
0
}) + (if ev.mods.contains(Modifiers::ALT) {
8
} else {
0
}) + (if ev.mods.contains(Modifiers::CTRL) {
16
} else {
0
})
};
let col1 = ev.col + 1;
let row1 = ev.row + 1;
match enc {
MouseEncoding::Sgr | MouseEncoding::SgrPixels => {
let cb = button_bits + motion + mod_bits;
let (x, y) = if enc == MouseEncoding::SgrPixels {
(ev.px + 1, ev.py + 1)
} else {
(col1, row1)
};
let final_byte = if ev.action == MouseAction::Release {
b'm'
} else {
b'M'
};
let mut v = vec![ESC, b'[', b'<'];
v.extend_from_slice(cb.to_string().as_bytes());
v.push(b';');
v.extend_from_slice(x.to_string().as_bytes());
v.push(b';');
v.extend_from_slice(y.to_string().as_bytes());
v.push(final_byte);
Some(v)
}
MouseEncoding::Default => {
let base = if ev.action == MouseAction::Release {
3
} else {
button_bits
};
let cb = base + motion + mod_bits + 32;
let cx = (col1 + 32).min(255) as u8;
let cy = (row1 + 32).min(255) as u8;
Some(vec![ESC, b'[', b'M', cb as u8, cx, cy])
}
MouseEncoding::Urxvt => {
let base = if ev.action == MouseAction::Release {
3
} else {
button_bits
};
let cb = base + motion + mod_bits + 32;
let mut v = vec![ESC, b'['];
v.extend_from_slice(cb.to_string().as_bytes());
v.push(b';');
v.extend_from_slice(col1.to_string().as_bytes());
v.push(b';');
v.extend_from_slice(row1.to_string().as_bytes());
v.push(b'M');
Some(v)
}
MouseEncoding::Utf8 => {
let base = if ev.action == MouseAction::Release {
3
} else {
button_bits
};
let mut v = vec![ESC, b'[', b'M'];
push_utf8(&mut v, base + motion + mod_bits + 32);
push_utf8(&mut v, col1 + 32);
push_utf8(&mut v, row1 + 32);
Some(v)
}
}
}
fn push_utf8(out: &mut Vec<u8>, val: usize) {
let c = char::from_u32(val as u32).unwrap_or('\u{fffd}');
let mut buf = [0u8; 4];
out.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
}
pub fn encode_paste(text: &str, bracketed: bool) -> Vec<u8> {
if !bracketed {
return text.as_bytes().to_vec();
}
let mut v = Vec::with_capacity(text.len() + 12);
v.extend_from_slice(b"\x1b[200~");
v.extend_from_slice(text.as_bytes());
v.extend_from_slice(b"\x1b[201~");
v
}
pub fn encode_focus(focused: bool, enabled: bool) -> Option<Vec<u8>> {
if !enabled {
return None;
}
Some(vec![ESC, b'[', if focused { b'I' } else { b'O' }])
}