Merge remote-tracking branch 'upstream/master' into scaling

This commit is contained in:
Dorota Czaplejewicz
2020-03-07 10:46:09 +00:00
24 changed files with 412 additions and 94 deletions

View File

@ -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.

View File

@ -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,
}

View File

@ -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,

View File

@ -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)]
@ -31,7 +49,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<KeyCode>,
/// Static description of what the key does when pressed or released
pub action: Action,
@ -46,6 +64,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<RefCell<KeyState>>) -> KeyStateId {

View File

@ -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"
#include "text-input-unstable-v3-client-protocol.h"
@ -62,6 +63,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

View File

@ -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
@ -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::*;
@ -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(
@ -961,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
@ -987,10 +1028,6 @@ mod seat {
}
}
},
Action::SetModifier(_) => log_print!(
logging::Level::Bug,
"Modifiers unsupported",
),
};
let pointer = ::util::Pointer(rckey.clone());
@ -1006,6 +1043,7 @@ mod test {
use super::*;
use std::ffi::CString;
use ::keyboard::PressType;
pub fn make_state() -> Rc<RefCell<::keyboard::KeyState>> {
Rc::new(RefCell::new(::keyboard::KeyState {

View File

@ -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)
}
}
}

View File

@ -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 ::action::Action;
use std::collections::HashSet;
use std::ffi::CString;
use ::action::Modifier;
use ::imservice;
use ::imservice::IMService;
use ::keyboard::{ KeyCode, KeyState, KeyStateId, PressType };
use ::logging;
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,62 +112,72 @@ enum SubmittedAction {
pub struct Submission {
imservice: Option<Box<IMService>>,
virtual_keyboard: VirtualKeyboard,
modifiers_active: Vec<(KeyStateId, Modifier)>,
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<KeyCode>,
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 mods_are_on = !self.modifiers_active.is_empty();
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,
let was_committed_as_text = match (&mut self.imservice, mods_are_on) {
(Some(imservice), false) => {
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,
}
},
/* 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())
},
};
@ -187,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

@ -197,6 +197,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::*;

View File

@ -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);
}
}
}

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);
}
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);