Compare commits

...

2 Commits

Author SHA1 Message Date
e31ef19d03 layout: Make press handling more transparent
In adition to that, an attempt at multiple locked levels of keys was added.
2019-12-01 13:37:00 +00:00
dcbd55c31e layout: Eliminate locked and presed key lists
The lists duplicated a source of truth, and were complicating the code, which can now be separated cleaner by effect areas.
2019-11-30 16:11:52 +00:00
6 changed files with 346 additions and 160 deletions

View File

@ -6,8 +6,8 @@ use std::ffi::CString;
#[derive(Debug, Clone, PartialEq)]
pub struct KeySym(pub String);
/// Use to switch layouts
type Level = String;
/// Name of a set of buttons displayed at one time within the layout
type View = String;
/// Use to send modified keypresses
#[derive(Debug, Clone, PartialEq)]
@ -20,12 +20,12 @@ pub enum Modifier {
#[derive(Debug, Clone, PartialEq)]
pub enum Action {
/// Switch to this view
SetLevel(Level),
SetView(View),
/// Switch to a view and latch
LockLevel {
lock: Level,
LockView {
lock: View,
/// When unlocked by pressing it or emitting a key
unlock: Level,
unlock: View,
},
/// Set this modifier TODO: release?
SetModifier(Modifier),

View File

@ -504,7 +504,7 @@ fn create_action<H: WarningHandler>(
match submission {
SubmitData::Action(
Action::SetView(view_name)
) => ::action::Action::SetLevel(
) => ::action::Action::SetView(
filter_view_name(
name, view_name.clone(), &view_names,
warning_handler,
@ -512,7 +512,7 @@ fn create_action<H: WarningHandler>(
),
SubmitData::Action(Action::Locking {
lock_view, unlock_view
}) => ::action::Action::LockLevel {
}) => ::action::Action::LockView {
lock: filter_view_name(
name,
lock_view.clone(),

View File

@ -41,16 +41,43 @@ pub enum PressType {
Pressed = 1,
}
pub type KeyCode = u32;
#[derive(Debug, Clone)]
pub struct KeyState {
pub pressed: PressType,
pub locked: bool,
/// A cache of raw keycodes derived from Action::Sumbit given a keymap
pub keycodes: Vec<u32>,
pub keycodes: Vec<KeyCode>,
/// Static description of what the key does when pressed or released
pub action: Action,
}
impl KeyState {
#[must_use]
pub fn into_activated(self) -> KeyState {
match self.action {
Action::LockView { lock: _, unlock: _ } => KeyState {
locked: self.locked ^ true,
..self
},
_ => self,
}
}
#[must_use]
pub fn into_switched(self) -> KeyState {
use self::PressType::*;
KeyState {
pressed: match self.pressed {
Released => Pressed,
Pressed => Released,
},
..self
}
}
}
/// Generates a mapping where each key gets a keycode, starting from ~~8~~
/// HACK: starting from 9, because 8 results in keycode 0,
/// which the compositor likes to discard

View File

@ -20,6 +20,7 @@
use std::cell::RefCell;
use std::collections::{ HashMap, HashSet };
use std::ffi::CString;
use std::fmt;
use std::rc::Rc;
use std::vec::Vec;
@ -27,8 +28,11 @@ use ::action::Action;
use ::float_ord::FloatOrd;
use ::keyboard::{ KeyState, PressType };
use ::submission::{ Timestamp, VirtualKeyboard };
use ::util;
use ::util::Stack;
use std::borrow::Borrow;
use std::iter::FromIterator;
/// Gathers stuff defined in C or called by C
pub mod c {
@ -191,10 +195,7 @@ pub mod c {
pub extern "C"
fn squeek_layout_get_current_view(layout: *const Layout) -> *const View {
let layout = unsafe { &*layout };
let view_name = layout.current_view.clone();
layout.views.get(&view_name)
.expect("Current view doesn't exist")
.as_ref() as *const View
layout.get_current_view().as_ref() as *const View
}
#[no_mangle]
@ -354,20 +355,25 @@ pub mod c {
let virtual_keyboard = VirtualKeyboard(virtual_keyboard);
// The list must be copied,
// because it will be mutated in the loop
for key in layout.pressed_keys.clone() {
let ui_backend = UIBackend {
widget_to_layout,
keyboard: ui_keyboard,
};
let keys = layout.get_pressed_keys();
for key in keys {
let key: &Rc<RefCell<KeyState>> = key.borrow();
ui::release_key(
seat::release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
Some(&ui_backend),
time,
ui_keyboard,
key
key,
);
}
}
/// Release all buittons but don't redraw
/// Release all buttons but don't redraw.
/// Use for situations where the UI is gone, e.g. unmapped.
#[no_mangle]
pub extern "C"
fn squeek_layout_release_all_only(
@ -375,16 +381,18 @@ pub mod c {
virtual_keyboard: ZwpVirtualKeyboardV1, // TODO: receive a reference to the backend
time: u32,
) {
let layout = unsafe { &mut *layout };
let mut layout = unsafe { &mut *layout };
let virtual_keyboard = VirtualKeyboard(virtual_keyboard);
// The list must be copied,
// because it will be mutated in the loop
for key in layout.pressed_keys.clone() {
let time = Timestamp(time);
for key in layout.get_pressed_keys() {
let key: &Rc<RefCell<KeyState>> = key.borrow();
layout.release_key(
seat::release_key(
&mut layout,
&virtual_keyboard,
None, // don't update anything UI related
time,
&mut key.clone(),
Timestamp(time)
);
}
}
@ -448,7 +456,12 @@ pub mod c {
Point { x: x_widget, y: y_widget }
);
let pressed = layout.pressed_keys.clone();
let ui_backend = UIBackend {
keyboard: ui_keyboard,
widget_to_layout,
};
let pressed = layout.get_pressed_keys();
let state_place = {
let view = layout.get_current_view();
let place = view.find_button_by_position(point);
@ -460,18 +473,16 @@ pub mod c {
if let Some((mut state, c_place)) = state_place {
let mut found = false;
for wrapped_key in pressed {
let key: &Rc<RefCell<KeyState>> = wrapped_key.borrow();
if Rc::ptr_eq(&state, &wrapped_key.0) {
for key in pressed {
if Rc::ptr_eq(&state, &key) {
found = true;
} else {
ui::release_key(
seat::release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
Some(&ui_backend),
time,
ui_keyboard,
key,
&key,
);
}
}
@ -480,15 +491,13 @@ pub mod c {
unsafe { eek_gtk_on_button_pressed(c_place, ui_keyboard) };
}
} else {
for wrapped_key in pressed {
let key: &Rc<RefCell<KeyState>> = wrapped_key.borrow();
ui::release_key(
for key in pressed {
seat::release_key(
layout,
&virtual_keyboard,
&widget_to_layout,
Some(&ui_backend),
time,
ui_keyboard,
key,
&key,
);
}
}
@ -740,12 +749,32 @@ pub enum ArrangementKind {
Wide = 1,
}
enum ViewLockKind {
/// Lock gets released the next time something is submitted
OneShot,
/// Lock persists until the lock button is clicked again
Persisting,
}
pub struct ViewLock {
/// Name of the view
view: String,
/// ID of the lock, for unlocking.
/// Ideally unique within the layout
id: String,
kind: ViewLockKind,
}
// TODO: split into sth like
// Arrangement (views) + details (keymap) + State (keys)
/// State of the UI, contains the backend as well
pub struct Layout {
pub kind: ArrangementKind,
pub current_view: String,
/// The view after unlocking all locking view locks.
pub base_view: String,
/// A stack of views that are locked, later locks with higher indices.
/// Setting a view directly discards all.
pub locked_views: Stack<ViewLock>,
// Views own the actual buttons which have state
// Maybe they should own UI only,
// and keys should be owned by a dedicated non-UI-State?
@ -755,15 +784,7 @@ pub struct Layout {
/// xkb keymap applicable to the contained keys. Unchangeable
pub keymap_str: CString,
// Changeable state
// a Vec would be enough, but who cares, this will be small & fast enough
// TODO: turn those into per-input point *_buttons to track dragging.
// The renderer doesn't need the list of pressed keys any more,
// because it needs to iterate
// through all buttons of the current view anyway.
// When the list tracks actual location,
// it becomes possible to place popovers and other UI accurately.
pub pressed_keys: HashSet<::util::Pointer<RefCell<KeyState>>>,
pub locked_keys: HashSet<::util::Pointer<RefCell<KeyState>>>,
// TODO: store clicked buttons per-input point to track dragging.
}
/// A builder structure for picking up layout data from storage
@ -772,8 +793,23 @@ pub struct LayoutData {
pub keymap_str: CString,
}
#[derive(Debug)]
struct NoSuchView;
impl fmt::Display for NoSuchView {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(out, "No such view")
}
}
struct NoSuchLock;
impl fmt::Display for NoSuchLock {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(out, "No such lock")
}
}
// Unfortunately, changes are not atomic due to mutability :(
// An error will not be recoverable
// The usage of &mut on Rc<RefCell<KeyState>> doesn't mean anything special.
@ -782,104 +818,122 @@ impl Layout {
pub fn new(data: LayoutData, kind: ArrangementKind) -> Layout {
Layout {
kind,
current_view: "base".to_owned(),
base_view: "base".to_owned(),
locked_views: Vec::new(),
views: data.views,
keymap_str: data.keymap_str,
pressed_keys: HashSet::new(),
locked_keys: HashSet::new(),
}
}
fn get_current_view(&self) -> &Box<View> {
self.views.get(&self.current_view).expect("Selected nonexistent view")
let view_name = self.locked_views.iter().next_back()
.map(|lock| &lock.view)
.unwrap_or(&self.base_view);
self.views.get(view_name).expect("Selected nonexistent view")
}
/// Returns all keys matching filter, without duplicates
fn get_filtered_keys<F>(&self, pred: F) -> Vec<Rc<RefCell<KeyState>>>
where F: Fn(&Box<Button>) -> Option<Rc<RefCell<KeyState>>>
{
let keys = self.views.iter().flat_map(|(_name, view)| {
view.rows.iter().flat_map(|row| {
row.buttons.iter().filter_map(|x| pred(x))
})
});
// Key states can be attached to multiple buttons, so duplicates must
// be removed
let unique: HashSet<util::Pointer<RefCell<KeyState>>>
= HashSet::from_iter(
keys.map(|key| util::Pointer(key.clone()))
);
unique.into_iter()
.map(|ptr| ptr.0)
.collect()
}
fn get_pressed_keys(&self) -> Vec<Rc<RefCell<KeyState>>> {
self.get_filtered_keys(|button| {
let pressed = RefCell::borrow(&button.state).pressed;
match pressed {
PressType::Pressed => Some(button.state.clone()),
PressType::Released => None
}
})
}
fn get_locked_keys(&self) -> Vec<Rc<RefCell<KeyState>>> {
self.get_filtered_keys(|button| {
let locked = RefCell::borrow(&button.state).locked;
match locked {
true => Some(button.state.clone()),
false => None
}
})
}
/// Top oneshot locks can be all removed when a button is pressed
fn get_top_oneshot_locks_idx(&self) -> usize {
let last_persisting_idx = self.locked_views.iter()
.rposition(|lock| match lock.kind {
ViewLockKind::Persisting => true,
ViewLockKind::OneShot => false,
});
match last_persisting_idx {
// don't remove the last persisting item
Some(idx) => idx + 1,
None => 0,
}
}
fn lock_view(&mut self, view: String, id: String)
-> Result<(), NoSuchView>
{
if self.views.contains_key(&view) {
self.locked_views.push(ViewLock {
view,
id,
kind: ViewLockKind::OneShot,
});
Ok(())
} else {
Err(NoSuchView)
}
}
fn unlock_view(&mut self, id: String) -> Result<(), NoSuchLock> {
let lock_idx = self.locked_views.iter()
// find the first occurrence: locking the same key multiple times
// sounds like not something we want to do
.position(|lock| lock.id == id);
lock_idx.map(|idx| self.locked_views.truncate(idx))
.ok_or(NoSuchLock)
}
fn set_view(&mut self, view: String) -> Result<(), NoSuchView> {
if self.views.contains_key(&view) {
self.current_view = view;
self.base_view = view;
self.locked_views = Vec::new();
Ok(())
} else {
Err(NoSuchView)
}
}
fn release_key(
&mut self,
virtual_keyboard: &VirtualKeyboard,
mut key: &mut Rc<RefCell<KeyState>>,
time: Timestamp,
) {
if !self.pressed_keys.remove(&::util::Pointer(key.clone())) {
eprintln!("Warning: key {:?} was not pressed", key);
}
virtual_keyboard.switch(
&mut key.borrow_mut(),
PressType::Released,
time,
);
self.set_level_from_press(&mut key);
}
fn press_key(
&mut self,
virtual_keyboard: &VirtualKeyboard,
key: &mut Rc<RefCell<KeyState>>,
time: Timestamp,
) {
if !self.pressed_keys.insert(::util::Pointer(key.clone())) {
eprintln!("Warning: key {:?} was already pressed", key);
}
let mut bkey = key.borrow_mut();
virtual_keyboard.switch(
&mut key.borrow_mut(),
&bkey.keycodes,
PressType::Pressed,
time,
);
}
fn set_level_from_press(&mut self, key: &Rc<RefCell<KeyState>>) {
let keys = self.locked_keys.clone();
for key in &keys {
self.locked_keys.remove(key);
self.set_state_from_press(key.borrow());
}
// Don't handle the same key twice, but handle it at least once,
// because its press is the reason we're here
if !keys.contains(&::util::Pointer(key.clone())) {
self.set_state_from_press(key);
}
}
fn set_state_from_press(&mut self, key: &Rc<RefCell<KeyState>>) {
// Action should not hold a reference to key,
// because key is later borrowed for mutation. So action is cloned.
// RefCell::borrow() is covered up by (dyn Borrow)::borrow()
// if used like key.borrow() :(
let action = RefCell::borrow(key).action.clone();
let view_name = match action {
Action::SetLevel(name) => {
Some(name.clone())
},
Action::LockLevel { lock, unlock } => {
let locked = {
let mut key = key.borrow_mut();
key.locked ^= true;
key.locked
};
if locked {
self.locked_keys.insert(::util::Pointer(key.clone()));
}
Some(if locked { lock } else { unlock }.clone())
},
_ => None,
};
if let Some(view_name) = view_name {
if let Err(_e) = self.set_view(view_name.clone()) {
eprintln!("No such view: {}, ignoring switch", view_name)
};
};
bkey.pressed = PressType::Pressed;
}
}
@ -924,8 +978,7 @@ mod procedures {
key: &Rc<RefCell<KeyState>>,
ui_keyboard: c::EekGtkKeyboard,
) {
let paths = ::layout::procedures::find_key_paths(&view, key);
for (_row, button) in paths {
for (_row, button) in find_key_paths(&view, key) {
unsafe {
c::procedures::eek_gtk_on_button_released(
button.as_ref() as *const Button,
@ -1012,45 +1065,150 @@ mod procedures {
}
}
pub struct UIBackend {
widget_to_layout: c::procedures::Transformation,
keyboard: c::EekGtkKeyboard,
}
/// Top level UI procedures
mod ui {
mod seat {
use super::*;
/// Only use from release_key for when the view switch is ignored
fn set_all_unlocked(layout: &mut Layout) {
for key in layout.get_locked_keys() {
let mut key = RefCell::borrow_mut(&key);
match &key.action {
Action::LockView { lock: _, unlock: _ } => {},
a => eprintln!(
"BUG: action {:?} was found inside locked keys",
a
),
};
key.locked = false;
}
}
/// Only use from release_key. Changes state of other keys only.
fn handle_set_view(layout: &mut Layout, view_name: String) {
set_all_unlocked(layout);
layout.set_view(view_name.clone())
.map_err(|e|
eprintln!("Bad view {} ({}), ignoring", view_name, e)
).ok();
}
fn handle_unstick_locks(layout: &mut Layout) {
let oneshot_idx = layout.get_top_oneshot_locks_idx();
let (_permalocks, onetimes) = layout.locked_views.split_at(oneshot_idx);
// Convert into a hashmap for easier finding of elements
let onetime_ids = HashSet::<&String>::from_iter(
onetimes.into_iter().map(|lock| &lock.id)
);
for key in layout.get_locked_keys() {
let mut key = RefCell::borrow_mut(&key);
match &key.action {
Action::LockView { lock: _, unlock: id } => {
if onetime_ids.contains(id) {
key.locked = false;
}
},
a => eprintln!(
"BUG: action {:?} was found inside locked keys",
a
),
};
}
layout.locked_views.truncate(oneshot_idx);
}
// TODO: turn into release_button
pub fn release_key(
layout: &mut Layout,
virtual_keyboard: &VirtualKeyboard,
widget_to_layout: &c::procedures::Transformation,
ui: Option<&UIBackend>,
time: Timestamp,
ui_keyboard: c::EekGtkKeyboard,
key: &Rc<RefCell<KeyState>>,
rckey: &Rc<RefCell<KeyState>>,
) {
layout.release_key(virtual_keyboard, &mut key.clone(), time);
let key: KeyState = {
RefCell::borrow(rckey).clone()
};
let action = key.action.clone();
let view = layout.get_current_view();
let action = RefCell::borrow(key).action.clone();
if let Action::ShowPreferences = action {
let paths = ::layout::procedures::find_key_paths(
view, key
);
// getting first item will cause mispositioning
// with more than one button with the same key
// on the keyboard
if let Some((row, button)) = paths.get(0) {
let bounds = ::layout::procedures::get_button_bounds(
view, row, button
).unwrap_or_else(|| {
eprintln!("BUG: Clicked button has no position?");
c::Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 }
});
::popover::show(
ui_keyboard,
widget_to_layout.reverse_bounds(bounds)
// update
let key = key.into_switched();
let key = match action {
Action::LockView { lock: _, unlock: _ } => key.into_activated(),
_ => key,
};
// process changes
match action {
Action::Submit { text: _, keys: _ } => {
handle_unstick_locks(layout);
virtual_keyboard.switch(
&key.keycodes,
PressType::Released,
time,
);
}
}
procedures::release_ui_buttons(view, key, ui_keyboard);
},
Action::SetView(view) => {
handle_set_view(layout, view)
},
Action::LockView { lock, unlock } => {
// The button that triggered this will be in the right state
// due to commit at the end.
set_all_unlocked(layout);
match key.locked {
true => {
layout.lock_view(lock.clone(), unlock).map_err(|e|
eprintln!("Bad view {} ({}), ignoring", lock, e)
).ok();
},
false => {
layout.unlock_view(unlock.clone()).map_err(|e| {
eprintln!(
"BUG: Bad id {} ({}), resetting locks",
unlock, e
);
layout.set_view("base".into()).unwrap()
}).ok();
},
// TODO: stuck => stick_view(lock, unlock)
}
},
Action::ShowPreferences => if let Some(ui) = &ui {
let view = layout.get_current_view();
let paths = ::layout::procedures::find_key_paths(
view, &rckey
);
// Getting first item will cause mispositioning
// with more than one button with the same key
// on the keyboard.
if let Some((row, button)) = paths.get(0) {
let bounds = ::layout::procedures::get_button_bounds(
view, row, button
).unwrap_or_else(|| {
eprintln!("BUG: Clicked button has no position?");
c::Bounds { x: 0f64, y: 0f64, width: 0f64, height: 0f64 }
});
::popover::show(
ui.keyboard,
ui.widget_to_layout.reverse_bounds(bounds)
);
}
},
Action::SetModifier(_) => eprintln!("Modifiers unsupported"),
};
//layout.release_key(virtual_keyboard, &mut key.clone(), time);
if let Some(ui) = ui {
let view = layout.get_current_view();
procedures::release_ui_buttons(view, &rckey, ui.keyboard);
};
// Commits activated button state changes
RefCell::replace(rckey, key);
}
}
@ -1065,7 +1223,7 @@ mod test {
pressed: PressType::Released,
locked: false,
keycodes: Vec::new(),
action: Action::SetLevel("default".into()),
action: Action::SetView("default".into()),
}))
}

View File

@ -1,6 +1,6 @@
/*! Managing the events belonging to virtual-keyboard interface. */
use ::keyboard::{ KeyState, PressType };
use ::keyboard::{ KeyCode, PressType };
/// Gathers stuff defined in C or called by C
pub mod c {
@ -33,16 +33,14 @@ impl VirtualKeyboard {
// TODO: split out keyboard state management
pub fn switch(
&self,
key: &mut KeyState,
keycodes: &Vec<KeyCode>,
action: PressType,
timestamp: Timestamp,
) {
key.pressed = action.clone();
let keycodes_count = key.keycodes.len();
for keycode in key.keycodes.iter() {
let keycodes_count = keycodes.len();
for keycode in keycodes.iter() {
let keycode = keycode - 8;
match (&key.pressed, keycodes_count) {
match (action, keycodes_count) {
// Pressing a key made out of a single keycode is simple:
// press on press, release on release.
(_, 1) => unsafe {

View File

@ -198,6 +198,9 @@ pub trait WarningHandler {
fn handle(&mut self, warning: &str);
}
/// TODO: only allow indexing, push, peek, and pop
pub type Stack<T> = Vec<T>;
#[cfg(test)]
mod tests {
use super::*;