Skip to content

Commit

Permalink
input: generate win32-input-mode for a subset of keys
Browse files Browse the repository at this point in the history
This commit causes the terminal to emit win32-input-mode encoded key up
and down events for a limited subset of keys When win32-input-mode is
enabled.

We limit them to keys where we know the VK key code equivalent,
and where those keys are either not representable (eg: modifier
only key events), or may generate ambiguous output (eg: CTRL-SPACE
in different keyboard layouts).

However, in my experiments, modifier only key presses confuse powershell
and cause it to emit `@`, so I've disabled that in the code for now.

refs: #318
refs: #1509
refs: #1510
  • Loading branch information
wez committed Jan 6, 2022
1 parent 27d452a commit ce23ee9
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 8 deletions.
33 changes: 25 additions & 8 deletions term/src/terminalstate/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,49 @@ 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()?;

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)
}
}
105 changes: 105 additions & 0 deletions termwiz/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,111 @@ impl KeyCode {
)
}

/// <https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md>
/// 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<String> {
#![allow(dead_code)]

use KeyCode::*;

// <https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes>
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,
};

// <https://docs.microsoft.com/en-us/windows/console/key-event-record-str>
// 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<String> {
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<String> {
Expand Down
1 change: 1 addition & 0 deletions window/src/os/windows/keycodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ fn build_map() -> HashMap<WPARAM, PhysKeyCode> {
(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),
Expand Down

0 comments on commit ce23ee9

Please sign in to comment.