diff --git a/data/keyboards/de.yaml b/data/keyboards/de.yaml index 66566289609a1fd4074bfe01b6bcb8e6546bb111..06687ebfc09b3588115ac22aea86d0f5a87b4025 100644 --- a/data/keyboards/de.yaml +++ b/data/keyboards/de.yaml @@ -45,7 +45,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: "erase" preferences: action: "show_prefs" outline: "special" diff --git a/data/keyboards/de_wide.yaml b/data/keyboards/de_wide.yaml index 1f6385573cb6de8f4048c58e2a3cabdf0ab7aa11..dc5dec0f482df8455623814c38c23c1d6a071f0d 100644 --- a/data/keyboards/de_wide.yaml +++ b/data/keyboards/de_wide.yaml @@ -45,7 +45,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: "erase" preferences: action: "show_prefs" outline: "special" diff --git a/data/keyboards/el.yaml b/data/keyboards/el.yaml index f24b207a72decce25510735c67a4eeb98816007a..6279f75be87873d8bde0712194fa2d2e43844f4e 100644 --- a/data/keyboards/el.yaml +++ b/data/keyboards/el.yaml @@ -46,7 +46,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: "erase" preferences: action: "show_prefs" outline: "altline" diff --git a/data/keyboards/es.yaml b/data/keyboards/es.yaml index bc7ba73f74a402d7f055eccc07d1d203bac7764f..7343e7b349a11b6022d7c0b183a8445c757c9601 100644 --- a/data/keyboards/es.yaml +++ b/data/keyboards/es.yaml @@ -44,7 +44,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: "erase" preferences: action: "show_prefs" outline: "default" diff --git a/data/keyboards/fi.yaml b/data/keyboards/fi.yaml index ec972f47eeb4401cee7e585e8cc537af78efe830..25493d705ce7ae3cdafd57124b98363ca1767ca3 100644 --- a/data/keyboards/fi.yaml +++ b/data/keyboards/fi.yaml @@ -39,7 +39,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: "erase" preferences: action: "show_prefs" outline: "altline" diff --git a/data/keyboards/it.yaml b/data/keyboards/it.yaml index 7a05ed461d9cc2cbe60f14958905bff7027ed1eb..089304722d54cd2cb403a9ece840e0685fd9ac3c 100644 --- a/data/keyboards/it.yaml +++ b/data/keyboards/it.yaml @@ -46,7 +46,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: "erase" preferences: action: "show_prefs" outline: "default" diff --git a/data/keyboards/jp+kana.yaml b/data/keyboards/jp+kana.yaml index 076a7db1c0aea08b5fc82073c38264841f574d25..b129d3d745b2e1180aee24abf5df4eba90f52c7b 100644 --- a/data/keyboards/jp+kana.yaml +++ b/data/keyboards/jp+kana.yaml @@ -195,7 +195,7 @@ buttons: BackSpace: outline: "wide" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: erase Return: outline: "wide" icon: "key-enter" diff --git a/data/keyboards/jp+kana_wide.yaml b/data/keyboards/jp+kana_wide.yaml index 684cedfc3f790d30352ab1c274c944c1f35bdd71..8f0c536e5381aafce58de34714ef460e03b6c847 100644 --- a/data/keyboards/jp+kana_wide.yaml +++ b/data/keyboards/jp+kana_wide.yaml @@ -195,7 +195,7 @@ buttons: BackSpace: outline: "wide" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: erase Return: outline: "wide" icon: "key-enter" diff --git a/data/keyboards/no.yaml b/data/keyboards/no.yaml index 27ff21ff2b8745ea4102172196ff77dc015bdac2..44de74cc3ee6f04f3e1160901ca8756c6ba2e0af 100644 --- a/data/keyboards/no.yaml +++ b/data/keyboards/no.yaml @@ -39,7 +39,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: erase preferences: action: "show_prefs" outline: "altline" diff --git a/data/keyboards/number.yaml b/data/keyboards/number.yaml index f180a2623a0df031149abcd37459ab857836d4c0..8b42ebb2fa2bc97c975d1505153c0ba02087b71e 100644 --- a/data/keyboards/number.yaml +++ b/data/keyboards/number.yaml @@ -16,7 +16,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: erase space: outline: spaceline text: " " diff --git a/data/keyboards/se.yaml b/data/keyboards/se.yaml index 0a0a127fd359b99d70400fc35b973de4b372a7b2..b51f49adafb989ebd7678df2051b042155c3c648 100644 --- a/data/keyboards/se.yaml +++ b/data/keyboards/se.yaml @@ -39,7 +39,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: erase preferences: action: "show_prefs" outline: "altline" diff --git a/data/keyboards/terminal.yaml b/data/keyboards/terminal.yaml index 6fb4854466e8a099c0a12497de031bfca7dec718..0e232fa78db853c7f8066ec7986b488c816cdce8 100644 --- a/data/keyboards/terminal.yaml +++ b/data/keyboards/terminal.yaml @@ -45,7 +45,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: erase preferences: action: "show_prefs" outline: "special" diff --git a/data/keyboards/us.yaml b/data/keyboards/us.yaml index d150c48099eee8a949b3e1c7deaa8a35fd280b22..7a7f4dd6d7a414a0000d293c7847980476db8de2 100644 --- a/data/keyboards/us.yaml +++ b/data/keyboards/us.yaml @@ -39,9 +39,9 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: erase preferences: - action: "show_prefs" + action: show_prefs outline: "special" icon: "keyboard-mode-symbolic" show_numbers: diff --git a/data/keyboards/us_wide.yaml b/data/keyboards/us_wide.yaml index 654db8f881e5be7fb024661ea30633eb010cf4f7..cf349c00c01f0890e9d071a66cc5a33f88cf4915 100644 --- a/data/keyboards/us_wide.yaml +++ b/data/keyboards/us_wide.yaml @@ -39,7 +39,7 @@ buttons: BackSpace: outline: "altline" icon: "edit-clear-symbolic" - keysym: "BackSpace" + action: "erase" preferences: action: "show_prefs" outline: "special" diff --git a/src/action.rs b/src/action.rs index 97e32dc7456c32ae80e97550346fc21d83f823a8..98bdcd8355bf3852966dfded08d84d4cdeb973ea 100644 --- a/src/action.rs +++ b/src/action.rs @@ -31,10 +31,13 @@ pub enum Action { SetModifier(Modifier), /// Submit some text Submit { - /// Text to submit with input-method + /// Text to submit with input-method. + /// If None, then keys are to be submitted instead. text: Option<CString>, /// The key events this symbol submits when submitting text is not possible keys: Vec<KeySym>, }, + /// Erase a position behind the cursor + Erase, ShowPreferences, } diff --git a/src/data.rs b/src/data.rs index 5b2c4c9d466d013cca0914d0968a19e813c04960..8484c7b41027ae3c4c7804312a3ee026cc9c2654 100644 --- a/src/data.rs +++ b/src/data.rs @@ -15,6 +15,7 @@ use std::vec::Vec; use xkbcommon::xkb; +use ::action; use ::keyboard::{ KeyState, PressType, generate_keymap, generate_keycodes, FormattingError @@ -264,6 +265,9 @@ enum Action { SetView(String), #[serde(rename="show_prefs")] ShowPrefs, + /// Remove last character + #[serde(rename="erase")] + Erase, } #[derive(Debug, Clone, Deserialize, PartialEq)] @@ -386,6 +390,10 @@ impl Layout { ) }).collect() }, + action::Action::Erase => vec![ + *keymap.get("BackSpace") + .expect(&format!("BackSpace missing from keymap")), + ], _ => Vec::new(), }; ( @@ -558,6 +566,7 @@ fn create_action<H: logging::Handler>( SubmitData::Action( Action::ShowPrefs ) => ::action::Action::ShowPreferences, + SubmitData::Action(Action::Erase) => action::Action::Erase, SubmitData::Keysym(keysym) => ::action::Action::Submit { text: None, keys: vec!(::action::KeySym( @@ -589,7 +598,7 @@ fn create_action<H: logging::Handler>( false => format!("U{:04X}", codepoint as u32), }) }).collect(), - } + }, } } diff --git a/src/imservice.c b/src/imservice.c index 2a6184c8989b829a5cdc851717d032f7aed19af5..b6b04784e51e23e3603f65bc246e0a204171dc3e 100644 --- a/src/imservice.c +++ b/src/imservice.c @@ -49,6 +49,23 @@ void imservice_connect_listeners(struct zwp_input_method_v2 *im, struct imservic zwp_input_method_v2_add_listener(im, &input_method_listener, imservice); } +void +eek_input_method_commit_string(struct zwp_input_method_v2 *zwp_input_method_v2, const char *text) +{ + zwp_input_method_v2_commit_string(zwp_input_method_v2, text); +} + +void +eek_input_method_delete_surrounding_text(struct zwp_input_method_v2 *zwp_input_method_v2, uint32_t before_length, uint32_t after_length) { + zwp_input_method_v2_delete_surrounding_text(zwp_input_method_v2, before_length, after_length); +}; + +void +eek_input_method_commit(struct zwp_input_method_v2 *zwp_input_method_v2, uint32_t serial) +{ + zwp_input_method_v2_commit(zwp_input_method_v2, serial); +} + /// Declared explicitly because _destroy is inline, /// making it unavailable in Rust void imservice_destroy_im(struct zwp_input_method_v2 *im) { diff --git a/src/imservice.rs b/src/imservice.rs index 4695defdd65f26e249ddd02f5f86d24ef137769c..a4546d94c28a57b877c1fcea7af29d2a64878672 100644 --- a/src/imservice.rs +++ b/src/imservice.rs @@ -1,3 +1,8 @@ +/*! Manages zwp_input_method_v2 protocol. + * + * Library module. + */ + use std::boxed::Box; use std::ffi::CString; use std::fmt; @@ -32,6 +37,9 @@ pub mod c { fn imservice_destroy_im(im: *mut c::InputMethod); #[allow(improper_ctypes)] // IMService will never be dereferenced in C pub fn imservice_connect_listeners(im: *mut InputMethod, imservice: *const IMService); + pub fn eek_input_method_commit_string(im: *mut InputMethod, text: *const c_char); + pub fn eek_input_method_delete_surrounding_text(im: *mut InputMethod, before: u32, after: u32); + pub fn eek_input_method_commit(im: *mut InputMethod, serial: u32); fn eekboard_context_service_set_hint_purpose(state: *const StateManager, hint: u32, purpose: u32); fn server_context_service_show_keyboard(imservice: *const UIManager); fn server_context_service_hide_keyboard(imservice: *const UIManager); @@ -328,7 +336,7 @@ impl Default for IMProtocolState { pub struct IMService { /// Owned reference (still created and destroyed in C) - pub im: *const c::InputMethod, + pub im: *mut c::InputMethod, /// Unowned reference. Be careful, it's shared with C at large state_manager: *const c::StateManager, /// Unowned reference. Be careful, it's shared with C at large @@ -340,6 +348,11 @@ pub struct IMService { serial: Wrapping<u32>, } +pub enum SubmitError { + /// The input method had not been activated + NotActive, +} + impl IMService { pub fn new( im: *mut c::InputMethod, @@ -364,4 +377,51 @@ impl IMService { } imservice } + + pub fn commit_string(&self, text: &CString) -> Result<(), SubmitError> { + match self.current.active { + true => { + unsafe { + c::eek_input_method_commit_string(self.im, text.as_ptr()) + } + Ok(()) + }, + false => Err(SubmitError::NotActive), + } + } + + pub fn delete_surrounding_text( + &self, + before: u32, after: u32, + ) -> Result<(), SubmitError> { + match self.current.active { + true => { + unsafe { + c::eek_input_method_delete_surrounding_text( + self.im, + before, after, + ) + } + Ok(()) + }, + false => Err(SubmitError::NotActive), + } + } + + pub fn commit(&mut self) -> Result<(), SubmitError> { + match self.current.active { + true => { + unsafe { + c::eek_input_method_commit(self.im, self.serial.0) + } + self.serial += Wrapping(1u32); + Ok(()) + }, + false => Err(SubmitError::NotActive), + } + } + + pub fn is_active(&self) -> bool { + self.current.active + } } diff --git a/src/keyboard.rs b/src/keyboard.rs index bff1ac1618142b0ddbf514e1ea8562e43c32861b..46f4bbf923bef479c971972d9c4be068203f141c 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -1,14 +1,17 @@ /*! State of the emulated keyboard and keys. * Regards the keyboard as if it was composed of switches. */ +use std::cell::RefCell; use std::collections::HashMap; use std::fmt; use std::io; +use std::rc::Rc; use std::string::FromUtf8Error; use ::action::Action; use ::logging; +// Traits use std::io::Write; use std::iter::{ FromIterator, IntoIterator }; @@ -20,6 +23,11 @@ pub enum PressType { pub type KeyCode = u32; +/// When the submitted actions of keys need to be tracked, +/// they need a stable, comparable ID +#[derive(PartialEq)] +pub struct KeyStateId(*const KeyState); + #[derive(Debug, Clone)] pub struct KeyState { pub pressed: PressType, @@ -49,6 +57,12 @@ impl KeyState { ..self } } + + /// KeyStates instances are the unique identifiers of pressed keys, + /// and the actions submitted with them. + pub fn get_id(keystate: &Rc<RefCell<KeyState>>) -> KeyStateId { + KeyStateId(keystate.as_ptr() as *const KeyState) + } } /// Sorts an iterator by converting it to a Vector and back @@ -66,9 +80,10 @@ fn sorted<'a, I: Iterator<Item=&'a str>>( pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>( key_names: C ) -> HashMap<String, u32> { + let special_keysyms = ["BackSpace", "Return"].iter().map(|&s| s); HashMap::from_iter( // sort to remove a source of indeterminism in keycode assignment - sorted(key_names.into_iter()) + sorted(key_names.into_iter().chain(special_keysyms)) .map(|name| String::from(name)) .zip(9..) ) @@ -95,7 +110,10 @@ impl From<io::Error> for FormattingError { } } -/// Generates a de-facto single level keymap. TODO: actually drop second level +/// Generates a de-facto single level keymap. +// TODO: don't rely on keys and their order, +// but rather on what keysyms and keycodes are in use. +// Iterating actions makes it hard to deduplicate keysyms. pub fn generate_keymap( keystates: &HashMap::<String, KeyState> ) -> Result<String, FormattingError> { @@ -110,22 +128,40 @@ pub fn generate_keymap( )?; for (name, state) in keystates.iter() { - if let Action::Submit { text: _, keys } = &state.action { - if let 0 = keys.len() { - log_print!( - logging::Level::Warning, - "Key {} has no keysyms", name, - ); - }; - for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) { + match &state.action { + Action::Submit { text: _, keys } => { + if let 0 = keys.len() { + log_print!( + logging::Level::Warning, + "Key {} has no keysyms", name, + ); + }; + for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) { + write!( + buf, + " + <{}> = {};", + named_keysym.0, + keycode, + )?; + } + }, + Action::Erase => { + let mut keycodes = state.keycodes.iter(); write!( buf, " - <{}> = {};", - named_keysym.0, - keycode, + <BackSpace> = {};", + keycodes.next().expect("Erase key has no keycode"), )?; - } + if let Some(_) = keycodes.next() { + log_print!( + logging::Level::Bug, + "Erase key has multiple keycodes", + ); + } + }, + _ => {}, } } @@ -137,7 +173,9 @@ pub fn generate_keymap( xkb_symbols \"squeekboard\" {{ name[Group1] = \"Letters\"; - name[Group2] = \"Numbers/Symbols\";" + name[Group2] = \"Numbers/Symbols\"; + + key <BackSpace> {{ [ BackSpace ] }};" )?; for (name, state) in keystates.iter() { diff --git a/src/layout.rs b/src/layout.rs index 671e774a575e908d2126a41088e487dc97ee32c8..63068c0229ca6029f21246c95f6e63bcc71c7096 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -876,11 +876,7 @@ mod seat { ); } let mut key = rckey.borrow_mut(); - submission.virtual_keyboard.switch( - &key.keycodes, - PressType::Pressed, - time, - ); + submission.handle_press(&key, KeyState::get_id(rckey), time); key.pressed = PressType::Pressed; } @@ -906,13 +902,11 @@ mod seat { // process changes match action { - Action::Submit { text: _, keys: _ } => { + Action::Submit { text: _, keys: _ } + | Action::Erase + => { unstick_locks(layout).apply(); - submission.virtual_keyboard.switch( - &key.keycodes, - PressType::Released, - time, - ); + submission.handle_release(KeyState::get_id(rckey), time); }, Action::SetView(view) => { try_set_view(layout, view) diff --git a/src/submission.rs b/src/submission.rs index db7e6cc42749063c7ef86b4ea9a41eec1925679c..7d3e294997690a055873256de36ffd14dcc82f89 100644 --- a/src/submission.rs +++ b/src/submission.rs @@ -16,8 +16,12 @@ * The text-input interface may be enabled and disabled at arbitrary times, * and those events SHOULD NOT cause any lost events. * */ - + +use ::action::Action; +use ::imservice; use ::imservice::IMService; +use ::keyboard::{ KeyCode, KeyState, KeyStateId, PressType }; +use ::logging; use ::vkeyboard::VirtualKeyboard; /// Gathers stuff defined in C or called by C @@ -57,6 +61,7 @@ pub mod c { Submission { imservice, virtual_keyboard: VirtualKeyboard(vk), + pressed: Vec::new(), } )) } @@ -92,9 +97,91 @@ pub mod c { #[derive(Clone, Copy)] pub struct Timestamp(pub u32); +enum SubmittedAction { + /// A collection of keycodes that were pressed + VirtualKeyboard(Vec<KeyCode>), + IMService, +} + pub struct Submission { - // used by C callbacks internally, TODO: make use with virtual keyboard - #[allow(dead_code)] imservice: Option<Box<IMService>>, - pub virtual_keyboard: VirtualKeyboard, + virtual_keyboard: VirtualKeyboard, + pressed: Vec<(KeyStateId, SubmittedAction)>, +} + +impl Submission { + /// Sends a submit text event if possible; + /// otherwise sends key press and makes a note of it + pub fn handle_press( + &mut self, + key: &KeyState, key_id: KeyStateId, + time: Timestamp, + ) { + match &key.action { + Action::Submit { text: _, keys: _ } + | Action::Erase + => (), + _ => { + log_print!( + logging::Level::Bug, + "Submitted key with action other than Submit or Erase", + ); + return; + }, + }; + + let was_committed_as_text = match (&mut self.imservice, &key.action) { + (Some(imservice), Action::Submit { text: Some(text), keys: _ }) => { + let submit_result = imservice.commit_string(text) + .and_then(|_| imservice.commit()); + match submit_result { + Ok(()) => true, + Err(imservice::SubmitError::NotActive) => false, + } + }, + (Some(imservice), Action::Erase) => { + let submit_result = imservice.delete_surrounding_text(1, 0) + .and_then(|_| imservice.commit()); + match submit_result { + Ok(()) => true, + Err(imservice::SubmitError::NotActive) => false, + } + } + (_, _) => false, + }; + + let submit_action = match was_committed_as_text { + true => SubmittedAction::IMService, + false => { + self.virtual_keyboard.switch( + &key.keycodes, + PressType::Pressed, + time, + ); + SubmittedAction::VirtualKeyboard(key.keycodes.clone()) + }, + }; + + self.pressed.push((key_id, submit_action)); + } + + pub fn handle_release(&mut self, key_id: KeyStateId, time: Timestamp) { + let index = self.pressed.iter().position(|(id, _)| *id == key_id); + if let Some(index) = index { + let (_id, action) = self.pressed.remove(index); + match action { + // string already sent, nothing to do + SubmittedAction::IMService => {}, + // no matter if the imservice got activated, + // keys must be released + SubmittedAction::VirtualKeyboard(keycodes) => { + self.virtual_keyboard.switch( + &keycodes, + PressType::Released, + time, + ) + }, + } + }; + } }