From 1ae85b23491e630a048da3f1ece9098cdf667d6e Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sat, 27 Jan 2018 16:25:34 -0800 Subject: [PATCH] send keyboard input to the pty --- Cargo.toml | 2 + src/main.rs | 12 ++- src/term/mod.rs | 84 +++++++++++++++++ src/xgfx.rs | 31 ++++++- src/xkeysyms.rs | 239 ++++++++++++++++++++++++++++++++++++++++++++++++ src/xwin.rs | 25 ++++- 6 files changed, 388 insertions(+), 5 deletions(-) create mode 100644 src/xkeysyms.rs diff --git a/Cargo.toml b/Cargo.toml index c21a0db9b..baf4a70cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ name = "wterm" version = "0.1.0" [dependencies] +bitflags = "1.0.1" failure = "0.1.1" freetype = "0.3.0" harfbuzz-sys = "0.1.15" @@ -23,5 +24,6 @@ features = [ "ewmh", "misc", "shm", + "keysyms", ] version = "0.2.0" diff --git a/src/main.rs b/src/main.rs index 5c3773e1e..14fbdd138 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ #[macro_use] extern crate failure; +#[macro_use] +extern crate bitflags; extern crate unicode_width; extern crate unicode_segmentation; extern crate harfbuzz_sys; @@ -26,6 +28,7 @@ use std::process::Command; use std::time::Duration; mod xgfx; +mod xkeysyms; mod font; use font::{Font, FontPattern, ftwrap}; @@ -57,7 +60,11 @@ fn dispatch_gui( } xcb::KEY_PRESS => { let key_press: &xcb::KeyPressEvent = unsafe { xcb::cast_event(&event) }; - println!("Key '{}' pressed", key_press.detail()); + window.key_down(key_press)?; + } + xcb::KEY_RELEASE => { + let key_press: &xcb::KeyPressEvent = unsafe { xcb::cast_event(&event) }; + window.key_up(key_press)?; } xcb::CLIENT_MESSAGE => { let msg: &xcb::ClientMessageEvent = unsafe { xcb::cast_event(&event) }; @@ -101,8 +108,7 @@ fn run() -> Result<(), Error> { initial_pixel_height, )?; - let mut cmd = Command::new("top"); - // cmd.arg("-l"); + let cmd = Command::new("zsh"); let child = slave.spawn_command(cmd)?; eprintln!("spawned: {:?}", child); diff --git a/src/term/mod.rs b/src/term/mod.rs index 2a58f0159..890cadc88 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1,11 +1,35 @@ //! Terminal model +use failure::Error; use std; use unicode_segmentation; use vte; pub mod color; +bitflags! { + #[derive(Default)] + pub struct KeyModifiers :u8{ + const CTRL = 1; + const ALT = 2; + const META = 4; + const SUPER = 8; + const SHIFT = 16; + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum KeyCode { + Char(char), + Unknown, + Control, + Alt, + Meta, + Super, + Hyper, + Shift, +} + #[derive(Debug, Clone, Copy)] pub struct CellAttributes { attributes: u16, @@ -328,6 +352,48 @@ impl TerminalState { &mut self.screen } } + + pub fn key_down( + &mut self, + key: KeyCode, + mods: KeyModifiers, + write: &mut W, + ) -> Result<(), Error> { + match key { + KeyCode::Char(c) => { + let adjusted = if mods.contains(KeyModifiers::CTRL) && c <= 0xff as char { + if mods.contains(KeyModifiers::SHIFT) { + // If shift is held we have C == 0x43 and want to translate + // that into 0x03 + (c as u8 - 0x40) as char + } else { + // If shift is not held we have c == 0x63 and want to translate + // that into 0x03 + (c as u8 - 0x60) as char + } + } else if mods.contains(KeyModifiers::ALT) && c <= 0xff as char { + (c as u8 | 0x80) as char + } else { + c + }; + + let mut buf = [0; 8]; + let encoded = adjusted.encode_utf8(&mut buf); + write.write(encoded.as_bytes())?; + Ok(()) + } + _ => Ok(()), + } + } + + pub fn key_up( + &mut self, + key: KeyCode, + mods: KeyModifiers, + write: &mut W, + ) -> Result<(), Error> { + Ok(()) + } } pub struct Terminal { @@ -390,6 +456,24 @@ impl Terminal { let len = screen.lines.len(); (width, &screen.lines[len - height..len]) } + + pub fn key_down( + &mut self, + key: KeyCode, + mods: KeyModifiers, + write: &mut W, + ) -> Result<(), Error> { + self.state.key_down(key, mods, write) + } + + pub fn key_up( + &mut self, + key: KeyCode, + mods: KeyModifiers, + write: &mut W, + ) -> Result<(), Error> { + self.state.key_up(key, mods, write) + } } #[derive(Debug)] diff --git a/src/xgfx.rs b/src/xgfx.rs index cb686a176..d4873be92 100644 --- a/src/xgfx.rs +++ b/src/xgfx.rs @@ -4,9 +4,12 @@ use std::ops::Deref; use std::result; use xcb; use xcb_util; +use xcb_util::ffi::keysyms::{xcb_key_press_lookup_keysym, xcb_key_symbols_alloc, + xcb_key_symbols_free, xcb_key_symbols_t}; use failure::{self, Error}; pub type Result = result::Result; +pub use xkeysyms::*; use super::term::color::RgbColor; @@ -15,6 +18,7 @@ pub struct Connection { screen_num: i32, atom_protocols: xcb::Atom, atom_delete: xcb::Atom, + keysyms: *mut xcb_key_symbols_t, } impl Deref for Connection { @@ -35,11 +39,14 @@ impl Connection { .get_reply()? .atom(); + let keysyms = unsafe { xcb_key_symbols_alloc(conn.get_raw_conn()) }; + Ok(Connection { conn, screen_num, atom_protocols, atom_delete, + keysyms, }) } @@ -54,6 +61,26 @@ impl Connection { pub fn atom_delete(&self) -> xcb::Atom { self.atom_delete } + + pub fn lookup_keysym(&self, event: &xcb::KeyPressEvent, shifted: bool) -> xcb::Keysym { + unsafe { + let sym = + xcb_key_press_lookup_keysym(self.keysyms, event.ptr, if shifted { 1 } else { 0 }); + if sym == 0 && shifted { + xcb_key_press_lookup_keysym(self.keysyms, event.ptr, 0) + } else { + sym + } + } + } +} + +impl Drop for Connection { + fn drop(&mut self) { + unsafe { + xcb_key_symbols_free(self.keysyms); + } + } } /// The X protocol allows referencing a number of drawable @@ -105,7 +132,9 @@ impl<'a> Window<'a> { &[ ( xcb::CW_EVENT_MASK, - xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_KEY_PRESS | xcb::EVENT_MASK_STRUCTURE_NOTIFY, + xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_KEY_PRESS | + xcb::EVENT_MASK_KEY_RELEASE | + xcb::EVENT_MASK_STRUCTURE_NOTIFY, ), ], ).request_check()?; diff --git a/src/xkeysyms.rs b/src/xkeysyms.rs new file mode 100644 index 000000000..89f88e9ee --- /dev/null +++ b/src/xkeysyms.rs @@ -0,0 +1,239 @@ +#![allow(non_upper_case_globals, dead_code)] +use xcb::KeyPressEvent; +use xcb::ffi::xproto::xcb_keysym_t; + +pub const XK_VoidSymbol: xcb_keysym_t = 0xffffff; +pub const XK_BackSpace: xcb_keysym_t = 0xff08; +pub const XK_Tab: xcb_keysym_t = 0xff09; +pub const XK_Linefeed: xcb_keysym_t = 0xff0a; +pub const XK_Clear: xcb_keysym_t = 0xff0b; +pub const XK_Return: xcb_keysym_t = 0xff0d; +pub const XK_Pause: xcb_keysym_t = 0xff13; +pub const XK_Scroll_Lock: xcb_keysym_t = 0xff14; +pub const XK_Sys_Req: xcb_keysym_t = 0xff15; +pub const XK_Escape: xcb_keysym_t = 0xff1b; +pub const XK_Delete: xcb_keysym_t = 0xffff; + +pub const XK_Home: xcb_keysym_t = 0xff50; +pub const XK_Left: xcb_keysym_t = 0xff51; +pub const XK_Up: xcb_keysym_t = 0xff52; +pub const XK_Right: xcb_keysym_t = 0xff53; +pub const XK_Down: xcb_keysym_t = 0xff54; +pub const XK_Prior: xcb_keysym_t = 0xff55; +pub const XK_Page_Up: xcb_keysym_t = 0xff55; +pub const XK_Next: xcb_keysym_t = 0xff56; +pub const XK_Page_Down: xcb_keysym_t = 0xff56; +pub const XK_End: xcb_keysym_t = 0xff57; +pub const XK_Begin: xcb_keysym_t = 0xff58; + +pub const XK_KP_Space: xcb_keysym_t = 0xff80; +pub const XK_KP_Tab: xcb_keysym_t = 0xff89; +pub const XK_KP_Enter: xcb_keysym_t = 0xff8d; +pub const XK_KP_F1: xcb_keysym_t = 0xff91; +pub const XK_KP_F2: xcb_keysym_t = 0xff92; +pub const XK_KP_F3: xcb_keysym_t = 0xff93; +pub const XK_KP_F4: xcb_keysym_t = 0xff94; +pub const XK_KP_Home: xcb_keysym_t = 0xff95; +pub const XK_KP_Left: xcb_keysym_t = 0xff96; +pub const XK_KP_Up: xcb_keysym_t = 0xff97; +pub const XK_KP_Right: xcb_keysym_t = 0xff98; +pub const XK_KP_Down: xcb_keysym_t = 0xff99; +pub const XK_KP_Prior: xcb_keysym_t = 0xff9a; +pub const XK_KP_Page_Up: xcb_keysym_t = 0xff9a; +pub const XK_KP_Next: xcb_keysym_t = 0xff9b; +pub const XK_KP_Page_Down: xcb_keysym_t = 0xff9b; +pub const XK_KP_End: xcb_keysym_t = 0xff9c; +pub const XK_KP_Begin: xcb_keysym_t = 0xff9d; +pub const XK_KP_Insert: xcb_keysym_t = 0xff9e; +pub const XK_KP_Delete: xcb_keysym_t = 0xff9f; +pub const XK_KP_Equal: xcb_keysym_t = 0xffbd; +pub const XK_KP_Multiply: xcb_keysym_t = 0xffaa; +pub const XK_KP_Add: xcb_keysym_t = 0xffab; +pub const XK_KP_Separator: xcb_keysym_t = 0xffac; +pub const XK_KP_Subtract: xcb_keysym_t = 0xffad; +pub const XK_KP_Decimal: xcb_keysym_t = 0xffae; +pub const XK_KP_Divide: xcb_keysym_t = 0xffaf; + +pub const XK_KP_0: xcb_keysym_t = 0xffb0; +pub const XK_KP_1: xcb_keysym_t = 0xffb1; +pub const XK_KP_2: xcb_keysym_t = 0xffb2; +pub const XK_KP_3: xcb_keysym_t = 0xffb3; +pub const XK_KP_4: xcb_keysym_t = 0xffb4; +pub const XK_KP_5: xcb_keysym_t = 0xffb5; +pub const XK_KP_6: xcb_keysym_t = 0xffb6; +pub const XK_KP_7: xcb_keysym_t = 0xffb7; +pub const XK_KP_8: xcb_keysym_t = 0xffb8; +pub const XK_KP_9: xcb_keysym_t = 0xffb9; + +pub const XK_F1: xcb_keysym_t = 0xffbe; +pub const XK_F2: xcb_keysym_t = 0xffbf; +pub const XK_F3: xcb_keysym_t = 0xffc0; +pub const XK_F4: xcb_keysym_t = 0xffc1; +pub const XK_F5: xcb_keysym_t = 0xffc2; +pub const XK_F6: xcb_keysym_t = 0xffc3; +pub const XK_F7: xcb_keysym_t = 0xffc4; +pub const XK_F8: xcb_keysym_t = 0xffc5; +pub const XK_F9: xcb_keysym_t = 0xffc6; +pub const XK_F10: xcb_keysym_t = 0xffc7; +pub const XK_F11: xcb_keysym_t = 0xffc8; +pub const XK_F12: xcb_keysym_t = 0xffc9; + +pub const XK_Shift_L: xcb_keysym_t = 0xffe1; +pub const XK_Shift_R: xcb_keysym_t = 0xffe2; +pub const XK_Control_L: xcb_keysym_t = 0xffe3; +pub const XK_Control_R: xcb_keysym_t = 0xffe4; +pub const XK_Caps_Lock: xcb_keysym_t = 0xffe5; +pub const XK_Shift_Lock: xcb_keysym_t = 0xffe6; + +pub const XK_Meta_L: xcb_keysym_t = 0xffe7; +pub const XK_Meta_R: xcb_keysym_t = 0xffe8; +pub const XK_Alt_L: xcb_keysym_t = 0xffe9; +pub const XK_Alt_R: xcb_keysym_t = 0xffea; +pub const XK_Super_L: xcb_keysym_t = 0xffeb; +pub const XK_Super_R: xcb_keysym_t = 0xffec; +pub const XK_Hyper_L: xcb_keysym_t = 0xffed; +pub const XK_Hyper_R: xcb_keysym_t = 0xffee; + +pub const XK_space: xcb_keysym_t = 0x0020; +pub const XK_exclam: xcb_keysym_t = 0x0021; +pub const XK_quotedbl: xcb_keysym_t = 0x0022; +pub const XK_numbersign: xcb_keysym_t = 0x0023; +pub const XK_dollar: xcb_keysym_t = 0x0024; +pub const XK_percent: xcb_keysym_t = 0x0025; +pub const XK_ampersand: xcb_keysym_t = 0x0026; +pub const XK_apostrophe: xcb_keysym_t = 0x0027; +pub const XK_quoteright: xcb_keysym_t = 0x0027; +pub const XK_parenleft: xcb_keysym_t = 0x0028; +pub const XK_parenright: xcb_keysym_t = 0x0029; +pub const XK_asterisk: xcb_keysym_t = 0x002a; +pub const XK_plus: xcb_keysym_t = 0x002b; +pub const XK_comma: xcb_keysym_t = 0x002c; +pub const XK_minus: xcb_keysym_t = 0x002d; +pub const XK_period: xcb_keysym_t = 0x002e; +pub const XK_slash: xcb_keysym_t = 0x002f; +pub const XK_0: xcb_keysym_t = 0x0030; +pub const XK_1: xcb_keysym_t = 0x0031; +pub const XK_2: xcb_keysym_t = 0x0032; +pub const XK_3: xcb_keysym_t = 0x0033; +pub const XK_4: xcb_keysym_t = 0x0034; +pub const XK_5: xcb_keysym_t = 0x0035; +pub const XK_6: xcb_keysym_t = 0x0036; +pub const XK_7: xcb_keysym_t = 0x0037; +pub const XK_8: xcb_keysym_t = 0x0038; +pub const XK_9: xcb_keysym_t = 0x0039; +pub const XK_colon: xcb_keysym_t = 0x003a; +pub const XK_semicolon: xcb_keysym_t = 0x003b; +pub const XK_less: xcb_keysym_t = 0x003c; +pub const XK_equal: xcb_keysym_t = 0x003d; +pub const XK_greater: xcb_keysym_t = 0x003e; +pub const XK_question: xcb_keysym_t = 0x003f; +pub const XK_at: xcb_keysym_t = 0x0040; +pub const XK_A: xcb_keysym_t = 0x0041; +pub const XK_B: xcb_keysym_t = 0x0042; +pub const XK_C: xcb_keysym_t = 0x0043; +pub const XK_D: xcb_keysym_t = 0x0044; +pub const XK_E: xcb_keysym_t = 0x0045; +pub const XK_F: xcb_keysym_t = 0x0046; +pub const XK_G: xcb_keysym_t = 0x0047; +pub const XK_H: xcb_keysym_t = 0x0048; +pub const XK_I: xcb_keysym_t = 0x0049; +pub const XK_J: xcb_keysym_t = 0x004a; +pub const XK_K: xcb_keysym_t = 0x004b; +pub const XK_L: xcb_keysym_t = 0x004c; +pub const XK_M: xcb_keysym_t = 0x004d; +pub const XK_N: xcb_keysym_t = 0x004e; +pub const XK_O: xcb_keysym_t = 0x004f; +pub const XK_P: xcb_keysym_t = 0x0050; +pub const XK_Q: xcb_keysym_t = 0x0051; +pub const XK_R: xcb_keysym_t = 0x0052; +pub const XK_S: xcb_keysym_t = 0x0053; +pub const XK_T: xcb_keysym_t = 0x0054; +pub const XK_U: xcb_keysym_t = 0x0055; +pub const XK_V: xcb_keysym_t = 0x0056; +pub const XK_W: xcb_keysym_t = 0x0057; +pub const XK_X: xcb_keysym_t = 0x0058; +pub const XK_Y: xcb_keysym_t = 0x0059; +pub const XK_Z: xcb_keysym_t = 0x005a; +pub const XK_bracketleft: xcb_keysym_t = 0x005b; +pub const XK_backslash: xcb_keysym_t = 0x005c; +pub const XK_bracketright: xcb_keysym_t = 0x005d; +pub const XK_asciicircum: xcb_keysym_t = 0x005e; +pub const XK_underscore: xcb_keysym_t = 0x005f; +pub const XK_grave: xcb_keysym_t = 0x0060; +pub const XK_quoteleft: xcb_keysym_t = 0x0060; +pub const XK_a: xcb_keysym_t = 0x0061; +pub const XK_b: xcb_keysym_t = 0x0062; +pub const XK_c: xcb_keysym_t = 0x0063; +pub const XK_d: xcb_keysym_t = 0x0064; +pub const XK_e: xcb_keysym_t = 0x0065; +pub const XK_f: xcb_keysym_t = 0x0066; +pub const XK_g: xcb_keysym_t = 0x0067; +pub const XK_h: xcb_keysym_t = 0x0068; +pub const XK_i: xcb_keysym_t = 0x0069; +pub const XK_j: xcb_keysym_t = 0x006a; +pub const XK_k: xcb_keysym_t = 0x006b; +pub const XK_l: xcb_keysym_t = 0x006c; +pub const XK_m: xcb_keysym_t = 0x006d; +pub const XK_n: xcb_keysym_t = 0x006e; +pub const XK_o: xcb_keysym_t = 0x006f; +pub const XK_p: xcb_keysym_t = 0x0070; +pub const XK_q: xcb_keysym_t = 0x0071; +pub const XK_r: xcb_keysym_t = 0x0072; +pub const XK_s: xcb_keysym_t = 0x0073; +pub const XK_t: xcb_keysym_t = 0x0074; +pub const XK_u: xcb_keysym_t = 0x0075; +pub const XK_v: xcb_keysym_t = 0x0076; +pub const XK_w: xcb_keysym_t = 0x0077; +pub const XK_x: xcb_keysym_t = 0x0078; +pub const XK_y: xcb_keysym_t = 0x0079; +pub const XK_z: xcb_keysym_t = 0x007a; +pub const XK_braceleft: xcb_keysym_t = 0x007b; +pub const XK_bar: xcb_keysym_t = 0x007c; +pub const XK_braceright: xcb_keysym_t = 0x007d; +pub const XK_asciitilde: xcb_keysym_t = 0x007e; + +use term::KeyCode; +use term::KeyModifiers; + +impl From for KeyCode { + fn from(k: xcb_keysym_t) -> Self { + match k { + XK_space...XK_asciitilde => { + // This range overlaps with ascii + KeyCode::Char(k as u8 as char) + } + XK_BackSpace | XK_Tab | XK_Linefeed | XK_Return | XK_Escape => { + KeyCode::Char((k & 0xff) as u8 as char) + } + XK_Control_L | XK_Control_R => KeyCode::Control, + XK_Alt_L | XK_Alt_R => KeyCode::Alt, + XK_Meta_L | XK_Meta_R => KeyCode::Meta, + XK_Super_L | XK_Super_R => KeyCode::Super, + XK_Hyper_L | XK_Hyper_R => KeyCode::Hyper, + XK_Shift_L | XK_Shift_R => KeyCode::Shift, + _ => KeyCode::Unknown, + } + } +} + +pub fn modifiers(event: &KeyPressEvent) -> KeyModifiers { + use xcb::xproto::*; + + let mut mods = KeyModifiers::default(); + let state = event.state() as u32; + + if state & MOD_MASK_SHIFT != 0 { + mods |= KeyModifiers::SHIFT; + } + if state & MOD_MASK_CONTROL != 0 { + mods |= KeyModifiers::CTRL; + } + if state & MOD_MASK_1 != 0 { + mods |= KeyModifiers::ALT; + } + if state & MOD_MASK_4 != 0 { + mods |= KeyModifiers::SUPER; + } + + mods +} diff --git a/src/xwin.rs b/src/xwin.rs index a04f1385c..b993204ef 100644 --- a/src/xwin.rs +++ b/src/xwin.rs @@ -5,8 +5,10 @@ use std::io::Read; use std::mem; use std::process::Child; use std::slice; -use term; +use term::{self, KeyCode, KeyModifiers}; +use xcb; use xgfx::{self, Connection, Drawable}; +use xkeysyms; pub struct TerminalWindow<'a> { window: xgfx::Window<'a>, @@ -324,4 +326,25 @@ impl<'a> TerminalWindow<'a> { pub fn need_paint(&self) -> bool { self.need_paint } + + fn decode_key(&self, event: &xcb::KeyPressEvent) -> (KeyCode, KeyModifiers) { + let mods = xkeysyms::modifiers(event); + let sym = self.conn.lookup_keysym( + event, + mods.contains(KeyModifiers::SHIFT), + ); + (sym.into(), mods) + } + + pub fn key_down(&mut self, event: &xcb::KeyPressEvent) -> Result<(), Error> { + let (code, mods) = self.decode_key(event); + println!("Key pressed {:?} {:?}", code, mods); + self.terminal.key_down(code, mods, &mut self.pty) + } + + pub fn key_up(&mut self, event: &xcb::KeyPressEvent) -> Result<(), Error> { + let (code, mods) = self.decode_key(event); + println!("Key released {:?} {:?}", code, mods); + self.terminal.key_up(code, mods, &mut self.pty) + } }