diff --git a/eek/eek-gtk-keyboard.c b/eek/eek-gtk-keyboard.c index 94e7531d..1531f4ec 100644 --- a/eek/eek-gtk-keyboard.c +++ b/eek/eek-gtk-keyboard.c @@ -99,8 +99,7 @@ eek_gtk_keyboard_real_draw (GtkWidget *self, eek_renderer_set_scale_factor (priv->renderer, gtk_widget_get_scale_factor (self)); } - - eek_renderer_render_keyboard (priv->renderer, cr); + eek_renderer_render_keyboard (priv->renderer, priv->submission, cr); return FALSE; } diff --git a/eek/eek-renderer.c b/eek/eek-renderer.c index 45ff5cd5..c8d03e48 100644 --- a/eek/eek-renderer.c +++ b/eek/eek-renderer.c @@ -214,8 +214,10 @@ render_button_label (cairo_t *cr, g_object_unref (layout); } +// FIXME: Pass just the active modifiers instead of entire submission void eek_renderer_render_keyboard (EekRenderer *self, + struct submission *submission, cairo_t *cr) { EekRendererPrivate *priv = eek_renderer_get_instance_private (self); @@ -235,7 +237,7 @@ eek_renderer_render_keyboard (EekRenderer *self, cairo_scale (cr, priv->widget_to_layout.scale, priv->widget_to_layout.scale); squeek_draw_layout_base_view(priv->keyboard->layout, self, cr); - squeek_layout_draw_all_changed(priv->keyboard->layout, self, cr); + squeek_layout_draw_all_changed(priv->keyboard->layout, self, cr, submission); cairo_restore (cr); } diff --git a/eek/eek-renderer.h b/eek/eek-renderer.h index 0d46237e..954ad171 100644 --- a/eek/eek-renderer.h +++ b/eek/eek-renderer.h @@ -25,6 +25,7 @@ #include #include "eek-types.h" +#include "src/submission.h" G_BEGIN_DECLS @@ -59,7 +60,7 @@ cairo_surface_t *eek_renderer_get_icon_surface(const gchar *icon_name, gint size, gint scale); -void eek_renderer_render_keyboard (EekRenderer *renderer, +void eek_renderer_render_keyboard (EekRenderer *renderer, struct submission *submission, cairo_t *cr); struct transformation diff --git a/src/action.rs b/src/action.rs index b356a038..693dd1c5 100644 --- a/src/action.rs +++ b/src/action.rs @@ -10,8 +10,11 @@ pub struct KeySym(pub String); type View = String; /// Use to send modified keypresses -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Modifier { + /// Control and Alt are the only modifiers + /// which doesn't interfere with levels, + /// so it's simple to implement as levels are deprecated in squeekboard. Control, Alt, } @@ -27,8 +30,8 @@ pub enum Action { /// When unlocked by pressing it or emitting a key unlock: View, }, - /// Set this modifier TODO: release? - SetModifier(Modifier), + /// Hold this modifier for as long as the button is pressed + ApplyModifier(Modifier), /// Submit some text Submit { /// Text to submit with input-method. diff --git a/src/data.rs b/src/data.rs index effa7981..163b7b30 100644 --- a/src/data.rs +++ b/src/data.rs @@ -240,14 +240,20 @@ type ButtonIds = String; #[derive(Debug, Default, Deserialize, PartialEq)] #[serde(deny_unknown_fields)] struct ButtonMeta { - /// Special action to perform on activation. Conflicts with keysym, text. + // TODO: structure (action, keysym, text, modifier) as an enum + // to detect conflicts and missing values at compile time + /// Special action to perform on activation. + /// Conflicts with keysym, text, modifier. action: Option, /// The name of the XKB keysym to emit on activation. - /// Conflicts with action, text + /// Conflicts with action, text, modifier. keysym: Option, /// The text to submit on activation. Will be derived from ID if not present - /// Conflicts with action, keysym + /// Conflicts with action, keysym, modifier. text: Option, + /// The modifier to apply while the key is locked + /// Conflicts with action, keysym, text + modifier: Option, /// If not present, will be derived from text or the button ID label: Option, /// Conflicts with label @@ -270,6 +276,20 @@ enum Action { Erase, } +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(deny_unknown_fields)] +enum Modifier { + Control, + Shift, + Lock, + #[serde(alias="Mod1")] + Alt, + Mod2, + Mod3, + Mod4, + Mod5, +} + #[derive(Debug, Clone, Deserialize, PartialEq)] #[serde(deny_unknown_fields)] struct Outline { @@ -510,22 +530,27 @@ fn create_action( Action(Action), Text(String), Keysym(String), + Modifier(Modifier), }; let submission = match ( &symbol_meta.action, &symbol_meta.keysym, - &symbol_meta.text + &symbol_meta.text, + &symbol_meta.modifier, ) { - (Some(action), None, None) => SubmitData::Action(action.clone()), - (None, Some(keysym), None) => SubmitData::Keysym(keysym.clone()), - (None, None, Some(text)) => SubmitData::Text(text.clone()), - (None, None, None) => SubmitData::Text(name.into()), + (Some(action), None, None, None) => SubmitData::Action(action.clone()), + (None, Some(keysym), None, None) => SubmitData::Keysym(keysym.clone()), + (None, None, Some(text), None) => SubmitData::Text(text.clone()), + (None, None, None, Some(modifier)) => { + SubmitData::Modifier(modifier.clone()) + }, + (None, None, None, None) => SubmitData::Text(name.into()), _ => { warning_handler.handle( logging::Level::Warning, &format!( - "Button {} has more than one of (action, keysym, text)", + "Button {} has more than one of (action, keysym, text, modifier)", name, ), ); @@ -614,6 +639,26 @@ fn create_action( }) }).collect(), }, + SubmitData::Modifier(modifier) => match modifier { + Modifier::Control => action::Action::ApplyModifier( + action::Modifier::Control, + ), + Modifier::Alt => action::Action::ApplyModifier( + action::Modifier::Alt, + ), + unsupported_modifier => { + warning_handler.handle( + logging::Level::Bug, + &format!( + "Modifier {:?} unsupported", unsupported_modifier, + ), + ); + action::Action::Submit { + text: None, + keys: Vec::new(), + } + }, + }, } } @@ -711,6 +756,7 @@ mod tests { keysym: None, action: None, text: None, + modifier: None, label: Some("test".into()), outline: None, } @@ -852,6 +898,7 @@ mod tests { keysym: None, text: None, action: None, + modifier: None, label: Some("test".into()), outline: None, } diff --git a/src/drawing.rs b/src/drawing.rs index 504b6ec2..5a0f91ad 100644 --- a/src/drawing.rs +++ b/src/drawing.rs @@ -3,9 +3,11 @@ use cairo; use std::cell::RefCell; +use ::action::Action; use ::keyboard; use ::layout::{ Button, Layout }; use ::layout::c::{ EekGtkKeyboard, Point }; +use ::submission::Submission; use glib::translate::FromGlibPtrNone; use gtk::WidgetExt; @@ -44,13 +46,21 @@ mod c { layout: *mut Layout, renderer: EekRenderer, cr: *mut cairo_sys::cairo_t, + submission: *const Submission, ) { let layout = unsafe { &mut *layout }; + let submission = unsafe { &*submission }; let cr = unsafe { cairo::Context::from_raw_none(cr) }; + let active_modifiers = submission.get_active_modifiers(); layout.foreach_visible_button(|offset, button| { let state = RefCell::borrow(&button.state).clone(); - let locked = state.action.is_active(&layout.current_view); + let active_mod = match &state.action { + Action::ApplyModifier(m) => active_modifiers.contains(m), + _ => false, + }; + let locked = state.action.is_active(&layout.current_view) + | active_mod; if state.pressed == keyboard::PressType::Pressed || locked { render_button_at_position( renderer, &cr, diff --git a/src/keyboard.rs b/src/keyboard.rs index b1600fba..aefc4c6e 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -23,6 +23,24 @@ pub enum PressType { pub type KeyCode = u32; +bitflags!{ + /// Map to `virtual_keyboard.modifiers` modifiers values + /// From https://www.x.org/releases/current/doc/kbproto/xkbproto.html#Keyboard_State + pub struct Modifiers: u8 { + const SHIFT = 0x1; + const LOCK = 0x2; + const CONTROL = 0x4; + /// Alt + const MOD1 = 0x8; + const MOD2 = 0x10; + const MOD3 = 0x20; + /// Meta + const MOD4 = 0x40; + /// AltGr + const MOD5 = 0x80; + } +} + /// When the submitted actions of keys need to be tracked, /// they need a stable, comparable ID #[derive(PartialEq)] diff --git a/src/layout.h b/src/layout.h index eefcce36..cd00d128 100644 --- a/src/layout.h +++ b/src/layout.h @@ -7,6 +7,7 @@ #include "eek/eek-gtk-keyboard.h" #include "eek/eek-renderer.h" #include "eek/eek-types.h" +#include "src/submission.h" #include "virtual-keyboard-unstable-v1-client-protocol.h" enum squeek_arrangement_kind { @@ -53,6 +54,6 @@ void squeek_layout_drag(struct squeek_layout *layout, struct transformation widget_to_layout, uint32_t timestamp, EekboardContextService *manager, EekGtkKeyboard *ui_keyboard); -void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr); +void squeek_layout_draw_all_changed(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr, struct submission *submission); void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr); #endif diff --git a/src/layout.rs b/src/layout.rs index 12227ef5..09fc4f72 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -990,6 +990,18 @@ mod seat { ) .apply() }, + Action::ApplyModifier(modifier) => { + // FIXME: key id is unneeded with stateless locks + let key_id = KeyState::get_id(rckey); + let gets_locked = !submission.is_modifier_active(modifier.clone()); + match gets_locked { + true => submission.handle_add_modifier( + key_id, + modifier, time, + ), + false => submission.handle_drop_modifier(key_id, time), + } + } // only show when UI is present Action::ShowPreferences => if let Some(ui) = &ui { // only show when layout manager is available @@ -1016,10 +1028,6 @@ mod seat { } } }, - Action::SetModifier(_) => log_print!( - logging::Level::Bug, - "Modifiers unsupported", - ), }; let pointer = ::util::Pointer(rckey.clone()); diff --git a/src/submission.rs b/src/submission.rs index e681b2e3..a98d1c18 100644 --- a/src/submission.rs +++ b/src/submission.rs @@ -16,14 +16,19 @@ * The text-input interface may be enabled and disabled at arbitrary times, * and those events SHOULD NOT cause any lost events. * */ - + +use std::collections::HashSet; use std::ffi::CString; - +use ::action::Modifier; use ::imservice; use ::imservice::IMService; -use ::keyboard::{ KeyCode, KeyStateId, PressType }; +use ::keyboard::{ KeyCode, KeyStateId, Modifiers, PressType }; +use ::util::vec_remove; use ::vkeyboard::VirtualKeyboard; +// traits +use std::iter::FromIterator; + /// Gathers stuff defined in C or called by C pub mod c { use super::*; @@ -60,6 +65,7 @@ pub mod c { Box::::into_raw(Box::new( Submission { imservice, + modifiers_active: Vec::new(), virtual_keyboard: VirtualKeyboard(vk), pressed: Vec::new(), } @@ -106,6 +112,7 @@ enum SubmittedAction { pub struct Submission { imservice: Option>, virtual_keyboard: VirtualKeyboard, + modifiers_active: Vec<(KeyStateId, Modifier)>, pressed: Vec<(KeyStateId, SubmittedAction)>, } @@ -125,8 +132,10 @@ impl Submission { keycodes: &Vec, time: Timestamp, ) { - let was_committed_as_text = match &mut self.imservice { - Some(imservice) => { + let mods_are_on = !self.modifiers_active.is_empty(); + + let was_committed_as_text = match (&mut self.imservice, mods_are_on) { + (Some(imservice), false) => { enum Outcome { Submitted(Result<(), imservice::SubmitError>), NotSubmitted, @@ -157,7 +166,7 @@ impl Submission { Outcome::NotSubmitted => false, } }, - _ => false, + (_, _) => false, }; let submit_action = match was_committed_as_text { @@ -194,4 +203,44 @@ impl Submission { } }; } + + pub fn handle_add_modifier( + &mut self, + key_id: KeyStateId, + modifier: Modifier, _time: Timestamp, + ) { + self.modifiers_active.push((key_id, modifier)); + self.update_modifiers(); + } + + pub fn handle_drop_modifier( + &mut self, + key_id: KeyStateId, + _time: Timestamp, + ) { + vec_remove(&mut self.modifiers_active, |(id, _)| *id == key_id); + self.update_modifiers(); + } + + fn update_modifiers(&mut self) { + let raw_modifiers = self.modifiers_active.iter() + .map(|(_id, m)| match m { + Modifier::Control => Modifiers::CONTROL, + Modifier::Alt => Modifiers::MOD1, + }) + .fold(Modifiers::empty(), |m, n| m | n); + self.virtual_keyboard.set_modifiers_state(raw_modifiers); + } + + pub fn is_modifier_active(&self, modifier: Modifier) -> bool { + self.modifiers_active.iter() + .position(|(_id, m)| *m == modifier) + .is_some() + } + + pub fn get_active_modifiers(&self) -> HashSet { + HashSet::from_iter( + self.modifiers_active.iter().map(|(_id, m)| m.clone()) + ) + } } diff --git a/src/util.rs b/src/util.rs index d58a3e77..9dafb34e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -195,6 +195,12 @@ pub trait WarningHandler { fn handle(&mut self, warning: &str); } +/// Removes the first matcing item +pub fn vec_remove bool>(v: &mut Vec, pred: F) -> Option { + let idx = v.iter().position(pred); + idx.map(|idx| v.remove(idx)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/vkeyboard.rs b/src/vkeyboard.rs index aac5d18f..d3e086e7 100644 --- a/src/vkeyboard.rs +++ b/src/vkeyboard.rs @@ -1,6 +1,6 @@ /*! Managing the events belonging to virtual-keyboard interface. */ -use ::keyboard::{ KeyCode, PressType }; +use ::keyboard::{ KeyCode, Modifiers, PressType }; use ::layout::c::LevelKeyboard; use ::submission::Timestamp; @@ -26,6 +26,11 @@ pub mod c { virtual_keyboard: ZwpVirtualKeyboardV1, keyboard: LevelKeyboard, ); + + pub fn eek_virtual_keyboard_set_modifiers( + virtual_keyboard: ZwpVirtualKeyboardV1, + modifiers: u32, + ); } } @@ -33,7 +38,7 @@ pub mod c { pub struct VirtualKeyboard(pub c::ZwpVirtualKeyboardV1); impl VirtualKeyboard { - // TODO: split out keyboard state management + // TODO: error out if keymap not set pub fn switch( &self, keycodes: &Vec, @@ -68,12 +73,16 @@ impl VirtualKeyboard { } } + pub fn set_modifiers_state(&self, modifiers: Modifiers) { + let modifiers = modifiers.bits() as u32; + unsafe { + c::eek_virtual_keyboard_set_modifiers(self.0, modifiers); + } + } + pub fn update_keymap(&self, keyboard: LevelKeyboard) { unsafe { - c::eek_virtual_keyboard_update_keymap( - self.0, - keyboard, - ); + c::eek_virtual_keyboard_update_keymap(self.0, keyboard); } } } diff --git a/src/wayland.c b/src/wayland.c index 42ba59df..a0f7e7b7 100644 --- a/src/wayland.c +++ b/src/wayland.c @@ -20,6 +20,12 @@ void eek_virtual_keyboard_update_keymap(struct zwp_virtual_keyboard_v1 *zwp_virt keyboard->keymap_fd, keyboard->keymap_len); } +void +eek_virtual_keyboard_set_modifiers(struct zwp_virtual_keyboard_v1 *zwp_virtual_keyboard_v1, uint32_t mods_depressed) { + zwp_virtual_keyboard_v1_modifiers(zwp_virtual_keyboard_v1, + mods_depressed, 0, 0, 0); +} + int squeek_output_add_listener(struct wl_output *wl_output, const struct wl_output_listener *listener, void *data) { return wl_output_add_listener(wl_output, listener, data);