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.
This commit is contained in:
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#include <pango/pangocairo.h>
|
||||
|
||||
#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
|
||||
|
||||
@ -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.
|
||||
|
||||
65
src/data.rs
65
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<Action>,
|
||||
/// The name of the XKB keysym to emit on activation.
|
||||
/// Conflicts with action, text
|
||||
/// Conflicts with action, text, modifier.
|
||||
keysym: Option<String>,
|
||||
/// 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>,
|
||||
/// 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
|
||||
label: Option<String>,
|
||||
/// 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<H: logging::Handler>(
|
||||
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<H: logging::Handler>(
|
||||
})
|
||||
}).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,
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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::<Submission>::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<Box<IMService>>,
|
||||
virtual_keyboard: VirtualKeyboard,
|
||||
modifiers_active: Vec<(KeyStateId, Modifier)>,
|
||||
pressed: Vec<(KeyStateId, SubmittedAction)>,
|
||||
}
|
||||
|
||||
@ -125,8 +132,10 @@ impl Submission {
|
||||
keycodes: &Vec<KeyCode>,
|
||||
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<Modifier> {
|
||||
HashSet::from_iter(
|
||||
self.modifiers_active.iter().map(|(_id, m)| m.clone())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,6 +195,12 @@ pub trait WarningHandler {
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -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<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) {
|
||||
unsafe {
|
||||
c::eek_virtual_keyboard_update_keymap(
|
||||
self.0,
|
||||
keyboard,
|
||||
);
|
||||
c::eek_virtual_keyboard_update_keymap(self.0, keyboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Reference in New Issue
Block a user