From 7266f539d4aa690e81938408b04c1263e8cdd602 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Wed, 5 Feb 2020 09:57:11 +0000 Subject: [PATCH 01/11] cargo: Update deps --- Cargo.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9dac2f4a..39d8841e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,10 +2,10 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -254,7 +254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "memchr" -version = "2.3.0" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -317,8 +317,8 @@ name = "regex" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "utf8-ranges 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -457,7 +457,7 @@ dependencies = [ ] [metadata] -"checksum aho-corasick 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)" = "5f56c476256dc249def911d6f7580b5fc7e875895b5d7ee88f5d602208035744" +"checksum aho-corasick 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "743ad5a418686aad3b87fd14c43badd828cf26e214a00f92a384291cf22e1811" "checksum atk-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7017e53393e713212aed7aea336b6553be4927f58c37070a56c2fe3d107e489" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum cairo-rs 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd940f0d609699e343ef71c4af5f66423afbf30d666f796dabd8fd15229cf5b6" @@ -481,7 +481,7 @@ dependencies = [ "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -"checksum memchr 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223" +"checksum memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53445de381a1f436797497c61d851644d0e8e88e6140f22872ad33a704933978" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" "checksum pango 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4c2cb169402a3eb1ba034a7cc7d95b8b1c106e9be5ba4be79a5a93dc1a2795f4" "checksum pango-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6eb49268e69dd0c1da5d3001a61aac08e2e9d2bfbe4ae4b19b9963c998f6453" From 930f5be0c86d33ae39aa5c65babffd3e7420b81e Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Wed, 5 Feb 2020 10:02:20 +0000 Subject: [PATCH 02/11] Release 1.9.0 "Fractal dimension" Highlights: - Fixed glib critical when switching layouts - Fixed minor memory leaks when switching layouts - Whenever the client supports it, text is sent as text instread of key presses - New Polish language layout - Fixed greek layout - Better key locking - Less leaks - Tweaks in terminal layout - Better emoji layout --- debian/changelog | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/debian/changelog b/debian/changelog index a3b01109..b74dd59a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,40 @@ +squeekboard (1.9.0) amber-phone; urgency=medium + + [ Dorota Czaplejewicz ] + * imservice: Add commit_string method + * submission: Handle submitting strings + * input_method: Use for erasing + * logging: Use in merged functions + * translations: Remove redundant ones + * translations: Translate builtin layouts + * greek: Rename to gr which is used by gnome settings + + [ Sebastian Krzyszkowiak ] + * layouts: Add Polish layouts + + [ Dorota Czaplejewicz ] + * locks: Draw based on current view + * locking: Lock keys statelessly + * layouts: Better accented uppercase in PL + * emoji: Add more choices + * row: Eliminate angle + * layout: Center views relative to each other and the layout bounds + * drawing: Generalized foreach_visible_button + * variant: Fix double-free + * variant: Fix leak + * keyboard_layout: Fix leak + * layout: Improve scoping of locked variable + * terminal: Make */ easier to reach + + [ Sebastian Krzyszkowiak ] + * layouts: terminal: Use altline outline for dot key + + [ Dorota Czaplejewicz ] + * text input: Disable erasing + * cargo: Update deps + + -- Dorota Czaplejewicz Wed, 19 Feb 2020 14:32:39 +0000 + squeekboard (1.8.1) amber-phone; urgency=medium [ Dorota Czaplejewicz ] From cb802cfb50f51e85456252b51ab1ccc369a16183 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Wed, 5 Feb 2020 12:25:46 +0000 Subject: [PATCH 03/11] layout: Improve press handling Makes it more similar to release handling, removes some redundant checks. This makes it easier to integrate modifiers in the future. --- src/keyboard.rs | 8 +++++ src/layout.rs | 40 +++++++++++++++++++--- src/submission.rs | 85 +++++++++++++++++++++++++---------------------- 3 files changed, 89 insertions(+), 44 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index cbdaa007..e1a3a325 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -46,6 +46,14 @@ impl KeyState { } } + #[must_use] + pub fn into_pressed(self) -> KeyState { + KeyState { + pressed: PressType::Pressed, + ..self + } + } + /// KeyStates instances are the unique identifiers of pressed keys, /// and the actions submitted with them. pub fn get_id(keystate: &Rc>) -> KeyStateId { diff --git a/src/layout.rs b/src/layout.rs index ec601b66..ae554d5a 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -26,10 +26,10 @@ use std::vec::Vec; use ::action::Action; use ::drawing; -use ::keyboard::{ KeyState, PressType }; +use ::keyboard::KeyState; use ::logging; use ::manager; -use ::submission::{ Submission, Timestamp }; +use ::submission::{ Submission, SubmitData, Timestamp }; use ::util::find_max_double; // Traits @@ -916,9 +916,38 @@ mod seat { "Key {:?} was already pressed", rckey, ); } - let mut key = rckey.borrow_mut(); - submission.handle_press(&key, KeyState::get_id(rckey), time); - key.pressed = PressType::Pressed; + let key: KeyState = { + RefCell::borrow(rckey).clone() + }; + let action = key.action.clone(); + match action { + Action::Submit { + text: Some(text), + keys: _, + } => submission.handle_press( + KeyState::get_id(rckey), + SubmitData::Text(&text), + &key.keycodes, + time, + ), + Action::Submit { + text: None, + keys: _, + } => submission.handle_press( + KeyState::get_id(rckey), + SubmitData::Keycodes, + &key.keycodes, + time, + ), + Action::Erase => submission.handle_press( + KeyState::get_id(rckey), + SubmitData::Erase, + &key.keycodes, + time, + ), + _ => {}, + }; + RefCell::replace(rckey, key.into_pressed()); } pub fn handle_release_key( @@ -1006,6 +1035,7 @@ mod test { use super::*; use std::ffi::CString; + use ::keyboard::PressType; pub fn make_state() -> Rc> { Rc::new(RefCell::new(::keyboard::KeyState { diff --git a/src/submission.rs b/src/submission.rs index 9ea969b2..e681b2e3 100644 --- a/src/submission.rs +++ b/src/submission.rs @@ -17,11 +17,11 @@ * and those events SHOULD NOT cause any lost events. * */ -use ::action::Action; +use std::ffi::CString; + use ::imservice; use ::imservice::IMService; -use ::keyboard::{ KeyCode, KeyState, KeyStateId, PressType }; -use ::logging; +use ::keyboard::{ KeyCode, KeyStateId, PressType }; use ::vkeyboard::VirtualKeyboard; /// Gathers stuff defined in C or called by C @@ -109,59 +109,66 @@ pub struct Submission { pressed: Vec<(KeyStateId, SubmittedAction)>, } +pub enum SubmitData<'a> { + Text(&'a CString), + Erase, + Keycodes, +} + 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, + key_id: KeyStateId, + data: SubmitData, + keycodes: &Vec, 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 { + Some(imservice) => { + enum Outcome { + Submitted(Result<(), imservice::SubmitError>), + NotSubmitted, + }; + + let submit_outcome = match data { + SubmitData::Text(text) => { + Outcome::Submitted(imservice.commit_string(text)) + }, + SubmitData::Erase => { + /* Delete_surrounding_text takes byte offsets, + * so cannot work without get_surrounding_text. + * This is a bug in the protocol. + */ + // imservice.delete_surrounding_text(1, 0), + Outcome::NotSubmitted + }, + SubmitData::Keycodes => Outcome::NotSubmitted, + }; + + match submit_outcome { + Outcome::Submitted(result) => { + match result.and_then(|()| imservice.commit()) { + Ok(()) => true, + Err(imservice::SubmitError::NotActive) => false, + } + }, + Outcome::NotSubmitted => false, + } }, + _ => false, }; - 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, - } - }, - /* Delete_surrounding_text takes byte offsets, - * so cannot work without get_surrounding_text. - * This is a bug in the protocol. - (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, + keycodes, PressType::Pressed, time, ); - SubmittedAction::VirtualKeyboard(key.keycodes.clone()) + SubmittedAction::VirtualKeyboard(keycodes.clone()) }, }; From 404f94638fcdf79d90f267b8635b6f699d495e02 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Thu, 20 Feb 2020 12:06:47 +0000 Subject: [PATCH 04/11] settings: Handle empty settings --- eekboard/eekboard-context-service.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/eekboard/eekboard-context-service.c b/eekboard/eekboard-context-service.c index cf5e7e4d..c07885c2 100644 --- a/eekboard/eekboard-context-service.c +++ b/eekboard/eekboard-context-service.c @@ -114,8 +114,14 @@ static void settings_get_layout(GSettings *settings, char **type, char **layout) { GVariant *inputs = g_settings_get_value(settings, "sources"); - // current layout is always first - g_variant_get_child(inputs, 0, "(ss)", type, layout); + if (g_variant_n_children(inputs) == 0) { + g_warning("No system layout present"); + *type = NULL; + *layout = NULL; + } else { + // current layout is always first + g_variant_get_child(inputs, 0, "(ss)", type, layout); + } g_variant_unref(inputs); } From c0aee5de265718b4bf0e0696029a2735365170b5 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Thu, 20 Feb 2020 12:17:50 +0000 Subject: [PATCH 05/11] Variant: Use proper pointer conversion between C and Rust --- src/popover.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/popover.rs b/src/popover.rs index ce1543ba..8330a920 100644 --- a/src/popover.rs +++ b/src/popover.rs @@ -29,6 +29,7 @@ mod variants { use glib::ToVariant; use glib::translate::FromGlibPtrFull; + use glib::translate::FromGlibPtrNone; use glib::translate::ToGlibPtr; /// Unpacks tuple & array variants @@ -91,12 +92,7 @@ mod variants { unsafe { let ret = glib_sys::g_variant_builder_end(builder); glib_sys::g_variant_builder_unref(builder); - // HACK: This is to prevent C taking ownership - // of "floating" Variants, - // where Rust gets to keep a stale reference - // and crash when trying to drop it. - glib_sys::g_variant_ref_sink(ret); - glib::Variant::from_glib_full(ret) + glib::Variant::from_glib_none(ret) } } } From 53b44668997e0dcdb7806444324a836bf6f6e7bf Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Sun, 23 Feb 2020 10:36:27 +0000 Subject: [PATCH 06/11] meta: Add doap file --- squeekboard.doap | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 squeekboard.doap diff --git a/squeekboard.doap b/squeekboard.doap new file mode 100644 index 00000000..838716d9 --- /dev/null +++ b/squeekboard.doap @@ -0,0 +1,17 @@ + + + squeekboard + A Wayland virtual keyboard + A virtual keyboard supporting Wayland, built primarily for the Librem 5 phone. + + + Linux + + + + Dorota Czaplejewicz + + + + + From 92e9d994feca584c59e2476d19838e34049e1499 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Wed, 15 Jan 2020 15:23:06 +0000 Subject: [PATCH 07/11] modifiers: Support Control and Alt Control and Alt are special in that they aren't expected to switch levels, and so don't need to change what characters are output. Use in layouts by adding `modifier: Control` or `modifier: Alt` in place of `text: "foo"`. The latching of the modifier will force the keyboard to emit raw key presses and prevent it from outputting text. --- eek/eek-gtk-keyboard.c | 3 +- eek/eek-renderer.c | 4 ++- eek/eek-renderer.h | 3 +- src/action.rs | 9 ++++-- src/data.rs | 65 ++++++++++++++++++++++++++++++++++++------ src/drawing.rs | 12 +++++++- src/keyboard.rs | 18 ++++++++++++ src/layout.h | 3 +- src/layout.rs | 16 ++++++++--- src/submission.rs | 61 +++++++++++++++++++++++++++++++++++---- src/util.rs | 6 ++++ src/vkeyboard.rs | 21 ++++++++++---- src/wayland.c | 6 ++++ 13 files changed, 193 insertions(+), 34 deletions(-) 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 e1a3a325..8178d148 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 ae554d5a..ec7ced30 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); From 9f6fe8318ce8da80ce5383c5fc0fa8f90ad0b2c8 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Fri, 28 Feb 2020 12:14:18 +0000 Subject: [PATCH 08/11] CI: Test that any bump to changelog has a corresponding tag Prevents forgetting to sign the tag, which is currently done out of band and independently of review. --- .gitlab-ci.yml | 10 ++++++++++ debian/check_release.py | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100755 debian/check_release.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index edeceef7..0f5a6701 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -84,3 +84,13 @@ test: script: - apt-get -y build-dep . - ninja -C _build test + +check_release: + <<: *tags + stage: test + only: + refs: + - master + script: + - apt-get install git python3 + - (head -n 1 ./debian/changelog && git tag) | ./debian/check_release.py diff --git a/debian/check_release.py b/debian/check_release.py new file mode 100755 index 00000000..66788911 --- /dev/null +++ b/debian/check_release.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +"""Checks tag before release. +Feed it the first changelog line, and then all available tags. +""" + +import re, sys +tag = "v" + re.findall("\\((.*)\\)", input())[0] +if tag not in map(str.strip, sys.stdin.readlines()): + raise Exception("Changelog's current version doesn't have a tag. Push the tag!") From 7eb5c6d466737989d39ba1a542abdcd333623c51 Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Fri, 28 Feb 2020 13:26:09 +0000 Subject: [PATCH 09/11] docs: Add the guiding principle --- HACKING.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/HACKING.md b/HACKING.md index 4c037d0f..4b4e2f45 100644 --- a/HACKING.md +++ b/HACKING.md @@ -3,6 +3,38 @@ Hacking This document describes the standards for modifying and maintaining the *squeekboard* project. +Principles +---------- + +The project was built upon some guiding principles, which should be respected primarily by the maintainers, but also by contributors to avoid needlessly rejected changes. + +The overarching principle of *squeekboard* is to empower users. + +Software is primarily meant to solve problems of its users. Often in the quest to make software better, a hard distinction is made between the developer, who becomes the creator, and the user, who takes the role of the consumer, without direct influence on the software they use. +This project aims to give users the power to make the software work for them by blurring the lines between users and developers. + +Nonwithstanding its current state, *squeekboard* must be structured in a way that provides users a gradual way to gain more experience and power to adjust it. It must be easy, in order of importance: + +- to use the software, +- to modify its resources, +- to change its behaviour, +- to contribute upstream. + +To give an idea of what it means in practice, those are some examples of what has been important for *squeekboard* so far: + +- being quick and useable, +- allowing local overrides of resources and config, +- storing resources and config as editable, standard files, +- having complete, up to date documentation of interfaces, +- having an easy process of sending contributions, +- adapting to to user's settings and constrains without overriding them, +- avoiding compiling whenever possible, +- making it easy to build, +- having code that is [simple and obvious](https://www.python.org/dev/peps/pep-0020/), +- having an easy process of testing and accepting contributions. + +You may notice that they are ordered roughly from "user-focused" to "maintainer-focused". While good properties are desired, sometimes they conflict, and maintainers should give additional weight to those benefitting the user compared to those benefitting regular contributors. + Sending patches --------------- From 8f3d0103493f76a8270c7965c63dd72d0478aa4c Mon Sep 17 00:00:00 2001 From: Dorota Czaplejewicz Date: Fri, 28 Feb 2020 13:30:43 +0000 Subject: [PATCH 10/11] hacking: Move into docs/ --- README.md | 2 +- HACKING.md => doc/hacking.md | 5 ++--- doc/index.md | 10 ++++++++-- doc/tutorial.md | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) rename HACKING.md => doc/hacking.md (94%) diff --git a/README.md b/README.md index afb92e21..756b4c7e 100644 --- a/README.md +++ b/README.md @@ -56,4 +56,4 @@ $ src/squeekboard Developing ---------- -See `HACKING.md` +See [`docs/hacking.md`](docs/hacking.md) for this copy, or the [official documentation](https://developer.puri.sm/projects/squeekboard/) for the current release. diff --git a/HACKING.md b/doc/hacking.md similarity index 94% rename from HACKING.md rename to doc/hacking.md index 4b4e2f45..7b016f63 100644 --- a/HACKING.md +++ b/doc/hacking.md @@ -38,7 +38,7 @@ You may notice that they are ordered roughly from "user-focused" to "maintainer- Sending patches --------------- -By submitting a change to this project, you agree to license it under the [GPL license version 3](./COPYING), or any later version. You also certify that your contribution fulfills the [Developer's Certificate of Origin 1.1](./dco.txt). +By submitting a change to this project, you agree to license it under the [GPL license version 3](https://source.puri.sm/Librem5/squeekboard/blob/master/COPYING), or any later version. You also certify that your contribution fulfills the [Developer's Certificate of Origin 1.1](https://source.puri.sm/Librem5/squeekboard/blob/master/dco.txt). Development environment ----------------------- @@ -56,8 +56,7 @@ sudo apt-get -y install build-essential sudo apt-get -y build-dep . ``` -For an explicit list of dependencies check the `Build-Depends` entry in the -[`debian/control`](./debian/control) file. +For an explicit list of dependencies check the `Build-Depends` entry in the [`debian/control`](https://source.puri.sm/Librem5/squeekboard/blob/master/debian/control) file. Testing ------- diff --git a/doc/index.md b/doc/index.md index 065d7a0c..0c73eecf 100644 --- a/doc/index.md +++ b/doc/index.md @@ -5,17 +5,23 @@ Contents -------- * [Tutorial](tutorial.md) +* [Contributing](hacking.md) Introduction ------------ -Squeekboard is the on-screen keyboard for the Librem 5 phone. For more information, look at the [README](https://source.puri.sm/Librem5/squeekboard/blob/master/README.md). +Squeekboard is the on-screen keyboard for the Librem 5 phone. For information about building, look at the [README](https://source.puri.sm/Librem5/squeekboard/blob/master/README.md). Layouts ------- -Squeekboard allows user-provided keyboard layouts. They can be created without recompiling the keyboard code. The [tutorial](/tutorial.md) explains the process in detail. +Squeekboard allows user-provided keyboard layouts. They can be created without recompiling the keyboard code. The [tutorial](tutorial.md) explains the process in detail. Layouts are created using a text-based format, based on YAML. TODO: Provide a description of the format. + +Contributions +------------- + +Anyone is free to modify *squeekboard*. See the [contributing document](hacking.md). diff --git a/doc/tutorial.md b/doc/tutorial.md index 716b8dda..d97c6173 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -31,7 +31,7 @@ So at least I will try to start writing a short how-to here and edit this post a **Running squeekboard** * Follow these instructions to run squeekboard: [https://source.puri.sm/Librem5/squeekboard/blob/master/README.md#running ](https://source.puri.sm/Librem5/squeekboard/blob/master/README.md#running) -* Additionally take a look at https://source.puri.sm/Librem5/squeekboard/blob/master/HACKING.md#testing +* Additionally take a look at the contribution document for [testing info](HACKING.md#testing) * You can either test it locally on your Linux system or use the [QEMU Librem 5 image ](https://developer.puri.sm/Librem5/Development_Environment/Boards/emulators.html) * To test squeekboard locally, you need phoc. Either compile that from the sources as well or use the CI repository ci.puri.sm for Debian based systems: `deb [arch=amd64] http://ci.puri.sm/ scratch librem5` From 67cc4f11cf3a32107e2794d3363f7fadde2176df Mon Sep 17 00:00:00 2001 From: &t Date: Wed, 4 Mar 2020 04:53:53 +0000 Subject: [PATCH 11/11] Fix minor comment typos --- src/keyboard.rs | 2 +- src/layout.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/keyboard.rs b/src/keyboard.rs index e1a3a325..b1600fba 100644 --- a/src/keyboard.rs +++ b/src/keyboard.rs @@ -31,7 +31,7 @@ pub struct KeyStateId(*const KeyState); #[derive(Debug, Clone)] pub struct KeyState { pub pressed: PressType, - /// A cache of raw keycodes derived from Action::Sumbit given a keymap + /// A cache of raw keycodes derived from Action::Submit given a keymap pub keycodes: Vec, /// Static description of what the key does when pressed or released pub action: Action, diff --git a/src/layout.rs b/src/layout.rs index ae554d5a..12227ef5 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -249,7 +249,7 @@ pub mod c { unsafe { Box::from_raw(layout) }; } - /// Entry points for more complex procedures and algoithms which span multiple modules + /// Entry points for more complex procedures and algorithms which span multiple modules pub mod procedures { use super::*;