Skip to content

Commit b32f88e

Browse files
committed
improve keyboard encoding and some escape sequences
1 parent 4deb736 commit b32f88e

File tree

4 files changed

+171
-15
lines changed

4 files changed

+171
-15
lines changed

src/keyboard.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use embassy_rp::peripherals::I2C1;
77
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
88
use embassy_sync::lazy_lock::LazyLock;
99
use embassy_sync::mutex::Mutex;
10-
use embassy_time::{Duration, Instant, Ticker};
10+
use embassy_time::{Duration, Instant, Ticker, with_timeout};
1111

1212
static BATTERY_PCT: AtomicU8 = AtomicU8::new(0xff);
1313

@@ -353,8 +353,14 @@ pub async fn keyboard_reader(
353353
}
354354
_ => {
355355
let proc = current_proc();
356-
proc.key_input(key).await;
357-
proc.render().await;
356+
if let Err(_) = with_timeout(Duration::from_millis(100), async {
357+
proc.key_input(key).await;
358+
proc.render().await;
359+
})
360+
.await
361+
{
362+
log::info!("timeout sending key to proc {}", proc.name());
363+
}
358364
}
359365
}
360366
}

src/net.rs

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::Irqs;
22
use crate::config::CONFIG;
3-
use crate::keyboard::{Key, KeyReport, KeyState};
3+
use crate::keyboard::{Key, KeyReport, KeyState, Modifiers};
44
use crate::net::alloc::string::ToString;
55
use crate::process::{LineEditor, Process, assign_proc, assign_proc_if};
66
use crate::rng::WezTermRng;
@@ -22,6 +22,7 @@ use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
2222
use embassy_sync::channel::Channel;
2323
use embassy_sync::lazy_lock::LazyLock;
2424
use embassy_sync::mutex::Mutex;
25+
use embassy_time::{Duration, with_timeout};
2526
use embedded_io_async::{Read, Write as _};
2627
use rand_core::RngCore;
2728
use static_cell::StaticCell;
@@ -141,8 +142,11 @@ pub async fn setup_wifi(
141142
STACK.get().lock().await.replace(stack);
142143
}
143144

145+
const TIMEOUT_DURATION: Duration = Duration::from_secs(10);
146+
144147
async fn ssh_channel_task(mut channel: ChanInOut<'_, '_>, key_rx: Arc<Channel<CS, KeyReport, 4>>) {
145148
log::info!("ssh_channel_task waiting for output");
149+
146150
loop {
147151
let mut buf = [0u8; 1024];
148152

@@ -166,24 +170,73 @@ async fn ssh_channel_task(mut channel: ChanInOut<'_, '_>, key_rx: Arc<Channel<CS
166170
Either::Second(key_report) => {
167171
// Encode a key with xterm style keyboard encoding.
168172
// FIXME: woefully incomplete!
173+
174+
if key_report.modifiers == Modifiers::CTRL {
175+
if let Key::Char(c) = key_report.key {
176+
if let Some(mapped) = ctrl_mapping(c) {
177+
log::info!(
178+
"doing mapped ctrl {} -> {}",
179+
c.escape_debug(),
180+
mapped.escape_debug()
181+
);
182+
let mut buf = [0u8; 4];
183+
log::info!(
184+
"{:?}",
185+
with_timeout(
186+
TIMEOUT_DURATION,
187+
channel.write_all(mapped.encode_utf8(&mut buf).as_bytes()),
188+
)
189+
.await
190+
);
191+
continue;
192+
}
193+
}
194+
}
195+
196+
if key_report.modifiers == Modifiers::ALT {
197+
// Alt sends escape first
198+
log::info!("ALT -> send escape first");
199+
log::info!(
200+
"{:?}",
201+
with_timeout(TIMEOUT_DURATION, channel.write_all(b"\x1b")).await
202+
);
203+
}
204+
169205
if let Key::Char(c) = key_report.key {
170206
let mut buf = [0u8; 4];
171-
channel
172-
.write_all(c.encode_utf8(&mut buf).as_bytes())
207+
log::info!("just sending {} as-is", c.escape_debug());
208+
log::info!(
209+
"{:?}",
210+
with_timeout(
211+
TIMEOUT_DURATION,
212+
channel.write_all(c.encode_utf8(&mut buf).as_bytes()),
213+
)
173214
.await
174-
.ok();
215+
);
175216
} else {
176217
let text = match key_report.key {
177218
Key::Enter => "\n",
178219
Key::BackSpace => "\u{7f}",
179220
Key::Tab => "\t",
180221
Key::Escape => "\u{1b}",
222+
Key::Up => "\u{1b}[A",
223+
Key::Down => "\u{1b}[B",
224+
Key::Right => "\u{1b}[C",
225+
Key::Left => "\u{1b}[D",
226+
Key::Home => "\u{1b}[H",
227+
Key::End => "\u{1b}[F",
228+
Key::PageUp => "\u{1b}[5~",
229+
Key::PageDown => "\u{1b}[6~",
181230
Key::None | Key::Char(_) => continue,
182231
_ => {
183232
continue;
184233
}
185234
};
186-
channel.write_all(text.as_bytes()).await.ok();
235+
log::info!("{key_report:?} -> {}", text.escape_debug());
236+
log::info!(
237+
"{:?}",
238+
with_timeout(TIMEOUT_DURATION, channel.write_all(text.as_bytes())).await
239+
);
187240
}
188241
}
189242
}
@@ -216,7 +269,6 @@ async fn ssh_session_task(host: String, command: Option<String>) {
216269
.await
217270
{
218271
Ok(()) => {
219-
use embassy_futures::join::*;
220272
use embassy_futures::select::*;
221273

222274
let key_channel = Arc::new(Channel::new());
@@ -374,7 +426,7 @@ async fn ssh_session_task(host: String, command: Option<String>) {
374426
Ok::<(), sunset::Error>(())
375427
};
376428

377-
let res = select(runner, join(ssh_ticker, spawn_session_future)).await;
429+
let res = select(runner, select(ssh_ticker, spawn_session_future)).await;
378430
log::info!("ssh result is {res:?}");
379431
assign_proc(prior_proc).await;
380432
}
@@ -416,6 +468,9 @@ async fn prompt_for_input(prompt: &str, kind: PromptKind) -> Option<String> {
416468

417469
#[async_trait::async_trait(?Send)]
418470
impl Process for PromptProc {
471+
fn name(&self) -> &str {
472+
"prompt"
473+
}
419474
async fn render(&self) {
420475
let mut screen = SCREEN.get().lock().await;
421476
match self.kind {
@@ -496,6 +551,9 @@ struct SshProcess {
496551

497552
#[async_trait::async_trait(?Send)]
498553
impl Process for SshProcess {
554+
fn name(&self) -> &str {
555+
"ssh"
556+
}
499557
async fn render(&self) {}
500558
fn un_prompt(&self, _screen: &mut Screen) {}
501559
async fn key_input(&self, key: KeyReport) {
@@ -534,3 +592,51 @@ async fn wifi_scanner(mut control: Control<'static>) {
534592
}
535593
}
536594
*/
595+
596+
/// Taken from wezterm-input-types
597+
/// Map c to its Ctrl equivalent.
598+
/// In theory, this mapping is simply translating alpha characters
599+
/// to upper case and then masking them by 0x1f, but xterm inherits
600+
/// some built-in translation from legacy X11 so that are some
601+
/// aliased mappings and a couple that might be technically tied
602+
/// to US keyboard layout (particularly the punctuation characters
603+
/// produced in combination with SHIFT) that may not be 100%
604+
/// the right thing to do here for users with non-US layouts.
605+
fn ctrl_mapping(c: char) -> Option<char> {
606+
Some(match c {
607+
'@' | '`' | ' ' | '2' => '\x00',
608+
'A' | 'a' => '\x01',
609+
'B' | 'b' => '\x02',
610+
'C' | 'c' => '\x03',
611+
'D' | 'd' => '\x04',
612+
'E' | 'e' => '\x05',
613+
'F' | 'f' => '\x06',
614+
'G' | 'g' => '\x07',
615+
'H' | 'h' => '\x08',
616+
'I' | 'i' => '\x09',
617+
'J' | 'j' => '\x0a',
618+
'K' | 'k' => '\x0b',
619+
'L' | 'l' => '\x0c',
620+
'M' | 'm' => '\x0d',
621+
'N' | 'n' => '\x0e',
622+
'O' | 'o' => '\x0f',
623+
'P' | 'p' => '\x10',
624+
'Q' | 'q' => '\x11',
625+
'R' | 'r' => '\x12',
626+
'S' | 's' => '\x13',
627+
'T' | 't' => '\x14',
628+
'U' | 'u' => '\x15',
629+
'V' | 'v' => '\x16',
630+
'W' | 'w' => '\x17',
631+
'X' | 'x' => '\x18',
632+
'Y' | 'y' => '\x19',
633+
'Z' | 'z' => '\x1a',
634+
'[' | '3' | '{' => '\x1b',
635+
'\\' | '4' | '|' => '\x1c',
636+
']' | '5' | '}' => '\x1d',
637+
'^' | '6' | '~' => '\x1e',
638+
'_' | '7' | '/' => '\x1f',
639+
'8' | '?' => '\x7f', // `Delete`
640+
_ => return None,
641+
})
642+
}

src/process.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub trait Process {
5656
async fn key_input(&self, key: KeyReport);
5757
async fn render(&self);
5858

59+
fn name(&self) -> &str;
60+
5961
// Erase whatever prompt may have been printed
6062
fn un_prompt(&self, _screen: &mut Screen) {}
6163
}
@@ -137,6 +139,9 @@ impl LocalShell {
137139

138140
#[async_trait::async_trait(?Send)]
139141
impl Process for LocalShell {
142+
fn name(&self) -> &str {
143+
"shell"
144+
}
140145
async fn render(&self) {
141146
let mut screen = SCREEN.get().lock().await;
142147
let command = self.command.lock().await;

src/screen.rs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,18 @@ impl VTActor for ScreenModel {
258258
self.cursor_y.0 += 1;
259259
self.check_scroll();
260260
}
261+
b'\x08' => {
262+
// Backspace
263+
// FIXME: margins!
264+
if self.cursor_x == 0 {
265+
self.line_log_mut(self.cursor_y).unwrap().needs_paint = true;
266+
self.cursor_y.0 = self.cursor_y.0.saturating_sub(1);
267+
self.cursor_x = self.width;
268+
} else {
269+
self.cursor_x -= 1;
270+
}
271+
self.line_log_mut(self.cursor_y).unwrap().needs_paint = true;
272+
}
261273
unhandled => {
262274
log::info!("c0/c1: unhandled {unhandled}");
263275
}
@@ -274,9 +286,22 @@ impl VTActor for ScreenModel {
274286
ignored_excess_intermediates: bool,
275287
byte: u8,
276288
) {
277-
log::info!(
278-
"esc: {params:?} intermediates={intermediates:?} ign={ignored_excess_intermediates} byte={byte}"
279-
);
289+
match byte {
290+
b'>' => {
291+
// DecNormalKeyPad
292+
}
293+
b'\\' => {
294+
// StringTerminator - terminator a previous OSC most likely
295+
}
296+
b'=' => {
297+
// DecApplicationKeyPad
298+
}
299+
_ => {
300+
log::info!(
301+
"esc: {params:?} intermediates={intermediates:?} ign={ignored_excess_intermediates} byte={byte}"
302+
);
303+
}
304+
}
280305
}
281306
fn csi_dispatch(&mut self, params: &[CsiParam], truncated: bool, byte: u8) {
282307
log::debug!("csi: {params:?} truncated={truncated} byte={byte}");
@@ -315,9 +340,15 @@ impl VTActor for ScreenModel {
315340
self.current_attributes.set(Attributes::BOLD, false);
316341
self.current_attributes.set(Attributes::HALF_BRIGHT, true);
317342
}
343+
CsiParam::Integer(3) => {
344+
// ITALIC
345+
}
318346
CsiParam::Integer(4) => {
319347
self.current_attributes.set(Attributes::UNDERLINE, true);
320348
}
349+
CsiParam::Integer(7) => {
350+
self.current_attributes.set(Attributes::REVERSE, true);
351+
}
321352
CsiParam::Integer(9) => {
322353
self.current_attributes
323354
.set(Attributes::STRIKE_THROUGH, true);
@@ -326,9 +357,15 @@ impl VTActor for ScreenModel {
326357
self.current_attributes.set(Attributes::BOLD, false);
327358
self.current_attributes.set(Attributes::HALF_BRIGHT, false);
328359
}
360+
CsiParam::Integer(23) => {
361+
// !ITALIC
362+
}
329363
CsiParam::Integer(24) => {
330364
self.current_attributes.set(Attributes::UNDERLINE, false);
331365
}
366+
CsiParam::Integer(27) => {
367+
self.current_attributes.set(Attributes::REVERSE, false);
368+
}
332369
CsiParam::Integer(29) => {
333370
self.current_attributes
334371
.set(Attributes::STRIKE_THROUGH, false);
@@ -351,8 +388,10 @@ impl VTActor for ScreenModel {
351388
self.current_color &= 0x0f;
352389
self.current_color |= ((bg - 39) as u8) << 4;
353390
}
354-
_ => {
355-
log::info!("CSI SGR {p:?} not handled");
391+
p => {
392+
log::info!(
393+
"csi: {params:?} truncated={truncated} byte={byte}; SGR {p:?} not handled"
394+
);
356395
}
357396
}
358397
}

0 commit comments

Comments
 (0)