Merge branch 'modifiers' into 'master'

Add simple modifiers support

See merge request Librem5/squeekboard!306
This commit is contained in:
Dorota Czaplejewicz
2020-03-07 10:32:33 +00:00
13 changed files with 193 additions and 34 deletions

View File

@ -99,8 +99,7 @@ eek_gtk_keyboard_real_draw (GtkWidget *self,
eek_renderer_set_scale_factor (priv->renderer, eek_renderer_set_scale_factor (priv->renderer,
gtk_widget_get_scale_factor (self)); gtk_widget_get_scale_factor (self));
} }
eek_renderer_render_keyboard (priv->renderer, priv->submission, cr);
eek_renderer_render_keyboard (priv->renderer, cr);
return FALSE; return FALSE;
} }

View File

@ -214,8 +214,10 @@ render_button_label (cairo_t *cr,
g_object_unref (layout); g_object_unref (layout);
} }
// FIXME: Pass just the active modifiers instead of entire submission
void void
eek_renderer_render_keyboard (EekRenderer *self, eek_renderer_render_keyboard (EekRenderer *self,
struct submission *submission,
cairo_t *cr) cairo_t *cr)
{ {
EekRendererPrivate *priv = eek_renderer_get_instance_private (self); 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); cairo_scale (cr, priv->widget_to_layout.scale, priv->widget_to_layout.scale);
squeek_draw_layout_base_view(priv->keyboard->layout, self, cr); 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); cairo_restore (cr);
} }

View File

@ -25,6 +25,7 @@
#include <pango/pangocairo.h> #include <pango/pangocairo.h>
#include "eek-types.h" #include "eek-types.h"
#include "src/submission.h"
G_BEGIN_DECLS G_BEGIN_DECLS
@ -59,7 +60,7 @@ cairo_surface_t *eek_renderer_get_icon_surface(const gchar *icon_name,
gint size, gint size,
gint scale); gint scale);
void eek_renderer_render_keyboard (EekRenderer *renderer, void eek_renderer_render_keyboard (EekRenderer *renderer, struct submission *submission,
cairo_t *cr); cairo_t *cr);
struct transformation struct transformation

View File

@ -10,8 +10,11 @@ pub struct KeySym(pub String);
type View = String; type View = String;
/// Use to send modified keypresses /// Use to send modified keypresses
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Modifier { 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, Control,
Alt, Alt,
} }
@ -27,8 +30,8 @@ pub enum Action {
/// When unlocked by pressing it or emitting a key /// When unlocked by pressing it or emitting a key
unlock: View, unlock: View,
}, },
/// Set this modifier TODO: release? /// Hold this modifier for as long as the button is pressed
SetModifier(Modifier), ApplyModifier(Modifier),
/// Submit some text /// Submit some text
Submit { Submit {
/// Text to submit with input-method. /// Text to submit with input-method.

View File

@ -240,14 +240,20 @@ type ButtonIds = String;
#[derive(Debug, Default, Deserialize, PartialEq)] #[derive(Debug, Default, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct ButtonMeta { 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<Action>, action: Option<Action>,
/// The name of the XKB keysym to emit on activation. /// The name of the XKB keysym to emit on activation.
/// Conflicts with action, text /// Conflicts with action, text, modifier.
keysym: Option<String>, keysym: Option<String>,
/// The text to submit on activation. Will be derived from ID if not present /// 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<String>, text: Option<String>,
/// The modifier to apply while the key is locked
/// Conflicts with action, keysym, text
modifier: Option<Modifier>,
/// If not present, will be derived from text or the button ID /// If not present, will be derived from text or the button ID
label: Option<String>, label: Option<String>,
/// Conflicts with label /// Conflicts with label
@ -270,6 +276,20 @@ enum Action {
Erase, 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)] #[derive(Debug, Clone, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct Outline { struct Outline {
@ -510,22 +530,27 @@ fn create_action<H: logging::Handler>(
Action(Action), Action(Action),
Text(String), Text(String),
Keysym(String), Keysym(String),
Modifier(Modifier),
}; };
let submission = match ( let submission = match (
&symbol_meta.action, &symbol_meta.action,
&symbol_meta.keysym, &symbol_meta.keysym,
&symbol_meta.text &symbol_meta.text,
&symbol_meta.modifier,
) { ) {
(Some(action), None, None) => SubmitData::Action(action.clone()), (Some(action), None, None, None) => SubmitData::Action(action.clone()),
(None, Some(keysym), None) => SubmitData::Keysym(keysym.clone()), (None, Some(keysym), None, None) => SubmitData::Keysym(keysym.clone()),
(None, None, Some(text)) => SubmitData::Text(text.clone()), (None, None, Some(text), None) => SubmitData::Text(text.clone()),
(None, None, None) => SubmitData::Text(name.into()), (None, None, None, Some(modifier)) => {
SubmitData::Modifier(modifier.clone())
},
(None, None, None, None) => SubmitData::Text(name.into()),
_ => { _ => {
warning_handler.handle( warning_handler.handle(
logging::Level::Warning, logging::Level::Warning,
&format!( &format!(
"Button {} has more than one of (action, keysym, text)", "Button {} has more than one of (action, keysym, text, modifier)",
name, name,
), ),
); );
@ -614,6 +639,26 @@ fn create_action<H: logging::Handler>(
}) })
}).collect(), }).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, keysym: None,
action: None, action: None,
text: None, text: None,
modifier: None,
label: Some("test".into()), label: Some("test".into()),
outline: None, outline: None,
} }
@ -852,6 +898,7 @@ mod tests {
keysym: None, keysym: None,
text: None, text: None,
action: None, action: None,
modifier: None,
label: Some("test".into()), label: Some("test".into()),
outline: None, outline: None,
} }

View File

@ -3,9 +3,11 @@
use cairo; use cairo;
use std::cell::RefCell; use std::cell::RefCell;
use ::action::Action;
use ::keyboard; use ::keyboard;
use ::layout::{ Button, Layout }; use ::layout::{ Button, Layout };
use ::layout::c::{ EekGtkKeyboard, Point }; use ::layout::c::{ EekGtkKeyboard, Point };
use ::submission::Submission;
use glib::translate::FromGlibPtrNone; use glib::translate::FromGlibPtrNone;
use gtk::WidgetExt; use gtk::WidgetExt;
@ -44,13 +46,21 @@ mod c {
layout: *mut Layout, layout: *mut Layout,
renderer: EekRenderer, renderer: EekRenderer,
cr: *mut cairo_sys::cairo_t, cr: *mut cairo_sys::cairo_t,
submission: *const Submission,
) { ) {
let layout = unsafe { &mut *layout }; let layout = unsafe { &mut *layout };
let submission = unsafe { &*submission };
let cr = unsafe { cairo::Context::from_raw_none(cr) }; let cr = unsafe { cairo::Context::from_raw_none(cr) };
let active_modifiers = submission.get_active_modifiers();
layout.foreach_visible_button(|offset, button| { layout.foreach_visible_button(|offset, button| {
let state = RefCell::borrow(&button.state).clone(); 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 { if state.pressed == keyboard::PressType::Pressed || locked {
render_button_at_position( render_button_at_position(
renderer, &cr, renderer, &cr,

View File

@ -23,6 +23,24 @@ pub enum PressType {
pub type KeyCode = u32; 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, /// When the submitted actions of keys need to be tracked,
/// they need a stable, comparable ID /// they need a stable, comparable ID
#[derive(PartialEq)] #[derive(PartialEq)]

View File

@ -7,6 +7,7 @@
#include "eek/eek-gtk-keyboard.h" #include "eek/eek-gtk-keyboard.h"
#include "eek/eek-renderer.h" #include "eek/eek-renderer.h"
#include "eek/eek-types.h" #include "eek/eek-types.h"
#include "src/submission.h"
#include "virtual-keyboard-unstable-v1-client-protocol.h" #include "virtual-keyboard-unstable-v1-client-protocol.h"
enum squeek_arrangement_kind { enum squeek_arrangement_kind {
@ -53,6 +54,6 @@ void squeek_layout_drag(struct squeek_layout *layout,
struct transformation widget_to_layout, struct transformation widget_to_layout,
uint32_t timestamp, EekboardContextService *manager, uint32_t timestamp, EekboardContextService *manager,
EekGtkKeyboard *ui_keyboard); 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); void squeek_draw_layout_base_view(struct squeek_layout *layout, EekRenderer* renderer, cairo_t *cr);
#endif #endif

View File

@ -990,6 +990,18 @@ mod seat {
) )
.apply() .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 // only show when UI is present
Action::ShowPreferences => if let Some(ui) = &ui { Action::ShowPreferences => if let Some(ui) = &ui {
// only show when layout manager is available // 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()); let pointer = ::util::Pointer(rckey.clone());

View File

@ -17,13 +17,18 @@
* and those events SHOULD NOT cause any lost events. * and those events SHOULD NOT cause any lost events.
* */ * */
use std::collections::HashSet;
use std::ffi::CString; use std::ffi::CString;
use ::action::Modifier;
use ::imservice; use ::imservice;
use ::imservice::IMService; use ::imservice::IMService;
use ::keyboard::{ KeyCode, KeyStateId, PressType }; use ::keyboard::{ KeyCode, KeyStateId, Modifiers, PressType };
use ::util::vec_remove;
use ::vkeyboard::VirtualKeyboard; use ::vkeyboard::VirtualKeyboard;
// traits
use std::iter::FromIterator;
/// Gathers stuff defined in C or called by C /// Gathers stuff defined in C or called by C
pub mod c { pub mod c {
use super::*; use super::*;
@ -60,6 +65,7 @@ pub mod c {
Box::<Submission>::into_raw(Box::new( Box::<Submission>::into_raw(Box::new(
Submission { Submission {
imservice, imservice,
modifiers_active: Vec::new(),
virtual_keyboard: VirtualKeyboard(vk), virtual_keyboard: VirtualKeyboard(vk),
pressed: Vec::new(), pressed: Vec::new(),
} }
@ -106,6 +112,7 @@ enum SubmittedAction {
pub struct Submission { pub struct Submission {
imservice: Option<Box<IMService>>, imservice: Option<Box<IMService>>,
virtual_keyboard: VirtualKeyboard, virtual_keyboard: VirtualKeyboard,
modifiers_active: Vec<(KeyStateId, Modifier)>,
pressed: Vec<(KeyStateId, SubmittedAction)>, pressed: Vec<(KeyStateId, SubmittedAction)>,
} }
@ -125,8 +132,10 @@ impl Submission {
keycodes: &Vec<KeyCode>, keycodes: &Vec<KeyCode>,
time: Timestamp, time: Timestamp,
) { ) {
let was_committed_as_text = match &mut self.imservice { let mods_are_on = !self.modifiers_active.is_empty();
Some(imservice) => {
let was_committed_as_text = match (&mut self.imservice, mods_are_on) {
(Some(imservice), false) => {
enum Outcome { enum Outcome {
Submitted(Result<(), imservice::SubmitError>), Submitted(Result<(), imservice::SubmitError>),
NotSubmitted, NotSubmitted,
@ -157,7 +166,7 @@ impl Submission {
Outcome::NotSubmitted => false, Outcome::NotSubmitted => false,
} }
}, },
_ => false, (_, _) => false,
}; };
let submit_action = match was_committed_as_text { 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<Modifier> {
HashSet::from_iter(
self.modifiers_active.iter().map(|(_id, m)| m.clone())
)
}
} }

View File

@ -195,6 +195,12 @@ pub trait WarningHandler {
fn handle(&mut self, warning: &str); fn handle(&mut self, warning: &str);
} }
/// Removes the first matcing item
pub fn vec_remove<T, F: FnMut(&T) -> bool>(v: &mut Vec<T>, pred: F) -> Option<T> {
let idx = v.iter().position(pred);
idx.map(|idx| v.remove(idx))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -1,6 +1,6 @@
/*! Managing the events belonging to virtual-keyboard interface. */ /*! Managing the events belonging to virtual-keyboard interface. */
use ::keyboard::{ KeyCode, PressType }; use ::keyboard::{ KeyCode, Modifiers, PressType };
use ::layout::c::LevelKeyboard; use ::layout::c::LevelKeyboard;
use ::submission::Timestamp; use ::submission::Timestamp;
@ -26,6 +26,11 @@ pub mod c {
virtual_keyboard: ZwpVirtualKeyboardV1, virtual_keyboard: ZwpVirtualKeyboardV1,
keyboard: LevelKeyboard, 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); pub struct VirtualKeyboard(pub c::ZwpVirtualKeyboardV1);
impl VirtualKeyboard { impl VirtualKeyboard {
// TODO: split out keyboard state management // TODO: error out if keymap not set
pub fn switch( pub fn switch(
&self, &self,
keycodes: &Vec<KeyCode>, keycodes: &Vec<KeyCode>,
@ -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) { pub fn update_keymap(&self, keyboard: LevelKeyboard) {
unsafe { unsafe {
c::eek_virtual_keyboard_update_keymap( c::eek_virtual_keyboard_update_keymap(self.0, keyboard);
self.0,
keyboard,
);
} }
} }
} }

View File

@ -20,6 +20,12 @@ void eek_virtual_keyboard_update_keymap(struct zwp_virtual_keyboard_v1 *zwp_virt
keyboard->keymap_fd, keyboard->keymap_len); 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, int squeek_output_add_listener(struct wl_output *wl_output,
const struct wl_output_listener *listener, void *data) { const struct wl_output_listener *listener, void *data) {
return wl_output_add_listener(wl_output, listener, data); return wl_output_add_listener(wl_output, listener, data);