zng_ext_input/
keyboard.rs

1//! Keyboard events and service.
2//!
3//! This extension processes the raw keyboard events retargeting then to the focused widget, generating derived events
4//! and variables.
5//!
6//! # Events
7//!
8//! Events this extension provides.
9//!
10//! * [`KEY_INPUT_EVENT`]
11//! * [`MODIFIERS_CHANGED_EVENT`]
12//!
13//! # Services
14//!
15//! Services this extension provides.
16//!
17//! * [`KEYBOARD`]
18
19use std::{collections::HashSet, time::Duration};
20
21use zng_app::{
22    DInstant, HeadlessApp,
23    event::{event, event_args},
24    shortcut::{GestureKey, KeyGesture, ModifierGesture, ModifiersState, Shortcut},
25    view_process::{
26        VIEW_PROCESS_INITED_EVENT,
27        raw_device_events::InputDeviceId,
28        raw_events::{
29            RAW_ANIMATIONS_CONFIG_CHANGED_EVENT, RAW_KEY_INPUT_EVENT, RAW_KEY_REPEAT_CONFIG_CHANGED_EVENT, RAW_WINDOW_FOCUS_EVENT,
30            RawKeyInputArgs,
31        },
32    },
33    widget::info::InteractionPath,
34    window::WindowId,
35};
36use zng_app_context::app_local;
37use zng_clone_move::clmv;
38use zng_ext_window::WINDOWS;
39use zng_layout::unit::{Factor, FactorUnits};
40use zng_txt::Txt;
41use zng_var::{Var, var, var_default};
42use zng_view_api::config::AnimationsConfig;
43pub use zng_view_api::{
44    config::KeyRepeatConfig,
45    keyboard::{Key, KeyCode, KeyLocation, KeyState, NativeKeyCode},
46};
47
48use crate::focus::FOCUS;
49
50event_args! {
51    /// Arguments for [`KEY_INPUT_EVENT`].
52    pub struct KeyInputArgs {
53        /// Window that received the event.
54        pub window_id: WindowId,
55
56        /// Device that generated the event.
57        pub device_id: InputDeviceId,
58
59        /// Physical key.
60        pub key_code: KeyCode,
61
62        /// The location of the key on the keyboard.
63        pub key_location: KeyLocation,
64
65        /// If the key was pressed or released.
66        pub state: KeyState,
67
68        /// Semantic key.
69        ///
70        /// Pressing `Shift+A` key will produce `Key::Char('a')` in QWERTY keyboards, the modifiers are not applied.
71        pub key: Key,
72        /// Semantic key modified by the current active modifiers.
73        ///
74        /// Pressing `Shift+A` key will produce `Key::Char('A')` in QWERTY keyboards, the modifiers are applied.
75        pub key_modified: Key,
76
77        /// Text typed.
78        ///
79        /// This is only set during [`KeyState::Pressed`] of a key that generates text.
80        ///
81        /// This is usually the `key_modified` char, but is also `'\r'` for `Key::Enter`. On Windows when a dead key was
82        /// pressed earlier but cannot be combined with the character from this key press, the produced text
83        /// will consist of two characters: the dead-key-character followed by the character resulting from this key press.
84        pub text: Txt,
85
86        /// What modifier keys where pressed when this event happened.
87        pub modifiers: ModifiersState,
88
89        /// Number of repeats generated by holding the key pressed.
90        ///
91        /// This is zero for the first key press, increments by one for each event while the key is held pressed.
92        pub repeat_count: u32,
93
94        /// The focused element at the time of the key input.
95        pub target: InteractionPath,
96
97        ..
98
99        /// If is in [`target`](Self::target).
100        fn is_in_target(&self, id: WidgetId) -> bool {
101            self.target.contains(id)
102        }
103    }
104
105    /// Arguments for [`MODIFIERS_CHANGED_EVENT`].
106    pub struct ModifiersChangedArgs {
107        /// Previous modifiers state.
108        pub prev_modifiers: ModifiersState,
109
110        /// Current modifiers state.
111        pub modifiers: ModifiersState,
112
113        ..
114
115        /// Broadcast to all.
116        fn is_in_target(&self, id: WidgetId) -> bool {
117            true
118        }
119    }
120}
121/// Shortcut methods.
122impl KeyInputArgs {
123    /// Gets the modified key for Numpad keys and the unmodified key for the rest.
124    pub fn shortcut_key(&self) -> &Key {
125        if matches!(self.key_location, KeyLocation::Numpad) {
126            &self.key_modified
127        } else {
128            &self.key
129        }
130    }
131
132    /// Gets the best shortcut approximation of the current pressed state.
133    ///
134    /// Note that the returned shortcut may be [invalid], don't use this to match shortcut presses, use
135    /// the [`GESTURES`] service for that. This method is for key binding editors.
136    /// This helper also does not support chords, only single key and modifier gestures.
137    ///
138    /// Returns `None` if this event if for a key release.
139    ///
140    /// [`GESTURES`]: crate::gesture::GESTURES
141    /// [invalid]: Shortcut::is_valid
142    pub fn editing_shortcut(&self) -> Option<Shortcut> {
143        if matches!(self.state, KeyState::Released) {
144            return None;
145        }
146
147        let source_key = self.shortcut_key();
148        if let Ok(modifier) = ModifierGesture::try_from(source_key)
149            && (self.modifiers.is_empty() || self.modifiers == modifier.modifiers_state())
150        {
151            return Some(Shortcut::Modifier(modifier));
152        }
153        let key = match source_key {
154            Key::Unidentified => GestureKey::try_from(self.key_code).unwrap_or(GestureKey::Key(Key::Unidentified)),
155            key => GestureKey::try_from(key.clone()).unwrap_or(GestureKey::Key(Key::Unidentified)),
156        };
157        Some(Shortcut::Gesture(KeyGesture {
158            modifiers: self.modifiers,
159            key,
160        }))
161    }
162}
163
164/// Text input methods.
165///
166/// The [`text`] field contains the raw text associated with the key-press by the operating system,
167/// these methods normalize and filter this text.
168///
169/// [`text`]: KeyInputArgs::text
170impl KeyInputArgs {
171    /// Returns `true` if the character is the backspace and `CTRL` is not pressed.
172    pub fn is_backspace(&self) -> bool {
173        !self.modifiers.contains(ModifiersState::CTRL) && self.text.contains('\u{8}')
174    }
175
176    /// Returns `true` if the character is delete and `CTRL` is not pressed.
177    pub fn is_delete(&self) -> bool {
178        !self.modifiers.contains(ModifiersState::CTRL) && self.text.contains('\u{7F}')
179    }
180
181    /// Returns `true` if the character is a tab space and `CTRL` is not pressed.
182    pub fn is_tab(&self) -> bool {
183        !self.modifiers.contains(ModifiersState::CTRL) && self.text.chars().any(|c| "\t\u{B}\u{1F}".contains(c))
184    }
185
186    /// Returns `true` if the character is a line-break and `CTRL` is not pressed.
187    pub fn is_line_break(&self) -> bool {
188        !self.modifiers.contains(ModifiersState::CTRL) && self.text.chars().any(|c| "\r\n\u{85}".contains(c))
189    }
190
191    /// Gets the characters to insert in a typed text.
192    ///
193    /// Replaces all [`is_tab`] with `\t` and all [`is_line_break`] with `\n`.
194    /// Returns `""` if there is no text or it contains ASCII control characters or `CTRL` is pressed.
195    ///
196    /// [`is_tab`]: Self::is_tab
197    /// [`is_line_break`]: Self::is_line_break
198    pub fn insert_str(&self) -> &str {
199        if self.modifiers.contains(ModifiersState::CTRL) {
200            // ignore legacy ASCII control combinators like `ctrl+i` generated `\t`.
201            ""
202        } else if self.is_tab() {
203            "\t"
204        } else if self.is_line_break() {
205            "\n"
206        } else if self.text.chars().any(|c| c.is_ascii_control()) {
207            ""
208        } else {
209            &self.text
210        }
211    }
212}
213
214event! {
215    /// Key pressed, repeat pressed or released event.
216    pub static KEY_INPUT_EVENT: KeyInputArgs {
217        let _ = KEYBOARD_SV.read();
218    };
219
220    /// Modifiers key state changed event.
221    pub static MODIFIERS_CHANGED_EVENT: ModifiersChangedArgs {
222        let _ = KEYBOARD_SV.read();
223    };
224}
225
226fn hooks() {
227    RAW_KEY_INPUT_EVENT
228        .hook(|args| {
229            let focused = FOCUS.focused().get();
230            KEYBOARD_SV.write().key_input(args, focused);
231            true
232        })
233        .perm();
234
235    RAW_KEY_REPEAT_CONFIG_CHANGED_EVENT
236        .hook(|args| {
237            let mut kb = KEYBOARD_SV.write();
238            kb.repeat_config.set(args.config);
239            kb.last_key_down = None;
240            true
241        })
242        .perm();
243
244    RAW_ANIMATIONS_CONFIG_CHANGED_EVENT
245        .hook(|args| {
246            let kb = KEYBOARD_SV.read();
247            kb.caret_animation_config
248                .set((args.config.caret_blink_interval, args.config.caret_blink_timeout));
249            true
250        })
251        .perm();
252
253    RAW_WINDOW_FOCUS_EVENT
254        .hook(|args| {
255            if args.new_focus.is_none() {
256                let mut kb = KEYBOARD_SV.write();
257                kb.clear_modifiers();
258                kb.codes.set(vec![]);
259                kb.keys.set(vec![]);
260
261                kb.last_key_down = None;
262            }
263            true
264        })
265        .perm();
266
267    VIEW_PROCESS_INITED_EVENT
268        .hook(|args| {
269            if args.is_respawn {
270                let mut kb = KEYBOARD_SV.write();
271                kb.clear_modifiers();
272                kb.codes.set(vec![]);
273                kb.keys.set(vec![]);
274
275                kb.last_key_down = None;
276            }
277            true
278        })
279        .perm();
280}
281
282/// Keyboard service.
283pub struct KEYBOARD;
284impl KEYBOARD {
285    /// Returns a read-only variable that tracks the currently pressed modifier keys.
286    pub fn modifiers(&self) -> Var<ModifiersState> {
287        KEYBOARD_SV.read().modifiers.read_only()
288    }
289
290    /// Returns a read-only variable that tracks the [`KeyCode`] of the keys currently pressed.
291    pub fn codes(&self) -> Var<Vec<KeyCode>> {
292        KEYBOARD_SV.read().codes.read_only()
293    }
294
295    /// Returns a read-only variable that tracks the [`Key`] identifier of the keys currently pressed.
296    pub fn keys(&self) -> Var<Vec<Key>> {
297        KEYBOARD_SV.read().keys.read_only()
298    }
299
300    /// Returns a variable that defines key press repeat start delay and repeat speed on the app.
301    ///
302    /// This delay is roughly the time the user must hold a key pressed to start repeating. When a second key press
303    /// happens without any other keyboard event and within twice this value it increments the [`repeat_count`] by the service.
304    ///
305    /// The value is the same as [`sys_repeat_config`], if set the variable disconnects from system config.
306    ///
307    /// [`sys_repeat_config`]: KEYBOARD::sys_repeat_config
308    /// [`repeat_count`]: KeyInputArgs::repeat_count
309    pub fn repeat_config(&self) -> Var<KeyRepeatConfig> {
310        KEYBOARD_SV.read().repeat_config.clone()
311    }
312
313    /// Returns a read-only variable that tracks the operating system key press repeat start delay and repeat speed.
314    ///
315    /// The variable updates every time the system config changes and on view-process (re)init.
316    pub fn sys_repeat_config(&self) -> Var<KeyRepeatConfig> {
317        KEYBOARD_SV.read().sys_repeat_config.read_only()
318    }
319
320    /// Returns a variable that defines the system config for the caret blink speed and timeout for the app.
321    ///
322    /// The first value defines the blink speed interval, the caret is visible for the duration, then not visible for the duration. The
323    /// second value defines the animation total duration, the caret stops animating and sticks to visible after this timeout is reached.
324    ///
325    /// You can use the [`caret_animation`] method to generate a new animation.
326    ///
327    /// The value is the same as [`sys_repeat_config`], if set the variable disconnects from system config.
328    ///
329    /// [`caret_animation`]: Self::caret_animation
330    /// [`sys_repeat_config`]: Self::sys_repeat_config
331    pub fn caret_animation_config(&self) -> Var<(Duration, Duration)> {
332        KEYBOARD_SV.read().caret_animation_config.clone()
333    }
334
335    /// Returns a read-only variable that tracks the operating system caret blink speed and timeout.
336    ///
337    /// The variable updates every time the system config changes and on view-process (re)init.
338    pub fn sys_caret_animation_config(&self) -> Var<(Duration, Duration)> {
339        KEYBOARD_SV.read().sys_caret_animation_config.read_only()
340    }
341
342    /// Returns a new read-only variable that animates the caret opacity.
343    ///
344    /// A new animation must be started after each key press. The value is always 1 or 0, no easing is used by default,
345    /// it can be added using the [`Var::easing`] method.
346    ///
347    /// [`Var::easing`]: zng_var::Var::easing
348    pub fn caret_animation(&self) -> Var<Factor> {
349        KEYBOARD_SV.read().caret_animation()
350    }
351}
352
353app_local! {
354    static KEYBOARD_SV: KeyboardService = {
355        hooks();
356        let sys_repeat_config = var_default();
357        let cfg = AnimationsConfig::default();
358        let sys_caret_animation_config = var((cfg.caret_blink_interval, cfg.caret_blink_timeout));
359        KeyboardService {
360            current_modifiers: HashSet::default(),
361            modifiers: var(ModifiersState::empty()),
362            codes: var(vec![]),
363            keys: var(vec![]),
364            repeat_config: sys_repeat_config.cow(),
365            sys_repeat_config,
366            caret_animation_config: sys_caret_animation_config.cow(),
367            sys_caret_animation_config,
368            last_key_down: None,
369        }
370    };
371}
372
373struct KeyboardService {
374    current_modifiers: HashSet<Key>,
375
376    modifiers: Var<ModifiersState>,
377    codes: Var<Vec<KeyCode>>,
378    keys: Var<Vec<Key>>,
379    repeat_config: Var<KeyRepeatConfig>,
380    sys_repeat_config: Var<KeyRepeatConfig>,
381    caret_animation_config: Var<(Duration, Duration)>,
382    sys_caret_animation_config: Var<(Duration, Duration)>,
383
384    last_key_down: Option<(InputDeviceId, KeyCode, DInstant, u32)>,
385}
386impl KeyboardService {
387    fn key_input(&mut self, args: &RawKeyInputArgs, focused: Option<InteractionPath>) {
388        let mut repeat = 0;
389
390        // update state and vars
391        match args.state {
392            KeyState::Pressed => {
393                if let Some((d_id, code, time, count)) = &mut self.last_key_down {
394                    let max_t = self.repeat_config.get().start_delay * 2;
395                    if args.key_code == *code && args.device_id == *d_id && (args.timestamp - *time) < max_t {
396                        *count = (*count).saturating_add(1);
397                        repeat = *count;
398                    } else {
399                        *d_id = args.device_id;
400                        *code = args.key_code;
401                        *count = 0;
402                    }
403                    *time = args.timestamp;
404                } else {
405                    self.last_key_down = Some((args.device_id, args.key_code, args.timestamp, 0));
406                }
407
408                let key_code = args.key_code;
409                self.codes.modify(move |cs| {
410                    if !cs.contains(&key_code) {
411                        cs.push(key_code);
412                    }
413                });
414
415                let key = &args.key;
416                if !matches!(&key, Key::Unidentified) {
417                    self.keys.modify(clmv!(key, |ks| {
418                        if ks.contains(&key) {
419                            ks.push(key);
420                        }
421                    }));
422
423                    if key.is_modifier() {
424                        self.set_modifiers(key.clone(), true);
425                    }
426                }
427            }
428            KeyState::Released => {
429                self.last_key_down = None;
430
431                let key = args.key_code;
432                self.codes.modify(move |cs| {
433                    if let Some(i) = cs.iter().position(|c| *c == key) {
434                        cs.swap_remove(i);
435                    }
436                });
437
438                let key = &args.key;
439                if !matches!(&key, Key::Unidentified) {
440                    self.keys.modify(clmv!(key, |ks| {
441                        if let Some(i) = ks.iter().position(|k| k == &key) {
442                            ks.swap_remove(i);
443                        }
444                    }));
445
446                    if key.is_modifier() {
447                        self.set_modifiers(key.clone(), false);
448                    }
449                }
450            }
451        }
452
453        // notify events
454        if let Some(target) = focused
455            && (target.window_id() == args.window_id
456                || WINDOWS
457                    .vars(target.window_id())
458                    .and_then(|v| if v.nest_parent().get().is_some() { v.parent().get() } else { None })
459                    == Some(args.window_id))
460        {
461            let args = KeyInputArgs::now(
462                target.window_id(),
463                args.device_id,
464                args.key_code,
465                args.key_location,
466                args.state,
467                args.key.clone(),
468                args.key_modified.clone(),
469                args.text.clone(),
470                self.current_modifiers(),
471                repeat,
472                target,
473            );
474            KEY_INPUT_EVENT.notify(args);
475        }
476    }
477    fn set_modifiers(&mut self, key: Key, pressed: bool) {
478        let prev_modifiers = self.current_modifiers();
479
480        if pressed {
481            self.current_modifiers.insert(key);
482        } else {
483            self.current_modifiers.remove(&key);
484        }
485
486        let new_modifiers = self.current_modifiers();
487
488        if prev_modifiers != new_modifiers {
489            self.modifiers.set(new_modifiers);
490            MODIFIERS_CHANGED_EVENT.notify(ModifiersChangedArgs::now(prev_modifiers, new_modifiers));
491        }
492    }
493
494    fn clear_modifiers(&mut self) {
495        let prev_modifiers = self.current_modifiers();
496        self.current_modifiers.clear();
497        let new_modifiers = self.current_modifiers();
498
499        if prev_modifiers != new_modifiers {
500            self.modifiers.set(new_modifiers);
501            MODIFIERS_CHANGED_EVENT.notify(ModifiersChangedArgs::now(prev_modifiers, new_modifiers));
502        }
503    }
504
505    fn current_modifiers(&self) -> ModifiersState {
506        let mut state = ModifiersState::empty();
507        for key in &self.current_modifiers {
508            state |= ModifiersState::from_key(key.clone());
509        }
510        state
511    }
512
513    fn caret_animation(&self) -> Var<Factor> {
514        let var = var(1.fct());
515        let cfg = self.caret_animation_config.clone();
516
517        let zero = 0.fct();
518        let one = 1.fct();
519        let mut init = true;
520
521        var.animate(move |anim, vm| {
522            let (interval, timeout) = cfg.get();
523            if anim.start_time().elapsed() >= timeout || interval == Duration::MAX {
524                if **vm != one {
525                    vm.set(one);
526                }
527                anim.stop();
528            } else {
529                if **vm == one {
530                    if !std::mem::take(&mut init) {
531                        vm.set(zero);
532                    }
533                } else {
534                    vm.set(one);
535                }
536                anim.sleep(interval, false);
537            }
538        })
539        .perm();
540
541        var.read_only()
542    }
543}
544
545/// Extension trait that adds keyboard simulation methods to [`HeadlessApp`].
546///
547/// [`HeadlessApp`]: zng_app::HeadlessApp
548pub trait HeadlessAppKeyboardExt {
549    /// Notifies keyboard input event.
550    ///
551    /// Note that the app is not updated so the event is pending after this call.
552    fn on_keyboard_input(&mut self, window_id: WindowId, code: KeyCode, location: KeyLocation, key: Key, state: KeyState);
553
554    /// Does a key-down, key-up and updates.
555    fn press_key(&mut self, window_id: WindowId, code: KeyCode, location: KeyLocation, key: Key);
556
557    /// Does a modifiers changed, key-down, key-up, reset modifiers and updates.
558    fn press_modified_key(&mut self, window_id: WindowId, modifiers: ModifiersState, code: KeyCode, location: KeyLocation, key: Key);
559}
560impl HeadlessAppKeyboardExt for HeadlessApp {
561    fn on_keyboard_input(&mut self, window_id: WindowId, code: KeyCode, location: KeyLocation, key: Key, state: KeyState) {
562        use zng_app::view_process::raw_events::*;
563
564        // init service if needed
565        let _ = KEYBOARD_SV.read();
566
567        let args = RawKeyInputArgs::now(
568            window_id,
569            InputDeviceId::virtual_keyboard(),
570            code,
571            location,
572            state,
573            key.clone(),
574            key,
575            "",
576        );
577        RAW_KEY_INPUT_EVENT.notify(args);
578    }
579
580    fn press_key(&mut self, window_id: WindowId, code: KeyCode, location: KeyLocation, key: Key) {
581        self.on_keyboard_input(window_id, code, location, key.clone(), KeyState::Pressed);
582        self.on_keyboard_input(window_id, code, location, key, KeyState::Released);
583        let _ = self.update(false);
584    }
585
586    fn press_modified_key(&mut self, window_id: WindowId, modifiers: ModifiersState, code: KeyCode, location: KeyLocation, key: Key) {
587        if modifiers.is_empty() {
588            self.press_key(window_id, code, location, key);
589        } else {
590            let modifiers = modifiers.keys();
591            for key in &modifiers {
592                self.on_keyboard_input(window_id, code, location, key.clone(), KeyState::Pressed);
593            }
594
595            // pressed the modifiers.
596            let _ = self.update(false);
597
598            self.on_keyboard_input(window_id, code, location, key.clone(), KeyState::Pressed);
599            self.on_keyboard_input(window_id, code, location, key.clone(), KeyState::Released);
600
601            // pressed the key.
602            let _ = self.update(false);
603
604            for key in modifiers {
605                self.on_keyboard_input(window_id, code, location, key, KeyState::Released);
606            }
607
608            // released the modifiers.
609            let _ = self.update(false);
610        }
611    }
612}