From 44424c0108cac040cb48b41b3805c7e079c980c3 Mon Sep 17 00:00:00 2001 From: Mattias Eriksson Date: Tue, 6 Feb 2024 17:43:17 +0100 Subject: [PATCH 1/2] Refactor key handling --- src/terminal_box.rs | 502 ++++++++++++++------------------------------ 1 file changed, 160 insertions(+), 342 deletions(-) diff --git a/src/terminal_box.rs b/src/terminal_box.rs index 2284c65..311caae 100644 --- a/src/terminal_box.rs +++ b/src/terminal_box.rs @@ -596,244 +596,86 @@ where Event::Keyboard(KeyEvent::KeyPressed { key_code, modifiers, - }) if state.is_focused => match ( - modifiers.logo(), - modifiers.control(), - modifiers.alt(), - modifiers.shift(), - ) { - (true, _, _, _) => { - // Ignore super keys + }) if state.is_focused => { + let mod_no = calculate_modifier_number(state); + let escape_code = match key_code { + KeyCode::Insert => csi("2", mod_no), + KeyCode::Delete => csi("3", mod_no), + KeyCode::PageUp => csi("5", mod_no), + KeyCode::PageDown => csi("6", mod_no), + KeyCode::Up => { + if is_app_cursor { + ss3("A", mod_no) + } else { + csi("A", mod_no) + } + } + KeyCode::Down => { + if is_app_cursor { + ss3("B", mod_no) + } else { + csi("B", mod_no) + } + } + KeyCode::Right => { + if is_app_cursor { + ss3("C", mod_no) + } else { + csi("C", mod_no) + } + } + KeyCode::Left => { + if is_app_cursor { + ss3("D", mod_no) + } else { + csi("D", mod_no) + } + } + KeyCode::Home => { + if is_app_cursor { + ss3("H", mod_no) + } else { + csi("H", mod_no) + } + } + KeyCode::End => { + if is_app_cursor { + ss3("F", mod_no) + } else { + csi("F", mod_no) + } + } + KeyCode::F1 => ss3("P", mod_no), + KeyCode::F2 => ss3("Q", mod_no), + KeyCode::F3 => ss3("R", mod_no), + KeyCode::F4 => ss3("S", mod_no), + KeyCode::F5 => csi("15", mod_no), + KeyCode::F6 => csi("17", mod_no), + KeyCode::F7 => csi("18", mod_no), + KeyCode::F8 => csi("19", mod_no), + KeyCode::F9 => csi("20", mod_no), + KeyCode::F10 => csi("21", mod_no), + KeyCode::F11 => csi("23", mod_no), + KeyCode::F12 => csi("24", mod_no), + _ => None, + }; + if let Some(escape_code) = escape_code { + terminal.input_scroll(escape_code); + return Status::Captured; } - (_, true, _, _) => match key_code { - KeyCode::Up => { - terminal.input_scroll(b"\x1B[1;5A".as_slice()); - status = Status::Captured; - } - KeyCode::Down => { - terminal.input_scroll(b"\x1B[1;5B".as_slice()); - status = Status::Captured; - } - KeyCode::Right => { - terminal.input_scroll(b"\x1B[1;5C".as_slice()); - status = Status::Captured; - } - KeyCode::Left => { - terminal.input_scroll(b"\x1B[1;5D".as_slice()); - status = Status::Captured; - } - KeyCode::End => { - terminal.input_scroll(b"\x1B[1;5F".as_slice()); - status = Status::Captured; - } - KeyCode::Home => { - terminal.input_scroll(b"\x1B[1;5H".as_slice()); - status = Status::Captured; - } - KeyCode::Insert => { - terminal.input_scroll(b"\x1B[2;5~".as_slice()); - status = Status::Captured; - } - KeyCode::Delete => { - terminal.input_scroll(b"\x1B[3;5~".as_slice()); - status = Status::Captured; - } - KeyCode::PageUp => { - terminal.input_scroll(b"\x1B[5;5~".as_slice()); - status = Status::Captured; - } - KeyCode::PageDown => { - terminal.input_scroll(b"\x1B[6;5~".as_slice()); - status = Status::Captured; - } - KeyCode::F1 => { - terminal.input_scroll(b"\x1BO;5P".as_slice()); - status = Status::Captured; - } - KeyCode::F2 => { - terminal.input_scroll(b"\x1BO;5Q".as_slice()); - status = Status::Captured; - } - KeyCode::F3 => { - terminal.input_scroll(b"\x1BO;5R".as_slice()); - status = Status::Captured; - } - KeyCode::F4 => { - terminal.input_scroll(b"\x1BO;5S".as_slice()); - status = Status::Captured; - } - KeyCode::F5 => { - terminal.input_scroll(b"\x1B[15;5~".as_slice()); - status = Status::Captured; - } - KeyCode::F6 => { - terminal.input_scroll(b"\x1B[17;5~".as_slice()); - status = Status::Captured; - } - KeyCode::F7 => { - terminal.input_scroll(b"\x1B[18;5~".as_slice()); - status = Status::Captured; - } - KeyCode::F8 => { - terminal.input_scroll(b"\x1B[19;5~".as_slice()); - status = Status::Captured; - } - KeyCode::F9 => { - terminal.input_scroll(b"\x1B[20;5~".as_slice()); - status = Status::Captured; - } - KeyCode::F10 => { - terminal.input_scroll(b"\x1B[21;5~".as_slice()); - status = Status::Captured; - } - KeyCode::F11 => { - terminal.input_scroll(b"\x1B[23;5~".as_slice()); - status = Status::Captured; - } - KeyCode::F12 => { - terminal.input_scroll(b"\x1B[24;5~".as_slice()); - status = Status::Captured; - } - //This is normally Ctrl+Minus, but since that - //is taken by zoom, we send that code for - //Ctrl+Underline instead, like xterm and - //gnome-terminal - KeyCode::Underline => { - terminal.input_scroll(b"\x1F".as_slice()); - status = Status::Captured; - } - _ => (), - }, - // Handle alt keys - (_, _, true, _) => match key_code { - KeyCode::Up => { - terminal.input_scroll(b"\x1B[1;3A".as_slice()); - status = Status::Captured; - } - KeyCode::Down => { - terminal.input_scroll(b"\x1B[1;3B".as_slice()); - status = Status::Captured; - } - KeyCode::Right => { - terminal.input_scroll(b"\x1B[1;3C".as_slice()); - status = Status::Captured; - } - KeyCode::Left => { - terminal.input_scroll(b"\x1B[1;3D".as_slice()); - status = Status::Captured; - } - KeyCode::End => { - terminal.input_scroll(b"\x1B[1;3F".as_slice()); - status = Status::Captured; - } - KeyCode::Home => { - terminal.input_scroll(b"\x1B[1;3H".as_slice()); - status = Status::Captured; - } - KeyCode::Insert => { - terminal.input_scroll(b"\x1B[2;3~".as_slice()); - status = Status::Captured; - } - KeyCode::Delete => { - terminal.input_scroll(b"\x1B[3;3~".as_slice()); - status = Status::Captured; - } - KeyCode::PageUp => { - terminal.input_scroll(b"\x1B[5;3~".as_slice()); - status = Status::Captured; - } - KeyCode::PageDown => { - terminal.input_scroll(b"\x1B[6;3~".as_slice()); - status = Status::Captured; - } - KeyCode::F1 => { - terminal.input_scroll(b"\x1B[1;3P".as_slice()); - status = Status::Captured; - } - KeyCode::F2 => { - terminal.input_scroll(b"\x1B1;3Q".as_slice()); - status = Status::Captured; - } - KeyCode::F3 => { - terminal.input_scroll(b"\x1B1;3R".as_slice()); - status = Status::Captured; - } - KeyCode::F4 => { - terminal.input_scroll(b"\x1B1;3S".as_slice()); - status = Status::Captured; - } - KeyCode::F5 => { - terminal.input_scroll(b"\x1B[15;3~".as_slice()); - status = Status::Captured; - } - KeyCode::F6 => { - terminal.input_scroll(b"\x1B[17;3~".as_slice()); - status = Status::Captured; - } - KeyCode::F7 => { - terminal.input_scroll(b"\x1B[18;3~".as_slice()); - status = Status::Captured; - } - KeyCode::F8 => { - terminal.input_scroll(b"\x1B[19;3~".as_slice()); - status = Status::Captured; - } - KeyCode::F9 => { - terminal.input_scroll(b"\x1B[20;3~".as_slice()); - status = Status::Captured; - } - KeyCode::F10 => { - terminal.input_scroll(b"\x1B[21;3~".as_slice()); - status = Status::Captured; - } - KeyCode::F11 => { - terminal.input_scroll(b"\x1B[23;3~".as_slice()); - status = Status::Captured; - } - KeyCode::F12 => { - terminal.input_scroll(b"\x1B[24;3~".as_slice()); - status = Status::Captured; - } - KeyCode::Backspace => { - terminal.input_scroll(b"\x1B\x7F".as_slice()); - status = Status::Captured; - } - _ => (), - }, - // Handle shift keys - (_, _, _, true) => match key_code { - KeyCode::End => { - terminal.scroll(TerminalScroll::Bottom); - } - KeyCode::Home => { - terminal.scroll(TerminalScroll::Top); - } - KeyCode::PageDown => { - terminal.scroll(TerminalScroll::PageDown); - } - KeyCode::PageUp => { - terminal.scroll(TerminalScroll::PageUp); - } - KeyCode::Tab => { - terminal.input_scroll(b"\x1B[Z".as_slice()); - } - _ => {} - }, - // Handle keys with no modifiers - (_, _, _, false) => match key_code { - KeyCode::Backspace => { - terminal.input_scroll(b"\x7F".as_slice()); - status = Status::Captured; - } - KeyCode::Tab => { - terminal.input_scroll(b"\t".as_slice()); - status = Status::Captured; - } + + //Special handle Enter, Escape, Backspace and Tab as described in + //https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-key-event-encoding + //Also special handle Ctrl-_ to behave like xterm + let alt_prefix = if modifiers.alt() { "\x1B" } else { "" }; + match key_code { KeyCode::Enter => { - terminal.input_scroll(b"\r".as_slice()); + terminal + .input_scroll(format!("{}{}", alt_prefix, "\x0D").as_bytes().to_vec()); status = Status::Captured; } KeyCode::Escape => { + //Escape with any modifier will cancel selection let had_selection = { let mut term = terminal.term.lock(); term.selection.take().is_some() @@ -841,113 +683,31 @@ where if had_selection { terminal.update(); } else { - terminal.input_scroll(b"\x1B".as_slice()); + terminal.input_scroll( + format!("{}{}", alt_prefix, "\x1B").as_bytes().to_vec(), + ); } status = Status::Captured; } - KeyCode::Up => { - let code = if is_app_cursor { b"\x1BOA" } else { b"\x1B[A" }; - - terminal.input_scroll(code.as_slice()); + KeyCode::Backspace => { + let code = if modifiers.control() { "\x08" } else { "\x7f" }; + terminal + .input_scroll(format!("{}{}", alt_prefix, code).as_bytes().to_vec()); status = Status::Captured; } - KeyCode::Down => { - let code = if is_app_cursor { b"\x1BOB" } else { b"\x1B[B" }; - - terminal.input_scroll(code.as_slice()); + KeyCode::Tab => { + let code = if modifiers.shift() { "\x1b[Z" } else { "\x09" }; + terminal + .input_scroll(format!("{}{}", alt_prefix, code).as_bytes().to_vec()); status = Status::Captured; } - KeyCode::Right => { - let code = if is_app_cursor { b"\x1BOC" } else { b"\x1B[C" }; - - terminal.input_scroll(code.as_slice()); + KeyCode::Underline if modifiers.control() => { + terminal.input_scroll(b"\x1F".as_slice()); status = Status::Captured; } - KeyCode::Left => { - let code = if is_app_cursor { b"\x1BOD" } else { b"\x1B[D" }; - - terminal.input_scroll(code.as_slice()); - status = Status::Captured; - } - KeyCode::End => { - let code = if is_app_cursor { b"\x1BOF" } else { b"\x1B[F" }; - - terminal.input_scroll(code.as_slice()); - status = Status::Captured; - } - KeyCode::Home => { - let code = if is_app_cursor { b"\x1BOH" } else { b"\x1B[H" }; - - terminal.input_scroll(code.as_slice()); - status = Status::Captured; - } - KeyCode::Insert => { - terminal.input_scroll(b"\x1B[2~".as_slice()); - status = Status::Captured; - } - KeyCode::Delete => { - terminal.input_scroll(b"\x1B[3~".as_slice()); - status = Status::Captured; - } - KeyCode::PageUp => { - terminal.input_scroll(b"\x1B[5~".as_slice()); - status = Status::Captured; - } - KeyCode::PageDown => { - terminal.input_scroll(b"\x1B[6~".as_slice()); - status = Status::Captured; - } - KeyCode::F1 => { - terminal.input_scroll(b"\x1BOP".as_slice()); - status = Status::Captured; - } - KeyCode::F2 => { - terminal.input_scroll(b"\x1BOQ".as_slice()); - status = Status::Captured; - } - KeyCode::F3 => { - terminal.input_scroll(b"\x1BOR".as_slice()); - status = Status::Captured; - } - KeyCode::F4 => { - terminal.input_scroll(b"\x1BOS".as_slice()); - status = Status::Captured; - } - KeyCode::F5 => { - terminal.input_scroll(b"\x1B[15~".as_slice()); - status = Status::Captured; - } - KeyCode::F6 => { - terminal.input_scroll(b"\x1B[17~".as_slice()); - status = Status::Captured; - } - KeyCode::F7 => { - terminal.input_scroll(b"\x1B[18~".as_slice()); - status = Status::Captured; - } - KeyCode::F8 => { - terminal.input_scroll(b"\x1B[19~".as_slice()); - status = Status::Captured; - } - KeyCode::F9 => { - terminal.input_scroll(b"\x1B[20~".as_slice()); - status = Status::Captured; - } - KeyCode::F10 => { - terminal.input_scroll(b"\x1B[21~".as_slice()); - status = Status::Captured; - } - KeyCode::F11 => { - terminal.input_scroll(b"\x1B[23~".as_slice()); - status = Status::Captured; - } - KeyCode::F12 => { - terminal.input_scroll(b"\x1B[24~".as_slice()); - status = Status::Captured; - } - _ => (), - }, - }, + _ => {} + } + } Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => { state.modifiers = modifiers; } @@ -961,18 +721,17 @@ where (true, _, _, _) => { // Ignore super } - (false, true, true, false) => { + (false, true, true, _) => { // Handle ctrl-alt for non-control characters - // Or should I try to minimize this to only - // catch control sequences that conflicts with - // keykodes for Split - // if character != '\u{4}' && character != '\u{12}' { - // is there any valid case for control characters with modifers - // ctrl-alt? - if !character.is_control() { - let mut buf = [0, 0, 0, 0]; - let str = character.encode_utf8(&mut buf); - terminal.input_scroll(str.as_bytes().to_vec()); + // and control characters 0-32 + if !character.is_control() || (character as u32) < 32 { + // Handle alt for non-control characters + let mut buf = [0x1B, 0, 0, 0, 0]; + let len = { + let str = character.encode_utf8(&mut buf[1..]); + str.len() + 1 + }; + terminal.input_scroll(buf[..len].to_vec()); status = Status::Captured; } } @@ -1316,3 +1075,62 @@ impl operation::Focusable for State { self.is_focused = false; } } + +/* + 2 | Shift + 3 | Alt + 4 | Shift + Alt + 5 | Control + 6 | Shift + Control + 7 | Alt + Control + 8 | Shift + Alt + Control + 9 | Meta + 10 | Meta + Shift + 11 | Meta + Alt + 12 | Meta + Alt + Shift + 13 | Meta + Ctrl + 14 | Meta + Ctrl + Shift + 15 | Meta + Ctrl + Alt + 16 | Meta + Ctrl + Alt + Shift +*/ +fn calculate_modifier_number(state: &mut State) -> u8 { + match ( + state.modifiers.control(), + state.modifiers.alt(), + state.modifiers.shift(), + ) { + // 8 | Shift + Alt + Control + (true, true, true) => 8, + // 7 | Alt + Control + (true, true, false) => 7, + // 6 | Shift + Control + (true, false, true) => 6, + // 5 | Control + (true, false, false) => 5, + // 4 | Shift + Alt + (false, true, true) => 4, + // 3 | Alt + (false, true, false) => 3, + (false, false, true) => 2, + // 2 | Shift + (false, false, false) => 0, + } +} + +#[inline(always)] +fn csi(code: &str, modifiers: u8) -> Option> { + if modifiers == 0 { + Some(format!("\x1B[{}~", code).as_bytes().to_vec()) + } else { + Some(format!("\x1B[{};{}~", code, modifiers).as_bytes().to_vec()) + } +} + +#[inline(always)] +fn ss3(code: &str, modifiers: u8) -> Option> { + if modifiers == 0 { + Some(format!("\x1B\x4F{}", code).as_bytes().to_vec()) + } else { + Some(format!("\x1B[1;{}{}", modifiers, code).as_bytes().to_vec()) + } +} From e5692cd902a204b3644f4047842893e454240709 Mon Sep 17 00:00:00 2001 From: Mattias Eriksson Date: Tue, 6 Feb 2024 20:55:15 +0100 Subject: [PATCH 2/2] Cleaner modifier calculation --- src/terminal_box.rs | 60 +++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/src/terminal_box.rs b/src/terminal_box.rs index 311caae..563b621 100644 --- a/src/terminal_box.rs +++ b/src/terminal_box.rs @@ -1077,49 +1077,35 @@ impl operation::Focusable for State { } /* - 2 | Shift - 3 | Alt - 4 | Shift + Alt - 5 | Control - 6 | Shift + Control - 7 | Alt + Control - 8 | Shift + Alt + Control - 9 | Meta - 10 | Meta + Shift - 11 | Meta + Alt - 12 | Meta + Alt + Shift - 13 | Meta + Ctrl - 14 | Meta + Ctrl + Shift - 15 | Meta + Ctrl + Alt - 16 | Meta + Ctrl + Alt + Shift + shift 0b1 (1) +alt 0b10 (2) +ctrl 0b100 (4) +super 0b1000 (8) +hyper 0b10000 (16) +meta 0b100000 (32) +caps_lock 0b1000000 (64) +num_lock 0b10000000 (128) */ fn calculate_modifier_number(state: &mut State) -> u8 { - match ( - state.modifiers.control(), - state.modifiers.alt(), - state.modifiers.shift(), - ) { - // 8 | Shift + Alt + Control - (true, true, true) => 8, - // 7 | Alt + Control - (true, true, false) => 7, - // 6 | Shift + Control - (true, false, true) => 6, - // 5 | Control - (true, false, false) => 5, - // 4 | Shift + Alt - (false, true, true) => 4, - // 3 | Alt - (false, true, false) => 3, - (false, false, true) => 2, - // 2 | Shift - (false, false, false) => 0, + let mut mod_no = 0; + if state.modifiers.shift() { + mod_no |= 1; } + if state.modifiers.alt() { + mod_no |= 2; + } + if state.modifiers.control() { + mod_no |= 4; + } + if state.modifiers.logo() { + mod_no |= 8; + } + mod_no + 1 } #[inline(always)] fn csi(code: &str, modifiers: u8) -> Option> { - if modifiers == 0 { + if modifiers == 1 { Some(format!("\x1B[{}~", code).as_bytes().to_vec()) } else { Some(format!("\x1B[{};{}~", code, modifiers).as_bytes().to_vec()) @@ -1128,7 +1114,7 @@ fn csi(code: &str, modifiers: u8) -> Option> { #[inline(always)] fn ss3(code: &str, modifiers: u8) -> Option> { - if modifiers == 0 { + if modifiers == 1 { Some(format!("\x1B\x4F{}", code).as_bytes().to_vec()) } else { Some(format!("\x1B[1;{}{}", modifiers, code).as_bytes().to_vec())