328 lines
11 KiB
Rust
328 lines
11 KiB
Rust
/*! Managing the state of text input in the application.
|
|
*
|
|
* This is a library module.
|
|
*
|
|
* It needs to combine text-input and virtual-keyboard protocols
|
|
* to achieve a consistent view of the text-input state,
|
|
* and to submit exactly what the user wanted.
|
|
*
|
|
* It must also not get tripped up by sudden disappearances of interfaces.
|
|
*
|
|
* The virtual-keyboard interface is always present.
|
|
*
|
|
* The text-input interface may not be presented,
|
|
* and, for simplicity, no further attempt to claim it is made.
|
|
*
|
|
* 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 crate::vkeyboard::c::ZwpVirtualKeyboardV1;
|
|
use crate::action::Modifier;
|
|
use crate::imservice;
|
|
use crate::imservice::IMService;
|
|
use crate::keyboard::{ KeyCode, KeyStateId, Modifiers, PressType };
|
|
use crate::layout;
|
|
use crate::util::vec_remove;
|
|
use crate::vkeyboard;
|
|
use crate::vkeyboard::VirtualKeyboard;
|
|
|
|
// traits
|
|
use std::iter::FromIterator;
|
|
|
|
/// Gathers stuff defined in C or called by C
|
|
pub mod c {
|
|
use super::*;
|
|
|
|
use crate::util::c::Wrapped;
|
|
|
|
pub type Submission = Wrapped<super::Submission>;
|
|
|
|
|
|
#[no_mangle]
|
|
pub extern "C"
|
|
fn submission_use_layout(
|
|
submission: Submission,
|
|
layout: *const layout::Layout,
|
|
time: u32,
|
|
) {
|
|
let submission = submission.clone_ref();
|
|
let mut submission = submission.borrow_mut();
|
|
let layout = unsafe { &*layout };
|
|
submission.use_layout(&layout.shape, Timestamp(time));
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C"
|
|
fn submission_hint_available(submission: Submission) -> u8 {
|
|
let submission = submission.clone_ref();
|
|
let submission = submission.borrow();
|
|
let active = submission.imservice.as_ref()
|
|
.map(|imservice| imservice.is_active());
|
|
(Some(true) == active) as u8
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct Timestamp(pub u32);
|
|
|
|
#[derive(Clone)]
|
|
enum SubmittedAction {
|
|
/// A collection of keycodes that were pressed
|
|
VirtualKeyboard(Vec<KeyCode>),
|
|
IMService,
|
|
}
|
|
|
|
pub struct Submission {
|
|
imservice: Option<Box<IMService>>,
|
|
virtual_keyboard: VirtualKeyboard,
|
|
modifiers_active: Vec<(KeyStateId, Modifier)>,
|
|
pressed: Vec<(KeyStateId, SubmittedAction)>,
|
|
keymap_fds: Vec<vkeyboard::c::KeyMap>,
|
|
keymap_idx: Option<usize>,
|
|
}
|
|
|
|
pub enum SubmitData<'a> {
|
|
Text(&'a CString),
|
|
Erase,
|
|
Keycodes,
|
|
}
|
|
|
|
impl Submission {
|
|
pub fn new(vk: ZwpVirtualKeyboardV1, imservice: Option<Box<IMService>>) -> Self {
|
|
Submission {
|
|
imservice,
|
|
modifiers_active: Vec::new(),
|
|
virtual_keyboard: VirtualKeyboard(vk),
|
|
pressed: Vec::new(),
|
|
keymap_fds: Vec::new(),
|
|
keymap_idx: None,
|
|
}
|
|
}
|
|
|
|
/// Sends a submit text event if possible;
|
|
/// otherwise sends key press and makes a note of it
|
|
pub fn handle_press(
|
|
&mut self,
|
|
key_id: KeyStateId,
|
|
data: SubmitData,
|
|
keycodes: &Vec<KeyCode>,
|
|
time: Timestamp,
|
|
) {
|
|
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,
|
|
}
|
|
|
|
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 submit_action = match was_committed_as_text {
|
|
true => SubmittedAction::IMService,
|
|
false => {
|
|
let keycodes_count = keycodes.len();
|
|
for keycode in keycodes.iter() {
|
|
self.select_keymap(keycode.keymap_idx, time);
|
|
let keycode = keycode.code;
|
|
match keycodes_count {
|
|
// Pressing a key made out of a single keycode is simple:
|
|
// press on press, release on release.
|
|
1 => self.virtual_keyboard.switch(
|
|
keycode,
|
|
PressType::Pressed,
|
|
time,
|
|
),
|
|
// A key made of multiple keycodes
|
|
// has to submit them one after the other.
|
|
_ => {
|
|
self.virtual_keyboard.switch(
|
|
keycode.clone(),
|
|
PressType::Pressed,
|
|
time,
|
|
);
|
|
self.virtual_keyboard.switch(
|
|
keycode.clone(),
|
|
PressType::Released,
|
|
time,
|
|
);
|
|
},
|
|
};
|
|
}
|
|
SubmittedAction::VirtualKeyboard(keycodes.clone())
|
|
},
|
|
};
|
|
|
|
self.pressed.push((key_id, submit_action));
|
|
}
|
|
|
|
pub fn handle_release(&mut self, key_id: KeyStateId, time: Timestamp) {
|
|
let index = self.pressed.iter().position(|(id, _)| *id == key_id);
|
|
if let Some(index) = index {
|
|
let (_id, action) = self.pressed.remove(index);
|
|
match action {
|
|
// string already sent, nothing to do
|
|
SubmittedAction::IMService => {},
|
|
// no matter if the imservice got activated,
|
|
// keys must be released
|
|
SubmittedAction::VirtualKeyboard(keycodes) => {
|
|
let keycodes_count = keycodes.len();
|
|
match keycodes_count {
|
|
1 => {
|
|
let keycode = &keycodes[0];
|
|
self.select_keymap(keycode.keymap_idx, time);
|
|
self.virtual_keyboard.switch(
|
|
keycode.code,
|
|
PressType::Released,
|
|
time,
|
|
);
|
|
},
|
|
// Design choice here: submit multiple all at press time
|
|
// and do nothing at release time.
|
|
_ => {},
|
|
};
|
|
},
|
|
}
|
|
};
|
|
}
|
|
|
|
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::Shift => Modifiers::SHIFT,
|
|
Modifier::Control => Modifiers::CONTROL,
|
|
Modifier::Alt => Modifiers::MOD1,
|
|
Modifier::Mod4 => Modifiers::MOD4,
|
|
})
|
|
.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())
|
|
)
|
|
}
|
|
|
|
fn clear_all_modifiers(&mut self) {
|
|
// Looks like an optimization,
|
|
// but preemptive cleaning is needed before setting a new keymap,
|
|
// so removing this check would break keymap setting.
|
|
if self.modifiers_active.is_empty() {
|
|
return;
|
|
}
|
|
self.modifiers_active = Vec::new();
|
|
self.virtual_keyboard.set_modifiers_state(Modifiers::empty())
|
|
}
|
|
|
|
fn release_all_virtual_keys(&mut self, time: Timestamp) {
|
|
let virtual_pressed = self.pressed
|
|
.clone().into_iter()
|
|
.filter_map(|(id, action)| {
|
|
match action {
|
|
SubmittedAction::VirtualKeyboard(_) => Some(id),
|
|
_ => None,
|
|
}
|
|
});
|
|
for id in virtual_pressed {
|
|
self.handle_release(id, time);
|
|
}
|
|
}
|
|
|
|
|
|
/// Changes keymap and clears pressed keys and modifiers.
|
|
///
|
|
/// It's not obvious if clearing is the right thing to do,
|
|
/// but keymap update may (or may not) do that,
|
|
/// possibly putting self.modifiers_active and self.pressed out of sync,
|
|
/// so a consistent stance is adopted to avoid that.
|
|
/// Alternatively, modifiers could be restored on the new keymap.
|
|
/// That approach might be difficult
|
|
/// due to modifiers meaning different things in different keymaps.
|
|
fn select_keymap(&mut self, idx: usize, time: Timestamp) {
|
|
if self.keymap_idx != Some(idx) {
|
|
self.keymap_idx = Some(idx);
|
|
let modifiers = self.modifiers_active.clone();
|
|
self.clear_all_modifiers();
|
|
self.release_all_virtual_keys(time);
|
|
let keymap = &self.keymap_fds[idx];
|
|
self.virtual_keyboard.update_keymap(keymap);
|
|
self.modifiers_active = modifiers;
|
|
self.update_modifiers();
|
|
}
|
|
}
|
|
|
|
pub fn use_layout(&mut self, layout: &layout::LayoutData, time: Timestamp) {
|
|
self.keymap_fds = layout.keymaps.iter()
|
|
.map(|keymap_str| vkeyboard::c::KeyMap::from_cstr(
|
|
keymap_str.as_c_str()
|
|
))
|
|
.collect();
|
|
self.keymap_idx = None;
|
|
|
|
// This can probably be eliminated,
|
|
// because key presses can trigger an update anyway.
|
|
// However, self.keymap_idx needs to become Option<>
|
|
// in order to force update on new layouts.
|
|
self.select_keymap(0, time);
|
|
|
|
// Reset modifiers when changing layouts.
|
|
self.clear_all_modifiers();
|
|
}
|
|
}
|