Merge branch 'text_input_enable' into 'master'
Submit and delete strings via text_input See merge request Librem5/squeekboard!304
This commit is contained in:
		@ -31,10 +31,13 @@ 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,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								src/data.rs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/data.rs
									
									
									
									
									
								
							@ -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,6 +390,10 @@ impl Layout {
 | 
			
		||||
                            )
 | 
			
		||||
                    }).collect()
 | 
			
		||||
                },
 | 
			
		||||
                action::Action::Erase => vec![
 | 
			
		||||
                    *keymap.get("BackSpace")
 | 
			
		||||
                        .expect(&format!("BackSpace missing from keymap")),
 | 
			
		||||
                ],
 | 
			
		||||
                _ => Vec::new(),
 | 
			
		||||
            };
 | 
			
		||||
            (
 | 
			
		||||
@ -558,6 +566,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(
 | 
			
		||||
@ -589,7 +598,7 @@ fn create_action<H: logging::Handler>(
 | 
			
		||||
                    false => format!("U{:04X}", codepoint as u32),
 | 
			
		||||
                })
 | 
			
		||||
            }).collect(),
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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,6 +23,11 @@ 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,
 | 
			
		||||
@ -49,6 +57,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 +80,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 +110,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 +128,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 +173,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() {
 | 
			
		||||
 | 
			
		||||
@ -876,11 +876,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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -906,13 +902,11 @@ mod seat {
 | 
			
		||||
 | 
			
		||||
        // 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)
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
                    )
 | 
			
		||||
                },
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user