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

This commit is contained in:
Dorota Czaplejewicz
2020-02-05 10:32:01 +00:00
24 changed files with 310 additions and 87 deletions

View File

@ -45,7 +45,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: "erase"
preferences:
action: "show_prefs"
outline: "special"

View File

@ -45,7 +45,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: "erase"
preferences:
action: "show_prefs"
outline: "special"

View File

@ -46,7 +46,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: "erase"
preferences:
action: "show_prefs"
outline: "altline"

View File

@ -44,7 +44,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: "erase"
preferences:
action: "show_prefs"
outline: "default"

View File

@ -39,7 +39,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: "erase"
preferences:
action: "show_prefs"
outline: "altline"

View File

@ -46,7 +46,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: "erase"
preferences:
action: "show_prefs"
outline: "default"

View File

@ -195,7 +195,7 @@ buttons:
BackSpace:
outline: "wide"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: erase
Return:
outline: "wide"
icon: "key-enter"

View File

@ -195,7 +195,7 @@ buttons:
BackSpace:
outline: "wide"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: erase
Return:
outline: "wide"
icon: "key-enter"

View File

@ -39,7 +39,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: erase
preferences:
action: "show_prefs"
outline: "altline"

View File

@ -16,7 +16,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: erase
space:
outline: spaceline
text: " "

View File

@ -39,7 +39,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: erase
preferences:
action: "show_prefs"
outline: "altline"

View File

@ -45,7 +45,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: erase
preferences:
action: "show_prefs"
outline: "special"

View File

@ -39,9 +39,9 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: erase
preferences:
action: "show_prefs"
action: show_prefs
outline: "special"
icon: "keyboard-mode-symbolic"
show_numbers:

View File

@ -39,7 +39,7 @@ buttons:
BackSpace:
outline: "altline"
icon: "edit-clear-symbolic"
keysym: "BackSpace"
action: "erase"
preferences:
action: "show_prefs"
outline: "special"

View File

@ -116,6 +116,7 @@ settings_get_layout(GSettings *settings, char **type, char **layout)
GVariant *inputs = g_settings_get_value(settings, "sources");
// current layout is always first
g_variant_get_child(inputs, 0, "(ss)", type, layout);
g_variant_unref(inputs);
}
void
@ -139,9 +140,11 @@ eekboard_context_service_update_layout(EekboardContextService *context, enum squ
switch (priv->purpose) {
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_NUMBER:
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_PHONE:
g_free(keyboard_layout);
keyboard_layout = g_strdup("number");
break;
case ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL:
g_free(keyboard_layout);
keyboard_layout = g_strdup("terminal");
break;
default:

View File

@ -31,10 +31,29 @@ pub enum Action {
SetModifier(Modifier),
/// Submit some text
Submit {
/// Text to submit with input-method
/// Text to submit with input-method.
/// If None, then keys are to be submitted instead.
text: Option<CString>,
/// The key events this symbol submits when submitting text is not possible
keys: Vec<KeySym>,
},
/// Erase a position behind the cursor
Erase,
ShowPreferences,
}
impl Action {
pub fn is_locked(&self, view_name: &str) -> bool {
match self {
Action::LockView { lock, unlock: _ } => lock == view_name,
_ => false,
}
}
pub fn is_active(&self, view_name: &str) -> bool {
match self {
Action::SetView(view) => view == view_name,
Action::LockView { lock, unlock: _ } => lock == view_name,
_ => false,
}
}
}

View File

@ -15,6 +15,7 @@ use std::vec::Vec;
use xkbcommon::xkb;
use ::action;
use ::keyboard::{
KeyState, PressType,
generate_keymap, generate_keycodes, FormattingError
@ -264,6 +265,9 @@ enum Action {
SetView(String),
#[serde(rename="show_prefs")]
ShowPrefs,
/// Remove last character
#[serde(rename="erase")]
Erase,
}
#[derive(Debug, Clone, Deserialize, PartialEq)]
@ -386,13 +390,16 @@ impl Layout {
)
}).collect()
},
action::Action::Erase => vec![
*keymap.get("BackSpace")
.expect(&format!("BackSpace missing from keymap")),
],
_ => Vec::new(),
};
(
name.into(),
KeyState {
pressed: PressType::Released,
locked: false,
keycodes,
action,
}
@ -574,6 +581,7 @@ fn create_action<H: logging::Handler>(
SubmitData::Action(
Action::ShowPrefs
) => ::action::Action::ShowPreferences,
SubmitData::Action(Action::Erase) => action::Action::Erase,
SubmitData::Keysym(keysym) => ::action::Action::Submit {
text: None,
keys: vec!(::action::KeySym(
@ -605,7 +613,7 @@ fn create_action<H: logging::Handler>(
false => format!("U{:04X}", codepoint as u32),
})
}).collect(),
}
},
}
}

View File

@ -37,6 +37,7 @@ mod c {
);
}
/// Draws all buttons that are not in the base state
#[no_mangle]
pub extern "C"
fn squeek_layout_draw_all_changed(
@ -49,12 +50,13 @@ mod c {
layout.foreach_visible_button(|offset, button| {
let state = RefCell::borrow(&button.state).clone();
if state.pressed == keyboard::PressType::Pressed || state.locked {
let locked = state.action.is_active(&layout.current_view);
if state.pressed == keyboard::PressType::Pressed || locked {
render_button_at_position(
renderer, &cr,
offset,
button.as_ref(),
state.pressed, state.locked,
state.pressed, locked,
);
}
})

View File

@ -49,6 +49,23 @@ void imservice_connect_listeners(struct zwp_input_method_v2 *im, struct imservic
zwp_input_method_v2_add_listener(im, &input_method_listener, imservice);
}
void
eek_input_method_commit_string(struct zwp_input_method_v2 *zwp_input_method_v2, const char *text)
{
zwp_input_method_v2_commit_string(zwp_input_method_v2, text);
}
void
eek_input_method_delete_surrounding_text(struct zwp_input_method_v2 *zwp_input_method_v2, uint32_t before_length, uint32_t after_length) {
zwp_input_method_v2_delete_surrounding_text(zwp_input_method_v2, before_length, after_length);
};
void
eek_input_method_commit(struct zwp_input_method_v2 *zwp_input_method_v2, uint32_t serial)
{
zwp_input_method_v2_commit(zwp_input_method_v2, serial);
}
/// Declared explicitly because _destroy is inline,
/// making it unavailable in Rust
void imservice_destroy_im(struct zwp_input_method_v2 *im) {

View File

@ -1,3 +1,8 @@
/*! Manages zwp_input_method_v2 protocol.
*
* Library module.
*/
use std::boxed::Box;
use std::ffi::CString;
use std::fmt;
@ -32,6 +37,9 @@ pub mod c {
fn imservice_destroy_im(im: *mut c::InputMethod);
#[allow(improper_ctypes)] // IMService will never be dereferenced in C
pub fn imservice_connect_listeners(im: *mut InputMethod, imservice: *const IMService);
pub fn eek_input_method_commit_string(im: *mut InputMethod, text: *const c_char);
pub fn eek_input_method_delete_surrounding_text(im: *mut InputMethod, before: u32, after: u32);
pub fn eek_input_method_commit(im: *mut InputMethod, serial: u32);
fn eekboard_context_service_set_hint_purpose(state: *const StateManager, hint: u32, purpose: u32);
fn server_context_service_show_keyboard(imservice: *const UIManager);
fn server_context_service_hide_keyboard(imservice: *const UIManager);
@ -328,7 +336,7 @@ impl Default for IMProtocolState {
pub struct IMService {
/// Owned reference (still created and destroyed in C)
pub im: *const c::InputMethod,
pub im: *mut c::InputMethod,
/// Unowned reference. Be careful, it's shared with C at large
state_manager: *const c::StateManager,
/// Unowned reference. Be careful, it's shared with C at large
@ -340,6 +348,11 @@ pub struct IMService {
serial: Wrapping<u32>,
}
pub enum SubmitError {
/// The input method had not been activated
NotActive,
}
impl IMService {
pub fn new(
im: *mut c::InputMethod,
@ -364,4 +377,51 @@ impl IMService {
}
imservice
}
pub fn commit_string(&self, text: &CString) -> Result<(), SubmitError> {
match self.current.active {
true => {
unsafe {
c::eek_input_method_commit_string(self.im, text.as_ptr())
}
Ok(())
},
false => Err(SubmitError::NotActive),
}
}
pub fn delete_surrounding_text(
&self,
before: u32, after: u32,
) -> Result<(), SubmitError> {
match self.current.active {
true => {
unsafe {
c::eek_input_method_delete_surrounding_text(
self.im,
before, after,
)
}
Ok(())
},
false => Err(SubmitError::NotActive),
}
}
pub fn commit(&mut self) -> Result<(), SubmitError> {
match self.current.active {
true => {
unsafe {
c::eek_input_method_commit(self.im, self.serial.0)
}
self.serial += Wrapping(1u32);
Ok(())
},
false => Err(SubmitError::NotActive),
}
}
pub fn is_active(&self) -> bool {
self.current.active
}
}

View File

@ -1,14 +1,17 @@
/*! State of the emulated keyboard and keys.
* Regards the keyboard as if it was composed of switches. */
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::io;
use std::rc::Rc;
use std::string::FromUtf8Error;
use ::action::Action;
use ::logging;
// Traits
use std::io::Write;
use std::iter::{ FromIterator, IntoIterator };
@ -20,10 +23,14 @@ pub enum PressType {
pub type KeyCode = u32;
/// When the submitted actions of keys need to be tracked,
/// they need a stable, comparable ID
#[derive(PartialEq)]
pub struct KeyStateId(*const KeyState);
#[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<KeyCode>,
/// Static description of what the key does when pressed or released
@ -31,17 +38,6 @@ pub struct KeyState {
}
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_released(self) -> KeyState {
KeyState {
@ -49,6 +45,12 @@ impl KeyState {
..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 {
KeyStateId(keystate.as_ptr() as *const KeyState)
}
}
/// Sorts an iterator by converting it to a Vector and back
@ -66,9 +68,10 @@ fn sorted<'a, I: Iterator<Item=&'a str>>(
pub fn generate_keycodes<'a, C: IntoIterator<Item=&'a str>>(
key_names: C
) -> HashMap<String, u32> {
let special_keysyms = ["BackSpace", "Return"].iter().map(|&s| s);
HashMap::from_iter(
// sort to remove a source of indeterminism in keycode assignment
sorted(key_names.into_iter())
sorted(key_names.into_iter().chain(special_keysyms))
.map(|name| String::from(name))
.zip(9..)
)
@ -95,7 +98,10 @@ impl From<io::Error> for FormattingError {
}
}
/// Generates a de-facto single level keymap. TODO: actually drop second level
/// Generates a de-facto single level keymap.
// TODO: don't rely on keys and their order,
// but rather on what keysyms and keycodes are in use.
// Iterating actions makes it hard to deduplicate keysyms.
pub fn generate_keymap(
keystates: &HashMap::<String, KeyState>
) -> Result<String, FormattingError> {
@ -110,22 +116,40 @@ pub fn generate_keymap(
)?;
for (name, state) in keystates.iter() {
if let Action::Submit { text: _, keys } = &state.action {
if let 0 = keys.len() {
log_print!(
logging::Level::Warning,
"Key {} has no keysyms", name,
);
};
for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) {
match &state.action {
Action::Submit { text: _, keys } => {
if let 0 = keys.len() {
log_print!(
logging::Level::Warning,
"Key {} has no keysyms", name,
);
};
for (named_keysym, keycode) in keys.iter().zip(&state.keycodes) {
write!(
buf,
"
<{}> = {};",
named_keysym.0,
keycode,
)?;
}
},
Action::Erase => {
let mut keycodes = state.keycodes.iter();
write!(
buf,
"
<{}> = {};",
named_keysym.0,
keycode,
<BackSpace> = {};",
keycodes.next().expect("Erase key has no keycode"),
)?;
}
if let Some(_) = keycodes.next() {
log_print!(
logging::Level::Bug,
"Erase key has multiple keycodes",
);
}
},
_ => {},
}
}
@ -137,7 +161,9 @@ pub fn generate_keymap(
xkb_symbols \"squeekboard\" {{
name[Group1] = \"Letters\";
name[Group2] = \"Numbers/Symbols\";"
name[Group2] = \"Numbers/Symbols\";
key <BackSpace> {{ [ BackSpace ] }};"
)?;
for (name, state) in keystates.iter() {
@ -195,7 +221,6 @@ mod tests {
keys: vec!(KeySym("a".into()), KeySym("c".into())),
},
keycodes: vec!(9, 10),
locked: false,
pressed: PressType::Released,
},
}).unwrap();

View File

@ -629,7 +629,6 @@ pub struct Layout {
// 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>>>,
}
/// A builder structure for picking up layout data from storage
@ -661,7 +660,6 @@ impl Layout {
views: data.views,
keymap_str: data.keymap_str,
pressed_keys: HashSet::new(),
locked_keys: HashSet::new(),
margins: data.margins,
}
}
@ -672,7 +670,7 @@ impl Layout {
}
pub fn get_current_view(&self) -> &View {
&self.get_current_view_position().1
&self.views.get(&self.current_view).expect("Selected nonexistent view").1
}
fn set_view(&mut self, view: String) -> Result<(), NoSuchView> {
@ -742,6 +740,23 @@ impl Layout {
}
}
}
pub fn get_locked_keys(&self) -> Vec<Rc<RefCell<KeyState>>> {
let mut out = Vec::new();
let view = self.get_current_view();
for (_, row) in &view.get_rows() {
for (_, button) in &row.buttons {
let locked = {
let state = RefCell::borrow(&button.state).clone();
state.action.is_locked(&self.current_view)
};
if locked {
out.push(button.state.clone());
}
}
}
out
}
}
mod procedures {
@ -868,9 +883,9 @@ mod seat {
#[must_use]
fn unstick_locks(layout: &mut Layout) -> ViewChange {
let mut new_view = None;
for key in layout.locked_keys.clone() {
for key in layout.get_locked_keys().clone() {
let key: &Rc<RefCell<KeyState>> = key.borrow();
let mut key = RefCell::borrow_mut(key);
let key = RefCell::borrow(key);
match &key.action {
Action::LockView { lock: _, unlock: view } => {
new_view = Some(view.clone());
@ -881,7 +896,6 @@ mod seat {
a,
),
};
key.locked = false;
}
ViewChange {
@ -903,11 +917,7 @@ mod seat {
);
}
let mut key = rckey.borrow_mut();
submission.virtual_keyboard.switch(
&key.keycodes,
PressType::Pressed,
time,
);
submission.handle_press(&key, KeyState::get_id(rckey), time);
key.pressed = PressType::Pressed;
}
@ -926,32 +936,26 @@ mod seat {
// update
let key = key.into_released();
let key = match action {
Action::LockView { lock: _, unlock: _ } => key.into_activated(),
_ => key,
};
let mut locked = key.action.is_locked(&layout.current_view);
// process changes
match action {
Action::Submit { text: _, keys: _ } => {
Action::Submit { text: _, keys: _ }
| Action::Erase
=> {
unstick_locks(layout).apply();
submission.virtual_keyboard.switch(
&key.keycodes,
PressType::Released,
time,
);
submission.handle_release(KeyState::get_id(rckey), time);
},
Action::SetView(view) => {
try_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.
locked ^= true;
unstick_locks(layout)
// It doesn't matter what the resulting view should be,
// it's getting changed anyway.
.choose_view(
match key.locked {
match locked {
true => lock.clone(),
false => unlock.clone(),
}
@ -993,11 +997,6 @@ mod seat {
let pointer = ::util::Pointer(rckey.clone());
// Apply state changes
layout.pressed_keys.remove(&pointer);
if key.locked {
layout.locked_keys.insert(pointer);
} else {
layout.locked_keys.remove(&pointer);
}
// Commit activated button state changes
RefCell::replace(rckey, key);
}
@ -1012,7 +1011,6 @@ mod test {
pub fn make_state() -> Rc<RefCell<::keyboard::KeyState>> {
Rc::new(RefCell::new(::keyboard::KeyState {
pressed: PressType::Released,
locked: false,
keycodes: Vec::new(),
action: Action::SetView("default".into()),
}))
@ -1088,7 +1086,6 @@ mod test {
current_view: String::new(),
keymap_str: CString::new("").unwrap(),
kind: ArrangementKind::Base,
locked_keys: HashSet::new(),
pressed_keys: HashSet::new(),
// Lots of bottom margin
margins: Margins {

View File

@ -91,6 +91,11 @@ 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)
}
}
@ -141,7 +146,7 @@ fn set_layout(kind: String, name: String) {
.chain(inputs).collect();
settings.set_value(
"sources",
&variants::ArrayPairString(inputs).to_variant()
&variants::ArrayPairString(inputs).to_variant(),
);
settings.apply();
}

View File

@ -16,8 +16,12 @@
* 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 ::imservice;
use ::imservice::IMService;
use ::keyboard::{ KeyCode, KeyState, KeyStateId, PressType };
use ::logging;
use ::vkeyboard::VirtualKeyboard;
/// Gathers stuff defined in C or called by C
@ -57,6 +61,7 @@ pub mod c {
Submission {
imservice,
virtual_keyboard: VirtualKeyboard(vk),
pressed: Vec::new(),
}
))
}
@ -92,9 +97,91 @@ pub mod c {
#[derive(Clone, Copy)]
pub struct Timestamp(pub u32);
pub struct Submission {
// used by C callbacks internally, TODO: make use with virtual keyboard
#[allow(dead_code)]
imservice: Option<Box<IMService>>,
pub virtual_keyboard: VirtualKeyboard,
enum SubmittedAction {
/// A collection of keycodes that were pressed
VirtualKeyboard(Vec<KeyCode>),
IMService,
}
pub struct Submission {
imservice: Option<Box<IMService>>,
virtual_keyboard: VirtualKeyboard,
pressed: Vec<(KeyStateId, SubmittedAction)>,
}
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,
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 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,
}
},
(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,
PressType::Pressed,
time,
);
SubmittedAction::VirtualKeyboard(key.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) => {
self.virtual_keyboard.switch(
&keycodes,
PressType::Released,
time,
)
},
}
};
}
}