diff --git a/term/src/terminalstate/keyboard.rs b/term/src/terminalstate/keyboard.rs index 51d3c467a..179bfb674 100644 --- a/term/src/terminalstate/keyboard.rs +++ b/term/src/terminalstate/keyboard.rs @@ -12,24 +12,37 @@ impl TerminalState { } } - /// Processes a key_down event generated by the gui/render layer + /// Processes a key event generated by the gui/render layer /// that is embedding the Terminal. This method translates the /// keycode into a sequence of bytes to send to the slave end /// of the pty via the `Write`-able object provided by the caller. - pub fn key_down(&mut self, key: KeyCode, mods: KeyModifiers) -> anyhow::Result<()> { - let to_send = key.encode( + fn key_up_down( + &mut self, + key: KeyCode, + mods: KeyModifiers, + is_down: bool, + ) -> anyhow::Result<()> { + let encoding = self.effective_keyboard_encoding(); + + let to_send = key.encode_up_down( mods, KeyCodeEncodeModes { - encoding: self.effective_keyboard_encoding(), + encoding, newline_mode: self.newline_mode, application_cursor_keys: self.application_cursor_keys, }, + is_down, )?; + if to_send.is_empty() { + return Ok(()); + } + + let label = if is_down { "key_down" } else { "key_up" }; if self.config.debug_key_events() { - log::info!("key_down: sending {:?}, {:?} {:?}", to_send, key, mods); + log::info!("{}: sending {:?}, {:?} {:?}", label, to_send, key, mods); } else { - log::trace!("key_down: sending {:?}, {:?} {:?}", to_send, key, mods); + log::trace!("{}: sending {:?}, {:?} {:?}", label, to_send, key, mods); } self.writer.write_all(to_send.as_bytes())?; self.writer.flush()?; @@ -37,7 +50,11 @@ impl TerminalState { Ok(()) } - pub fn key_up(&mut self, _key: KeyCode, _mods: KeyModifiers) -> anyhow::Result<()> { - Ok(()) + pub fn key_up(&mut self, key: KeyCode, mods: KeyModifiers) -> anyhow::Result<()> { + self.key_up_down(key, mods, false) + } + + pub fn key_down(&mut self, key: KeyCode, mods: KeyModifiers) -> anyhow::Result<()> { + self.key_up_down(key, mods, true) } } diff --git a/termwiz/src/input.rs b/termwiz/src/input.rs index 27ecc9a49..5847c02d4 100644 --- a/termwiz/src/input.rs +++ b/termwiz/src/input.rs @@ -251,6 +251,111 @@ impl KeyCode { ) } + /// + /// We only encode a handful of specific keys where we know that we can + /// translate between the VK_XXX value and our own representation, + /// and where it resolves potential ambiguity, or allows passing through + /// key down events for modifier keys themselves. + /// We don't have enough information here to represent all possible + /// key presses correctly in the win32-input-mode encoding. + pub fn encode_win32_input_mode(&self, mods: Modifiers, is_down: bool) -> Option { + #![allow(dead_code)] + + use KeyCode::*; + + // + const VK_SPACE: usize = 0x20; + const VK_RETURN: usize = 0x0d; + const VK_F1: usize = 0x70; + const VK_SHIFT: usize = 0x10; + const VK_LSHIFT: usize = 0xa0; + const VK_RSHIFT: usize = 0xa1; + const VK_CONTROL: usize = 0x11; + const VK_LCONTROL: usize = 0xa2; + const VK_RCONTROL: usize = 0xa3; + const VK_MENU: usize = 0x12; + const VK_LMENU: usize = 0xa4; + const VK_RMENU: usize = 0xa5; + + // Note that we don't currently generate events for modifier keys: + // when I tried that, powershell ended up inserting an @ symbol whenever + // I pressed a lone modifier, presumably because we don't have enough + // information to completely fill out the fields in the protocol. + // I tried routing the scan code down here, but it didn't change + // the behavior. + + let (vkey, uni) = match self { + Char(' ') => (VK_SPACE, 0x20), + Enter => (VK_RETURN, 0x0d), + Function(n) if *n >= 1 && *n <= 24 => ((*n as usize - 1) + VK_F1, 0x0), + /* + Shift => (VK_SHIFT, 0x0), + LeftShift => (VK_LSHIFT, 0x0), + RightShift => (VK_RSHIFT, 0x0), + Control => (VK_CONTROL, 0x0), + LeftControl => (VK_LCONTROL, 0x0), + RightControl => (VK_RCONTROL, 0x0), + Alt => (VK_MENU, 0x0), + LeftAlt => (VK_LMENU, 0x0), + RightAlt => (VK_RMENU, 0x0), + */ + _ => return None, + }; + + // + // defines the dwControlKeyState values + let mut control_key_state = 0; + const SHIFT_PRESSED: usize = 0x10; + const RIGHT_ALT_PRESSED: usize = 0x01; + const LEFT_ALT_PRESSED: usize = 0x02; + const LEFT_CTRL_PRESSED: usize = 0x04; + const RIGHT_CTRL_PRESSED: usize = 0x08; + + if mods.contains(Modifiers::SHIFT) { + control_key_state |= SHIFT_PRESSED; + } + if mods.contains(Modifiers::ALT) { + if vkey == VK_RMENU { + control_key_state |= RIGHT_ALT_PRESSED; + } else { + control_key_state |= LEFT_ALT_PRESSED; + } + } + if mods.contains(Modifiers::CTRL) { + if vkey == VK_RCONTROL { + control_key_state |= RIGHT_CTRL_PRESSED; + } else { + control_key_state |= LEFT_CTRL_PRESSED; + } + } + + let key_down = if is_down { 1 } else { 0 }; + + Some(format!( + "\u{1b}[{};;{};{};{}_", + vkey, uni, key_down, control_key_state + )) + } + + pub fn encode_up_down( + &self, + mods: Modifiers, + modes: KeyCodeEncodeModes, + is_down: bool, + ) -> Result { + if modes.encoding == KeyboardEncoding::Win32 { + if let Some(res) = self.encode_win32_input_mode(mods, is_down) { + return Ok(res); + } + } + + if !is_down { + return Ok(String::new()); + } + + self.encode(mods, modes) + } + /// Returns the xterm compatible byte sequence that represents this KeyCode /// and Modifier combination. pub fn encode(&self, mods: Modifiers, modes: KeyCodeEncodeModes) -> Result { diff --git a/window/src/os/windows/keycodes.rs b/window/src/os/windows/keycodes.rs index 46aac54aa..83a4ccda5 100644 --- a/window/src/os/windows/keycodes.rs +++ b/window/src/os/windows/keycodes.rs @@ -155,6 +155,7 @@ fn build_map() -> HashMap { (VK_CAPITAL, PhysKeyCode::CapsLock), (VK_MENU, PhysKeyCode::LeftAlt), (VK_LMENU, PhysKeyCode::LeftAlt), + (VK_CONTROL, PhysKeyCode::LeftControl), (VK_LCONTROL, PhysKeyCode::LeftControl), (VK_RWIN, PhysKeyCode::RightWindows), (VK_RSHIFT, PhysKeyCode::RightShift),