Skip to content

Commit ea30429

Browse files
committed
Fix escape sequences decoding on Windows
1 parent 7c6a1b3 commit ea30429

File tree

3 files changed

+62
-21
lines changed

3 files changed

+62
-21
lines changed

src/term/input_parser.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ impl InputParser {
2121
}
2222
}
2323

24-
pub fn parse_input<F>(&mut self, input: &[u8], is_raw_mode: bool, mut f: F)
25-
where
24+
pub fn parse_input<F>(
25+
&mut self,
26+
input: &[u8],
27+
is_raw_mode: bool,
28+
more: bool,
29+
mut f: F,
30+
) where
2631
F: FnMut(E),
2732
{
28-
let more = false;
2933
let mut i = 0;
3034
let mut consumed = 0;
3135

@@ -123,7 +127,14 @@ impl InputParser {
123127
}
124128
} else {
125129
// Most of CSI
126-
let len = parse_csi(&buf[i..], is_raw_mode, &mut f);
130+
let len = if let Some(len) =
131+
parse_csi(&buf[i..], is_raw_mode, &mut f)
132+
{
133+
len
134+
} else {
135+
// Not enough input to complete CSI sequence.
136+
break;
137+
};
127138
i += len;
128139
consumed = i;
129140
}
@@ -216,7 +227,8 @@ fn parse_char_key(
216227
Ok((i, Some(key)))
217228
}
218229

219-
fn parse_csi<F>(buf: &[u8], is_raw_mode: bool, f: F) -> usize
230+
/// Returns `None` is there is not enough input.
231+
fn parse_csi<F>(buf: &[u8], is_raw_mode: bool, f: F) -> Option<usize>
220232
where
221233
F: FnMut(E),
222234
{
@@ -236,19 +248,19 @@ where
236248
buf[i - 1]
237249
} else {
238250
log::error!("TODO: CSI is incomplete.");
239-
return i;
251+
return Some(i);
240252
}
241253
} else {
242-
log::error!("CSI sequence has wrong final.");
243-
return i;
254+
// Not enough input to complete CSI sequence.
255+
return None;
244256
};
245257

246258
match parse_csi_impl(buf, params, intermediates, final_, is_raw_mode, f) {
247259
Ok(()) => (),
248260
Err(err) => log::error!("CSI error: {}", err),
249261
}
250262

251-
i
263+
Some(i)
252264
}
253265

254266
fn parse_csi_impl<F>(

src/term/term_driver.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,9 @@ impl TermDriver {
143143
match rustix::io::read(stdin, Box::as_mut(&mut read_buf)) {
144144
Ok(read_count) => {
145145
let slice = &read_buf[..read_count];
146-
input_parser
147-
.parse_input(slice, true, |e| sender.send(e).log_ignore());
146+
input_parser.parse_input(slice, true, false, |e| {
147+
sender.send(e).log_ignore()
148+
});
148149
}
149150
Err(err) => log::error!("stdin(err): {:?}", err),
150151
}

src/term/windows.rs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,23 +90,20 @@ impl WinVt {
9090
}
9191

9292
fn decode_key_record<F: FnMut(InternalTermEvent)>(
93-
input_parser: &mut InputParser,
9493
record: &KEY_EVENT_RECORD,
9594
f: &mut F,
9695
) {
9796
use winapi::um::winuser::*;
9897

98+
let uchar = unsafe { *record.uChar.UnicodeChar() };
99+
99100
let modifiers = modifiers_from_ctrl_key_state(record.dwControlKeyState);
100101
let virtual_key_code = record.wVirtualKeyCode as i32;
101102

102103
// We normally ignore all key release events, but we will make an exception for an Alt key
103104
// release if it carries a u_char value, as this indicates an Alt code.
104-
let is_alt_code = virtual_key_code == VK_MENU
105-
&& record.bKeyDown == 0
106-
&& *unsafe { record.uChar.UnicodeChar() } != 0;
107-
if is_alt_code {
108-
let utf16 = *unsafe { record.uChar.UnicodeChar() };
109-
match utf16 {
105+
if is_alt_code(record) {
106+
match uchar {
110107
surrogate @ 0xD800..=0xDFFF => {
111108
log::error!("Unhandled surrogate key record.");
112109
return;
@@ -157,8 +154,7 @@ fn decode_key_record<F: FnMut(InternalTermEvent)>(
157154
VK_TAB if modifiers.contains(KeyModifiers::SHIFT) => Some(KeyCode::BackTab),
158155
VK_TAB => Some(KeyCode::Tab),
159156
_ => {
160-
let utf16 = *unsafe { record.uChar.UnicodeChar() };
161-
match utf16 {
157+
match uchar {
162158
0x00..=0x1f => {
163159
// Some key combinations generate either no u_char value or generate control
164160
// codes. To deliver back a KeyCode::Char(...) event we want to know which
@@ -193,6 +189,12 @@ fn decode_key_record<F: FnMut(InternalTermEvent)>(
193189
}
194190
}
195191

192+
fn is_alt_code(record: &KEY_EVENT_RECORD) -> bool {
193+
record.wVirtualKeyCode as i32 == winapi::um::winuser::VK_MENU
194+
&& record.bKeyDown == 0
195+
&& unsafe { *record.uChar.UnicodeChar() } != 0
196+
}
197+
196198
enum CharCase {
197199
LowerCase,
198200
UpperCase,
@@ -398,7 +400,30 @@ pub fn decode_input_records<F: FnMut(InternalTermEvent)>(
398400
for record in records {
399401
match record.EventType {
400402
KEY_EVENT => {
401-
decode_key_record(input_parser, unsafe { record.Event.KeyEvent() }, f)
403+
let record = unsafe { record.Event.KeyEvent() };
404+
let uchar = unsafe { *record.uChar.UnicodeChar() };
405+
406+
if record.bKeyDown == 0 && !is_alt_code(record) {
407+
// Ignore release events.
408+
//
409+
// If we want to support release key events on Windows we need to
410+
// handle:
411+
// - if the key is part of escape sequence: consume both press+release
412+
// events
413+
// - otherwise emit both press and release
414+
} else if (1..=0x7f).contains(&uchar)
415+
&& record.dwControlKeyState == 0
416+
&& record.bKeyDown == 1
417+
{
418+
let ch = uchar as u8 as char;
419+
let mut buf = [0u8; 4];
420+
let bytes = ch.encode_utf8(&mut buf).as_bytes();
421+
for _ in 0..record.wRepeatCount {
422+
input_parser.parse_input(bytes, true, true, &mut *f);
423+
}
424+
} else {
425+
decode_key_record(record, f);
426+
}
402427
}
403428
MOUSE_EVENT => decode_mouse_record(
404429
input_parser,
@@ -411,6 +436,9 @@ pub fn decode_input_records<F: FnMut(InternalTermEvent)>(
411436
_ => {}
412437
}
413438
}
439+
440+
// Process pending ESC character.
441+
input_parser.parse_input(b"", true, false, f);
414442
}
415443

416444
fn modifiers_from_ctrl_key_state(state: u32) -> KeyModifiers {

0 commit comments

Comments
 (0)