* Physical keyboard events @ 2024-10-28 23:15 Cecilio Pardo 2024-10-29 13:40 ` Eli Zaretskii ` (2 more replies) 0 siblings, 3 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-10-28 23:15 UTC (permalink / raw) To: emacs-devel Hello, I'm planning to implement physical key press/release events for emacs. I would add a new element to 'enum event_kind', that in turn would send a new input event. This input event will be bound in 'special-event-map' so that it will not modify the normal flow of keyboard input. Platform dependent code would send these events on key press and release. Then a lisp fuction can be bound to this on the special-event-map, to implement the detection of: - double/triple tap on Shift, Control, Alt, etc - Long presses on Shift, Control, Alt, etc. This actions could be bound to commands, or could add a modififer (Super, Hyper) to next commands. And make Tetris independent of the keyboard repeat rate :) Has something like this been discussed before, so I can check? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-28 23:15 Physical keyboard events Cecilio Pardo @ 2024-10-29 13:40 ` Eli Zaretskii 2024-10-29 15:07 ` Cecilio Pardo 2024-10-29 17:13 ` Alan Mackenzie 2024-11-03 23:44 ` Cecilio Pardo 2 siblings, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-10-29 13:40 UTC (permalink / raw) To: Cecilio Pardo; +Cc: emacs-devel > Date: Tue, 29 Oct 2024 00:15:22 +0100 > From: Cecilio Pardo <cpardo@imayhem.com> > > Hello, > > I'm planning to implement physical key press/release events for emacs. > I would add a new element to 'enum event_kind', that in turn would > send a new input event. This input event will be bound in > 'special-event-map' so that it will not modify the normal flow of > keyboard input. Platform dependent code would send these events > on key press and release. I hope these new events will not be sent at all times, only when some optional variable is set (similar to track-mouse, perhaps). I wouldn't want Emacs to start processing press/release events on Shift or Ctrl unless a Lisp program needs that, and I don't think we want to change our processing of keyboard such that instead of a single keypress with modifiers we need to process multiple key-press and key-release events when the user simply types on the keyboard. Physical keys also raise the issue of supporting input methods, keyboard layout switches, etc. > Then a lisp fuction can be bound to this on the special-event-map, to > implement the detection of: > > - double/triple tap on Shift, Control, Alt, etc > - Long presses on Shift, Control, Alt, etc. > > This actions could be bound to commands, or could add a modififer > (Super, Hyper) to next commands. > > And make Tetris independent of the keyboard repeat rate :) > > Has something like this been discussed before, so I can check? No, I don't think so, although we get feature requests for something like that from time to time. However, on what systems and which Emacs configurations will it be possible to provide such a feature? Thanks. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 13:40 ` Eli Zaretskii @ 2024-10-29 15:07 ` Cecilio Pardo 2024-10-29 15:38 ` Peter Feigl 2024-10-29 16:44 ` Eli Zaretskii 0 siblings, 2 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-10-29 15:07 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel On 29/10/2024 14:40, Eli Zaretskii wrote: >> I'm planning to implement physical key press/release events for emacs. >> I would add a new element to 'enum event_kind', that in turn would >> send a new input event. This input event will be bound in >> 'special-event-map' so that it will not modify the normal flow of >> keyboard input. Platform dependent code would send these events >> on key press and release. > > I hope these new events will not be sent at all times, only when some > optional variable is set (similar to track-mouse, perhaps). I > wouldn't want Emacs to start processing press/release events on Shift > or Ctrl unless a Lisp program needs that, and I don't think we want to > change our processing of keyboard such that instead of a single > keypress with modifiers we need to process multiple key-press and > key-release events when the user simply types on the keyboard. Thats why the events will be bound in special-event-map. Nobody will see them, except for the code that handles them. We can of course completely disable them with a variable. > Physical keys also raise the issue of supporting input methods, > keyboard layout switches, etc. I will define a list of keys: LeftShit, RightShift, LeftControl, etc. The platform dependent code will decide which one was pressed. As events will be invisible, I don't think we will interfere with input methods. > However, on what systems and which Emacs configurations will it be > possible to provide such a feature? I think all GUI systems can use this. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 15:07 ` Cecilio Pardo @ 2024-10-29 15:38 ` Peter Feigl 2024-10-29 17:54 ` Cecilio Pardo 2024-10-29 23:41 ` James Thomas 2024-10-29 16:44 ` Eli Zaretskii 1 sibling, 2 replies; 93+ messages in thread From: Peter Feigl @ 2024-10-29 15:38 UTC (permalink / raw) To: Cecilio Pardo, Eli Zaretskii; +Cc: emacs-devel > I will define a list of keys: LeftShit, RightShift, LeftControl, etc. > The platform dependent code will decide which one was pressed. As events > will be invisible, I don't think we will interfere with input methods. I caution you to think about this very well, there are *very* many non-standard keyboards or remapped keyboards out there, where users expect everything to work. In general, it is best to leave the mapping of physical keys to intended keypresses to whatever the operating system provides. In the specific case of Xorg, please only look at the keysyms, do not try to do anything magical with the scancodes, this will only result in a broken implementation. I understand the wish to have this, but it is a way more complex topic than it looks like from a first glance. I have had to deal with way too many bad implementations of this than wanting to deal with it in Emacs as well :-/ Sorry for the criticism, in case you have already thought about all of this and solved it, I'd really be interested in hearing more about it! Greetings, Peter ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 15:38 ` Peter Feigl @ 2024-10-29 17:54 ` Cecilio Pardo 2024-10-29 23:41 ` James Thomas 1 sibling, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-10-29 17:54 UTC (permalink / raw) To: Peter Feigl, Eli Zaretskii; +Cc: emacs-devel On 29/10/2024 16:38, Peter Feigl wrote: >> I will define a list of keys: LeftShit, RightShift, LeftControl, etc. >> The platform dependent code will decide which one was pressed. As events >> will be invisible, I don't think we will interfere with input methods. > > I caution you to think about this very well, there are *very* many > non-standard keyboards or remapped keyboards out there, where users > expect everything to work. > > In general, it is best to leave the mapping of physical keys to intended > keypresses to whatever the operating system provides. In the specific > case of Xorg, please only look at the keysyms, do not try to do anything > magical with the scancodes, this will only result in a broken > implementation. > > I understand the wish to have this, but it is a way more complex topic > than it looks like from a first glance. > > I have had to deal with way too many bad implementations of this than > wanting to deal with it in Emacs as well :-/ > > Sorry for the criticism, in case you have already thought about all of > this and solved it, I'd really be interested in hearing more about it! Thanks for the insight. I will keep it simple and be conservative. And ready to throw it all away if the result asks for it. :) ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 15:38 ` Peter Feigl 2024-10-29 17:54 ` Cecilio Pardo @ 2024-10-29 23:41 ` James Thomas 1 sibling, 0 replies; 93+ messages in thread From: James Thomas @ 2024-10-29 23:41 UTC (permalink / raw) To: Cecilio Pardo; +Cc: Peter Feigl, emacs-devel Peter Feigl wrote: >> I will define a list of keys: LeftShit, RightShift, LeftControl, etc. >> The platform dependent code will decide which one was pressed. As events >> will be invisible, I don't think we will interfere with input methods. > > I caution you to think about this very well, there are *very* many > non-standard keyboards or remapped keyboards out there, where users > expect everything to work. > > In general, it is best to leave the mapping of physical keys to intended > keypresses to whatever the operating system provides. In the specific > case of Xorg, please only look at the keysyms, do not try to do anything > magical with the scancodes, this will only result in a broken > implementation. > > I understand the wish to have this, but it is a way more complex topic > than it looks like from a first glance. > > I have had to deal with way too many bad implementations of this than > wanting to deal with it in Emacs as well :-/ > > Sorry for the criticism, in case you have already thought about all of > this and solved it, I'd really be interested in hearing more about it! I feel the same way. On that note, isn't it possible to achieve the same thing in Xorg simply by sending fake key or fake locked modifier events? IIRC, just using xkbcomp. -- ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 15:07 ` Cecilio Pardo 2024-10-29 15:38 ` Peter Feigl @ 2024-10-29 16:44 ` Eli Zaretskii 2024-10-29 16:55 ` Yuri Khan 2024-10-29 17:52 ` Cecilio Pardo 1 sibling, 2 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-10-29 16:44 UTC (permalink / raw) To: Cecilio Pardo; +Cc: emacs-devel > Date: Tue, 29 Oct 2024 16:07:16 +0100 > Cc: emacs-devel@gnu.org > From: Cecilio Pardo <cpardo@imayhem.com> > > On 29/10/2024 14:40, Eli Zaretskii wrote: > > >> I'm planning to implement physical key press/release events for emacs. > >> I would add a new element to 'enum event_kind', that in turn would > >> send a new input event. This input event will be bound in > >> 'special-event-map' so that it will not modify the normal flow of > >> keyboard input. Platform dependent code would send these events > >> on key press and release. > > > > I hope these new events will not be sent at all times, only when some > > optional variable is set (similar to track-mouse, perhaps). I > > wouldn't want Emacs to start processing press/release events on Shift > > or Ctrl unless a Lisp program needs that, and I don't think we want to > > change our processing of keyboard such that instead of a single > > keypress with modifiers we need to process multiple key-press and > > key-release events when the user simply types on the keyboard. > > Thats why the events will be bound in special-event-map. Nobody will see > them, except for the code that handles them. We can of course > completely disable them with a variable. Even if no one sees them, bombarding the Emacs event queue with useless events will have its effect: Emacs will become more sluggish, some while-no-input loops will unexpectedly exit just because the user happened to touch the Shift key for some reason, etc. > > Physical keys also raise the issue of supporting input methods, > > keyboard layout switches, etc. > > I will define a list of keys: LeftShit, RightShift, LeftControl, etc. > The platform dependent code will decide which one was pressed. As events > will be invisible, I don't think we will interfere with input methods. I meant the non-modifier keys. When some OS feature redefines a key, low-level events will not know that, and will still consider the key labeled 'a' as 'a', even though I might have switched the keyboard to another language, where the key labeled 'a' actually produces a completely different character, say, 'ש'. > > However, on what systems and which Emacs configurations will it be > > possible to provide such a feature? > > I think all GUI systems can use this. So X (with or without any toolkit we support), PGTK, MS-Windows, and macOS -- all those let applications access low-level key events? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 16:44 ` Eli Zaretskii @ 2024-10-29 16:55 ` Yuri Khan 2024-10-29 17:46 ` Eli Zaretskii 2024-10-29 17:56 ` Cecilio Pardo 2024-10-29 17:52 ` Cecilio Pardo 1 sibling, 2 replies; 93+ messages in thread From: Yuri Khan @ 2024-10-29 16:55 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Cecilio Pardo, emacs-devel On Tue, 29 Oct 2024 at 23:46, Eli Zaretskii <eliz@gnu.org> wrote: > I meant the non-modifier keys. When some OS feature redefines a key, > low-level events will not know that, and will still consider the key > labeled 'a' as 'a', even though I might have switched the keyboard to > another language, where the key labeled 'a' actually produces a > completely different character, say, 'ש'. From where I stand, that would be a feature, in some carefully chosen cases. When I’m switched to a layout where the ‘a’ key produces an ‘ф’ character, or an ‘α’, or a ち, I’d still want Ctrl+this key to behave as C-a. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 16:55 ` Yuri Khan @ 2024-10-29 17:46 ` Eli Zaretskii 2024-10-30 2:56 ` Max Nikulin 2024-10-29 17:56 ` Cecilio Pardo 1 sibling, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-10-29 17:46 UTC (permalink / raw) To: Yuri Khan; +Cc: cpardo, emacs-devel > From: Yuri Khan <yuri.v.khan@gmail.com> > Date: Tue, 29 Oct 2024 23:55:49 +0700 > Cc: Cecilio Pardo <cpardo@imayhem.com>, emacs-devel@gnu.org > > On Tue, 29 Oct 2024 at 23:46, Eli Zaretskii <eliz@gnu.org> wrote: > > > I meant the non-modifier keys. When some OS feature redefines a key, > > low-level events will not know that, and will still consider the key > > labeled 'a' as 'a', even though I might have switched the keyboard to > > another language, where the key labeled 'a' actually produces a > > completely different character, say, 'ש'. > > >From where I stand, that would be a feature, in some carefully chosen > cases. When I’m switched to a layout where the ‘a’ key produces an ‘ф’ > character, or an ‘α’, or a ち, I’d still want Ctrl+this key to behave > as C-a. Yes, but you want that _only_ for keys with modifiers. Which is not what will happen. Some feature... ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 17:46 ` Eli Zaretskii @ 2024-10-30 2:56 ` Max Nikulin 2024-10-30 6:28 ` Yuri Khan 2024-10-30 15:21 ` Eli Zaretskii 0 siblings, 2 replies; 93+ messages in thread From: Max Nikulin @ 2024-10-30 2:56 UTC (permalink / raw) To: emacs-devel On 30/10/2024 00:46, Eli Zaretskii wrote: >> From: Yuri Khan Date: Tue, 29 Oct 2024 23:55:49 +0700 >> When I’m switched to a layout where the ‘a’ key produces an ‘ф’ >> character, or an ‘α’, or a ち, I’d still want Ctrl+this key to behave >> as C-a. > > Yes, but you want that _only_ for keys with modifiers. Which is not > what will happen. Some feature... Not *for key with modifiers*, but during lookup in keymaps. There are enough keybindings that include keys without modifiers. It is a recurring confusion that appears in every discussion of related bugs and in mailing list threads, e.g. <https://debbugs.gnu.org/43830> "keyboard layout handling incompatible with rest of the OS" Perhaps it is impossible to achieve in terminal frames. Modifier-only gestures sounds like a different feature though. Cecilio, in the case of severe resistance you may consider another approach: sniff keyboard events at lower level (xinput2? IBus plugin?) by a dedicated daemon and emit D-Bus events that may be interpreted by Emacs (and other applications). The bonus is support of Emacs frames inside terminal applications. Isolation of applications in Wayland may be a trouble, but perhaps some compositors support configurable gestures out of the box. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 2:56 ` Max Nikulin @ 2024-10-30 6:28 ` Yuri Khan 2024-10-30 6:39 ` Peter Feigl 2024-10-30 15:27 ` Eli Zaretskii 2024-10-30 15:21 ` Eli Zaretskii 1 sibling, 2 replies; 93+ messages in thread From: Yuri Khan @ 2024-10-30 6:28 UTC (permalink / raw) To: Max Nikulin; +Cc: emacs-devel On Wed, 30 Oct 2024 at 10:22, Max Nikulin <manikulin@gmail.com> wrote: > Not *for key with modifiers*, but during lookup in keymaps. There are > enough keybindings that include keys without modifiers. I’m going to posit the hypothesis that the *only* command for which it makes sense to use the keysym of the current layout is ‘self-insert-command’. Everything else wants the keysym of “the” Latin-based layout if there is one and only one; the keycode if there is none; and some heuristic tiebreaker for the case where the user has more than one Latin-based layout configured. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 6:28 ` Yuri Khan @ 2024-10-30 6:39 ` Peter Feigl 2024-10-30 15:27 ` Eli Zaretskii 1 sibling, 0 replies; 93+ messages in thread From: Peter Feigl @ 2024-10-30 6:39 UTC (permalink / raw) To: Yuri Khan, Max Nikulin; +Cc: emacs-devel > Everything else wants the keysym of “the” Latin-based layout if there > is one and only one; the keycode if there is none; and some heuristic > tiebreaker for the case where the user has more than one Latin-based > layout configured. I have a keyboard that has dvorak-like layout and emits these keycodes *physically*. There are many other variations of this around. The keycode tells you *nothing* about the physical location of a key, so if you were to use it for – as an example – using WASD for moving a character around the screen, that would be entirely useless for these keyboards, because WASD are *not* in the positions you assume them to be. Of course you could argue that that's just a few percent of the users, so we shouldn't care about them, they'll cope in some other way. I've seen way too many applications (e.g. many games) try to guess the active keyboard layout, and invariably fail on anything non-standard. There's a reason why Xorg allows to remap everything, it would be a shame to not support all this flexibility. Greetings, Peter ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 6:28 ` Yuri Khan 2024-10-30 6:39 ` Peter Feigl @ 2024-10-30 15:27 ` Eli Zaretskii 2024-10-30 17:13 ` Yuri Khan 1 sibling, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-10-30 15:27 UTC (permalink / raw) To: Yuri Khan; +Cc: manikulin, emacs-devel > From: Yuri Khan <yuri.v.khan@gmail.com> > Date: Wed, 30 Oct 2024 13:28:20 +0700 > Cc: emacs-devel@gnu.org > > On Wed, 30 Oct 2024 at 10:22, Max Nikulin <manikulin@gmail.com> wrote: > > > Not *for key with modifiers*, but during lookup in keymaps. There are > > enough keybindings that include keys without modifiers. > > I’m going to posit the hypothesis that the *only* command for which it > makes sense to use the keysym of the current layout is > ‘self-insert-command’. > > Everything else wants the keysym of “the” Latin-based layout if there > is one and only one; the keycode if there is none; and some heuristic > tiebreaker for the case where the user has more than one Latin-based > layout configured. How do you suggest to arrange for a low-level keyboard interface to know which kind of Emacs command attempted to read the keyboard? Moreover, what about type-ahead? When Emacs is busy, the user can type quite a lot of commands, and the keyboard processing cannot know at that point which Emacs commands will actually read those queued keys. IOW, I don't understand how to use your hypothesis, even if it is true, in practice. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 15:27 ` Eli Zaretskii @ 2024-10-30 17:13 ` Yuri Khan 2024-10-30 17:37 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Yuri Khan @ 2024-10-30 17:13 UTC (permalink / raw) To: Eli Zaretskii; +Cc: manikulin, emacs-devel On Wed, 30 Oct 2024 at 22:27, Eli Zaretskii <eliz@gnu.org> wrote: > > From: Yuri Khan <yuri.v.khan@gmail.com> > > Date: Wed, 30 Oct 2024 13:28:20 +0700 > > Cc: emacs-devel@gnu.org > > I’m going to posit the hypothesis that the *only* command for which it > > makes sense to use the keysym of the current layout is > > ‘self-insert-command’. > > > > Everything else wants the keysym of “the” Latin-based layout if there > > is one and only one; the keycode if there is none; and some heuristic > > tiebreaker for the case where the user has more than one Latin-based > > layout configured. > > How do you suggest to arrange for a low-level keyboard interface to > know which kind of Emacs command attempted to read the keyboard? Since it’s not clairvoyant, it would need to save enough information for the keymap lookup code downstream. Specifically (XKB/X11 case): state of modifiers, keycode, keysym according to the currently active group, and keysym according to the appropriate Latin-based group. Then, at the time of keymap lookup: * Look up the active modifiers + active group keysym. * If this yields ‘self-insert-command’ or another command specifically whitelisted, return that. * Look up the active modifiers + keysym from the Latin-based group. Return that. Test case: If a mode keymap binds both ‘/’ and ‘.’, and a key produces ‘/’ in the Latin layout and ‘.’ in Cyrillic layout, then the ‘/’ command shall be executed regardless of whether Latin or Cyrillic layout is active. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 17:13 ` Yuri Khan @ 2024-10-30 17:37 ` Eli Zaretskii 2024-10-30 19:26 ` Dov Grobgeld 2024-10-31 6:13 ` Yuri Khan 0 siblings, 2 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-10-30 17:37 UTC (permalink / raw) To: Yuri Khan; +Cc: manikulin, emacs-devel > From: Yuri Khan <yuri.v.khan@gmail.com> > Date: Thu, 31 Oct 2024 00:13:31 +0700 > Cc: manikulin@gmail.com, emacs-devel@gnu.org > > On Wed, 30 Oct 2024 at 22:27, Eli Zaretskii <eliz@gnu.org> wrote: > > > > From: Yuri Khan <yuri.v.khan@gmail.com> > > > Date: Wed, 30 Oct 2024 13:28:20 +0700 > > > Cc: emacs-devel@gnu.org > > > > I’m going to posit the hypothesis that the *only* command for which it > > > makes sense to use the keysym of the current layout is > > > ‘self-insert-command’. > > > > > > Everything else wants the keysym of “the” Latin-based layout if there > > > is one and only one; the keycode if there is none; and some heuristic > > > tiebreaker for the case where the user has more than one Latin-based > > > layout configured. > > > > How do you suggest to arrange for a low-level keyboard interface to > > know which kind of Emacs command attempted to read the keyboard? > > Since it’s not clairvoyant, it would need to save enough information > for the keymap lookup code downstream. Specifically (XKB/X11 case): > state of modifiers, keycode, keysym according to the currently active > group, and keysym according to the appropriate Latin-based group. > > Then, at the time of keymap lookup: > > * Look up the active modifiers + active group keysym. > * If this yields ‘self-insert-command’ or another command > specifically whitelisted, return that. > * Look up the active modifiers + keysym from the Latin-based group. Return that. How do you know which keymap to look up and for which character, before you decide which character to produce? > Test case: If a mode keymap binds both ‘/’ and ‘.’, and a key produces > ‘/’ in the Latin layout and ‘.’ in Cyrillic layout, then the ‘/’ > command shall be executed regardless of whether Latin or Cyrillic > layout is active. What if '/' is bound to self-insert-command, but '.' to something else: what do you return then? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 17:37 ` Eli Zaretskii @ 2024-10-30 19:26 ` Dov Grobgeld 2024-10-30 19:36 ` Juri Linkov 2024-10-30 19:55 ` Eli Zaretskii 2024-10-31 6:13 ` Yuri Khan 1 sibling, 2 replies; 93+ messages in thread From: Dov Grobgeld @ 2024-10-30 19:26 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Yuri Khan, manikulin, emacs-devel Switching to a different keyboard language causes all navigation keyboard shortcuts to fail by default. This is why I found myself adding the following to my .emacs file. I agree it would be nice if there was some automatic way of creating this mapping, but using keycodes, does not seem to be a correct way to do it. ;; some motion bindings in Hebrew mode that reflect key ;; positions for Dvorak. (global-set-key [(control ?ש)] 'move-beginning-of-line) (global-set-key [(control ?ג)] 'move-end-of-line) (global-set-key [(control ?ר)] 'previous-line) (global-set-key [(control ?ך)] 'next-line) (global-set-key [(control ?ה)] 'kill-line) (global-set-key [(control ?ף)] 'isearch-forward) (global-set-key [(control ?ם)] 'isearch-backward) (global-set-key [(control ?ץ)] 'scroll-up-command) (global-set-key [(meta ?ץ)] 'scroll-down-command) (global-set-key [(meta ?ט)] 'forward-word) (global-set-key [(control ?ט)] 'forward-char) (global-set-key [(meta ?מ)] 'backward-word) (global-set-key [(control ?מ)] 'backward-char) (define-key isearch-mode-map [(control ?ף)] 'isearch-repeat-forward) (define-key isearch-mode-map [(control ?ם)] 'isearch-repeat-backward) (global-set-key [(control ?נ) (control ?ף)] 'save-buffer) On Wed, Oct 30, 2024 at 7:38 PM Eli Zaretskii <eliz@gnu.org> wrote: > > > From: Yuri Khan <yuri.v.khan@gmail.com> > > Date: Thu, 31 Oct 2024 00:13:31 +0700 > > Cc: manikulin@gmail.com, emacs-devel@gnu.org > > > > On Wed, 30 Oct 2024 at 22:27, Eli Zaretskii <eliz@gnu.org> wrote: > > > > > > From: Yuri Khan <yuri.v.khan@gmail.com> > > > > Date: Wed, 30 Oct 2024 13:28:20 +0700 > > > > Cc: emacs-devel@gnu.org > > > > > > I’m going to posit the hypothesis that the *only* command for which it > > > > makes sense to use the keysym of the current layout is > > > > ‘self-insert-command’. > > > > > > > > Everything else wants the keysym of “the” Latin-based layout if there > > > > is one and only one; the keycode if there is none; and some heuristic > > > > tiebreaker for the case where the user has more than one Latin-based > > > > layout configured. > > > > > > How do you suggest to arrange for a low-level keyboard interface to > > > know which kind of Emacs command attempted to read the keyboard? > > > > Since it’s not clairvoyant, it would need to save enough information > > for the keymap lookup code downstream. Specifically (XKB/X11 case): > > state of modifiers, keycode, keysym according to the currently active > > group, and keysym according to the appropriate Latin-based group. > > > > Then, at the time of keymap lookup: > > > > * Look up the active modifiers + active group keysym. > > * If this yields ‘self-insert-command’ or another command > > specifically whitelisted, return that. > > * Look up the active modifiers + keysym from the Latin-based group. Return that. > > How do you know which keymap to look up and for which character, > before you decide which character to produce? > > > Test case: If a mode keymap binds both ‘/’ and ‘.’, and a key produces > > ‘/’ in the Latin layout and ‘.’ in Cyrillic layout, then the ‘/’ > > command shall be executed regardless of whether Latin or Cyrillic > > layout is active. > > What if '/' is bound to self-insert-command, but '.' to something > else: what do you return then? > ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 19:26 ` Dov Grobgeld @ 2024-10-30 19:36 ` Juri Linkov 2024-10-30 19:55 ` Eli Zaretskii 1 sibling, 0 replies; 93+ messages in thread From: Juri Linkov @ 2024-10-30 19:36 UTC (permalink / raw) To: Dov Grobgeld; +Cc: Eli Zaretskii, Yuri Khan, manikulin, emacs-devel > Switching to a different keyboard language causes all navigation > keyboard shortcuts to fail by default. This is why I found myself > adding the following to my .emacs file. I agree it would be nice if > there was some automatic way of creating this mapping, but using > keycodes, does not seem to be a correct way to do it. > > ;; some motion bindings in Hebrew mode that reflect key > ;; positions for Dvorak. > (global-set-key [(control ?ש)] 'move-beginning-of-line) > (global-set-key [(control ?ג)] 'move-end-of-line) > (global-set-key [(control ?ר)] 'previous-line) > (global-set-key [(control ?ך)] 'next-line) > (global-set-key [(control ?ה)] 'kill-line) > (global-set-key [(control ?ף)] 'isearch-forward) > (global-set-key [(control ?ם)] 'isearch-backward) > (global-set-key [(control ?ץ)] 'scroll-up-command) > (global-set-key [(meta ?ץ)] 'scroll-down-command) > (global-set-key [(meta ?ט)] 'forward-word) > (global-set-key [(control ?ט)] 'forward-char) > (global-set-key [(meta ?מ)] 'backward-word) > (global-set-key [(control ?מ)] 'backward-char) > (define-key isearch-mode-map [(control ?ף)] 'isearch-repeat-forward) > (define-key isearch-mode-map [(control ?ם)] 'isearch-repeat-backward) > (global-set-key [(control ?נ) (control ?ף)] 'save-buffer) There is the automatic way of creating such mapping by using the package https://github.com/a13/reverse-im.el Then you can customize it to any input method, e.g.: (reverse-im-input-methods '("hebrew")) ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 19:26 ` Dov Grobgeld 2024-10-30 19:36 ` Juri Linkov @ 2024-10-30 19:55 ` Eli Zaretskii 1 sibling, 0 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-10-30 19:55 UTC (permalink / raw) To: Dov Grobgeld; +Cc: yuri.v.khan, manikulin, emacs-devel > From: Dov Grobgeld <dov.grobgeld@gmail.com> > Date: Wed, 30 Oct 2024 21:26:21 +0200 > Cc: Yuri Khan <yuri.v.khan@gmail.com>, manikulin@gmail.com, emacs-devel@gnu.org > > Switching to a different keyboard language causes all navigation > keyboard shortcuts to fail by default. That's window-system specific, I think. It doesn't happen on MS-Windows, for example. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 17:37 ` Eli Zaretskii 2024-10-30 19:26 ` Dov Grobgeld @ 2024-10-31 6:13 ` Yuri Khan 1 sibling, 0 replies; 93+ messages in thread From: Yuri Khan @ 2024-10-31 6:13 UTC (permalink / raw) To: Eli Zaretskii; +Cc: manikulin, emacs-devel On Thu, 31 Oct 2024 at 00:37, Eli Zaretskii <eliz@gnu.org> wrote: > > * Look up the active modifiers + active group keysym. > > * If this yields ‘self-insert-command’ or another command > > specifically whitelisted, return that. > > * Look up the active modifiers + keysym from the Latin-based group. Return that. > > How do you know which keymap to look up and for which character, > before you decide which character to produce? What seems to be the difficulty with the description above? The first lookup happens the same way as it currently does. For the second lookup, pretend the user remembered to switch to their preferred Latin-based layout before hitting the keys. > > Test case: If a mode keymap binds both ‘/’ and ‘.’, and a key produces > > ‘/’ in the Latin layout and ‘.’ in Cyrillic layout, then the ‘/’ > > command shall be executed regardless of whether Latin or Cyrillic > > layout is active. > > What if '/' is bound to self-insert-command, but '.' to something > else: what do you return then? What kind of mode would allow self-insertion for some characters but use others for commands? Like maybe Calc, where ‘.’ is the decimal point but ‘/’ is the division command? In this case I think using ‘.’ for entering the decimal point wins, and the division command has to be invoked by either the kp-divide key or the {/ \} key. That is, calc-divide has to be whitelisted as invocable with the national layout. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 2:56 ` Max Nikulin 2024-10-30 6:28 ` Yuri Khan @ 2024-10-30 15:21 ` Eli Zaretskii 2024-10-30 16:59 ` Max Nikulin 1 sibling, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-10-30 15:21 UTC (permalink / raw) To: Max Nikulin; +Cc: emacs-devel > From: Max Nikulin <manikulin@gmail.com> > Date: Wed, 30 Oct 2024 09:56:30 +0700 > > On 30/10/2024 00:46, Eli Zaretskii wrote: > >> From: Yuri Khan Date: Tue, 29 Oct 2024 23:55:49 +0700 > >> When I’m switched to a layout where the ‘a’ key produces an ‘ф’ > >> character, or an ‘α’, or a ち, I’d still want Ctrl+this key to behave > >> as C-a. > > > > Yes, but you want that _only_ for keys with modifiers. Which is not > > what will happen. Some feature... > > Not *for key with modifiers*, but during lookup in keymaps. What Emacs key is NOT looked up in a keymap? They all are. So I don't understand what you are saying here. > There are > enough keybindings that include keys without modifiers. It is a > recurring confusion that appears in every discussion of related bugs and > in mailing list threads, e.g. > <https://debbugs.gnu.org/43830> > "keyboard layout handling incompatible with rest of the OS" That's exactly why I said "with modifiers": that bug report is about C-s and the likes. By contrast, when the keyboard language is switched, you do NOT want the s/ы key to generate s instead of ы. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 15:21 ` Eli Zaretskii @ 2024-10-30 16:59 ` Max Nikulin 0 siblings, 0 replies; 93+ messages in thread From: Max Nikulin @ 2024-10-30 16:59 UTC (permalink / raw) To: emacs-devel On 30/10/2024 22:21, Eli Zaretskii wrote: >> From: Max Nikulin Date: Wed, 30 Oct 2024 09:56:30 +0700 >> On 30/10/2024 00:46, Eli Zaretskii wrote: >>>> From: Yuri Khan Date: Tue, 29 Oct 2024 23:55:49 +0700 >>>> When I’m switched to a layout where the ‘a’ key produces an ‘ф’ >>>> character, or an ‘α’, or a ち, I’d still want Ctrl+this key to behave >>>> as C-a. >>> >>> Yes, but you want that _only_ for keys with modifiers. Which is not >>> what will happen. Some feature... >> >> Not *for key with modifiers*, but during lookup in keymaps. [...] >> <https://debbugs.gnu.org/43830> > > That's exactly why I said "with modifiers": that bug report is about > C-s and the likes. By contrast, when the keyboard language is > switched, you do NOT want the s/ы key to generate s instead of ы. Consider "C-s" `isearch-forward' followed by "M-s i" `isearch-toggle-invisible': "C-ы M-ы ш" with bare "ш" should work as "C-s M-s i" but "ш" bound to `self-insert-command' should insert "ш". Taking into account which way some applications are localized, perhaps there are users who rarely switched to a Latin (e.g. US) keyboard layout. E.g. spreadsheets may have translated function names, not to mention accelerator keys. There should be a way to specify whether symbol is from the base (Latin) keyboard layout or from localized one. E.g. "," and some other punctuation characters are jumping. Perhaps it may be achieved by additional virtual modifier: "L-," should be matched against localized layout. However user may have multiple non-Latin layouts configured and GNOME reconfigures XKB on each layout switch to 2 groups: Latin and current one. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 16:55 ` Yuri Khan 2024-10-29 17:46 ` Eli Zaretskii @ 2024-10-29 17:56 ` Cecilio Pardo 1 sibling, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-10-29 17:56 UTC (permalink / raw) To: Yuri Khan, Eli Zaretskii; +Cc: emacs-devel On 29/10/2024 17:55, Yuri Khan wrote: >> I meant the non-modifier keys. When some OS feature redefines a key, >> low-level events will not know that, and will still consider the key >> labeled 'a' as 'a', even though I might have switched the keyboard to >> another language, where the key labeled 'a' actually produces a >> completely different character, say, 'ש'. > > From where I stand, that would be a feature, in some carefully chosen > cases. When I’m switched to a layout where the ‘a’ key produces an ‘ф’ > character, or an ‘α’, or a ち, I’d still want Ctrl+this key to behave > as C-a. Probably we can implement this. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 16:44 ` Eli Zaretskii 2024-10-29 16:55 ` Yuri Khan @ 2024-10-29 17:52 ` Cecilio Pardo 1 sibling, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-10-29 17:52 UTC (permalink / raw) To: Eli Zaretskii; +Cc: emacs-devel On 29/10/2024 17:44, Eli Zaretskii wrote: >> Thats why the events will be bound in special-event-map. Nobody will see >> them, except for the code that handles them. We can of course >> completely disable them with a variable. > > Even if no one sees them, bombarding the Emacs event queue with > useless events will have its effect: Emacs will become more sluggish, > some while-no-input loops will unexpectedly exit just because the user > happened to touch the Shift key for some reason, etc. This should then be always disabled by default, and explain the price to pay to those who want it. I find it acceptable. >>> Physical keys also raise the issue of supporting input methods, >>> keyboard layout switches, etc. >> >> I will define a list of keys: LeftShit, RightShift, LeftControl, etc. >> The platform dependent code will decide which one was pressed. As events >> will be invisible, I don't think we will interfere with input methods. > > I meant the non-modifier keys. When some OS feature redefines a key, > low-level events will not know that, and will still consider the key > labeled 'a' as 'a', even though I might have switched the keyboard to > another language, where the key labeled 'a' actually produces a > completely different character, say, 'ש'. The use cases I have in mind are with modifiers. If others appear, it should be clear to the user that she need to bind the key by what the OS sends when she presses it. >>> However, on what systems and which Emacs configurations will it be >>> possible to provide such a feature? >> >> I think all GUI systems can use this. > > So X (with or without any toolkit we support), PGTK, MS-Windows, and > macOS -- all those let applications access low-level key events? I would not call it low level. On Windows, it would WM_KEYDOWN and VM_KEYUP. And we would use the 'virtual key', not the scan code. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-28 23:15 Physical keyboard events Cecilio Pardo 2024-10-29 13:40 ` Eli Zaretskii @ 2024-10-29 17:13 ` Alan Mackenzie 2024-10-29 18:20 ` Cecilio Pardo 2024-11-03 23:44 ` Cecilio Pardo 2 siblings, 1 reply; 93+ messages in thread From: Alan Mackenzie @ 2024-10-29 17:13 UTC (permalink / raw) To: Cecilio Pardo; +Cc: emacs-devel Hello, Cecilio. On Tue, Oct 29, 2024 at 00:15:22 +0100, Cecilio Pardo wrote: > Hello, > I'm planning to implement physical key press/release events for emacs. > I would add a new element to 'enum event_kind', that in turn would > send a new input event. This input event will be bound in > 'special-event-map' so that it will not modify the normal flow of > keyboard input. Platform dependent code would send these events > on key press and release. > Then a lisp fuction can be bound to this on the special-event-map, to > implement the detection of: > - double/triple tap on Shift, Control, Alt, etc > - Long presses on Shift, Control, Alt, etc. I envisage these leading to trouble. For example, my KVM box intercepts a sufficiently rapid double press on (either) Control key, and then expects a CR to switch computers, or ESC to cancel (and maybe there are other keys I don't know about). I quite frequently start typing a command by pressing the Control key, then change my mind and let it go again. I might sometimes hold that key while trying to decide what I want to do. The same surely applies to Meta as well. To make this work on a tty would involve fancy work on the keyboard layout if it's even possible. On a Linux console, it might even need enhancements to the kernel. I'm not sure binding our shift keys to commands, or even prefixes, is a good idea. > This actions could be bound to commands, or could add a modififer > (Super, Hyper) to next commands. > And make Tetris independent of the keyboard repeat rate :) > Has something like this been discussed before, so I can check? -- Alan Mackenzie (Nuremberg, Germany). ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 17:13 ` Alan Mackenzie @ 2024-10-29 18:20 ` Cecilio Pardo 2024-10-29 19:31 ` Alan Mackenzie 0 siblings, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-10-29 18:20 UTC (permalink / raw) To: Alan Mackenzie; +Cc: emacs-devel On 29/10/2024 18:13, Alan Mackenzie wrote: > I envisage these leading to trouble. > > For example, my KVM box intercepts a sufficiently rapid double press on > (either) Control key, and then expects a CR to switch computers, or ESC > to cancel (and maybe there are other keys I don't know about). > > I quite frequently start typing a command by pressing the Control key, > then change my mind and let it go again. I might sometimes hold that > key while trying to decide what I want to do. The same surely applies > to Meta as well. The way I hope to implement it, there will be no interference if you don't activate it. Many editors such as VSCode and the ones from JetBrains use this, and people appreciate it. > To make this work on a tty would involve fancy work on the keyboard > layout if it's even possible. On a Linux console, it might even need > enhancements to the kernel. > > I'm not sure binding our shift keys to commands, or even prefixes, is a > good idea. I was planning for GUI systems alone. Thanks. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 18:20 ` Cecilio Pardo @ 2024-10-29 19:31 ` Alan Mackenzie 2024-10-29 21:45 ` Cecilio Pardo 2024-10-30 3:27 ` Eli Zaretskii 0 siblings, 2 replies; 93+ messages in thread From: Alan Mackenzie @ 2024-10-29 19:31 UTC (permalink / raw) To: Cecilio Pardo; +Cc: emacs-devel Hello, Cecilio. On Tue, Oct 29, 2024 at 19:20:43 +0100, Cecilio Pardo wrote: > On 29/10/2024 18:13, Alan Mackenzie wrote: > > I envisage these leading to trouble. > > For example, my KVM box intercepts a sufficiently rapid double press on > > (either) Control key, and then expects a CR to switch computers, or ESC > > to cancel (and maybe there are other keys I don't know about). > > I quite frequently start typing a command by pressing the Control key, > > then change my mind and let it go again. I might sometimes hold that > > key while trying to decide what I want to do. The same surely applies > > to Meta as well. > The way I hope to implement it, there will be no interference if you > don't activate it. Many editors such as VSCode and the ones from > JetBrains use this, and people appreciate it. Somebody not activating it will not have access to the commands which other people assign to these new key combinations. That could be a problem. > > To make this work on a tty would involve fancy work on the keyboard > > layout if it's even possible. On a Linux console, it might even need > > enhancements to the kernel. > > I'm not sure binding our shift keys to commands, or even prefixes, is a > > good idea. > I was planning for GUI systems alone. That's a very un-Emacs like thing to do. Apart from specifically graphicky things, the convention is that features are available regardless of whether on a GUI or a TUI. For this reason, bindings like C-<up> are frowned upon, although they are used in some places (hello, Org!). > Thanks. -- Alan Mackenzie (Nuremberg, Germany). ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 19:31 ` Alan Mackenzie @ 2024-10-29 21:45 ` Cecilio Pardo 2024-10-30 6:02 ` Yuri Khan 2024-10-30 3:27 ` Eli Zaretskii 1 sibling, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-10-29 21:45 UTC (permalink / raw) To: Alan Mackenzie; +Cc: emacs-devel On 29/10/2024 20:31, Alan Mackenzie wrote: >> The way I hope to implement it, there will be no interference if you >> don't activate it. Many editors such as VSCode and the ones from >> JetBrains use this, and people appreciate it. > > Somebody not activating it will not have access to the commands which > other people assign to these new key combinations. That could be a > problem. This bindings would be reserved for the user, not for packages default bindings. Of course, we can't control what packages do. >> I was planning for GUI systems alone. > > That's a very un-Emacs like thing to do. Apart from specifically > graphicky things, the convention is that features are available > regardless of whether on a GUI or a TUI. For this reason, bindings like > C-<up> are frowned upon, although they are used in some places (hello, > Org!). It is practically imposible to do it on ttys, I think. I wish I could implement this in lisp, or as a module, but that is not possible. Can only be implemented modifying emacs core. This is almost a toy thing, so I'll leave it as there is disagreement. Thanks for your time. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 21:45 ` Cecilio Pardo @ 2024-10-30 6:02 ` Yuri Khan 2024-10-30 15:23 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Yuri Khan @ 2024-10-30 6:02 UTC (permalink / raw) To: Cecilio Pardo; +Cc: Alan Mackenzie, emacs-devel On Wed, 30 Oct 2024 at 04:46, Cecilio Pardo <cpardo@imayhem.com> wrote: > > That's a very un-Emacs like thing to do. Apart from specifically > > graphicky things, the convention is that features are available > > regardless of whether on a GUI or a TUI. For this reason, bindings like > > C-<up> are frowned upon, although they are used in some places (hello, > > Org!). > > It is practically imposible to do it on ttys, I think. Look into modern terminal emulators such as kitty, wezterm, and several others implementing an advanced keyboard protocol. https://sw.kovidgoyal.net/kitty/keyboard-protocol/ ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 6:02 ` Yuri Khan @ 2024-10-30 15:23 ` Eli Zaretskii 2024-10-30 16:51 ` Yuri Khan 0 siblings, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-10-30 15:23 UTC (permalink / raw) To: Yuri Khan; +Cc: cpardo, acm, emacs-devel > From: Yuri Khan <yuri.v.khan@gmail.com> > Date: Wed, 30 Oct 2024 13:02:22 +0700 > Cc: Alan Mackenzie <acm@muc.de>, emacs-devel@gnu.org > > On Wed, 30 Oct 2024 at 04:46, Cecilio Pardo <cpardo@imayhem.com> wrote: > > > > That's a very un-Emacs like thing to do. Apart from specifically > > > graphicky things, the convention is that features are available > > > regardless of whether on a GUI or a TUI. For this reason, bindings like > > > C-<up> are frowned upon, although they are used in some places (hello, > > > Org!). > > > > It is practically imposible to do it on ttys, I think. > > Look into modern terminal emulators such as kitty, wezterm, and > several others implementing an advanced keyboard protocol. We want to be able to use this on consoles, if possible, not just on terminal emulators (which are really GUI programs in disguise). ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 15:23 ` Eli Zaretskii @ 2024-10-30 16:51 ` Yuri Khan 2024-10-30 17:25 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Yuri Khan @ 2024-10-30 16:51 UTC (permalink / raw) To: Eli Zaretskii; +Cc: cpardo, acm, emacs-devel On Wed, 30 Oct 2024 at 22:23, Eli Zaretskii <eliz@gnu.org> wrote: > > From: Yuri Khan <yuri.v.khan@gmail.com> > > Look into modern terminal emulators such as kitty, wezterm, and > > several others implementing an advanced keyboard protocol. > > We want to be able to use this on consoles, if possible, not just on > terminal emulators (which are really GUI programs in disguise). You said it yourself that we don’t confine features to the lowest common denominator. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-30 16:51 ` Yuri Khan @ 2024-10-30 17:25 ` Eli Zaretskii 0 siblings, 0 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-10-30 17:25 UTC (permalink / raw) To: Yuri Khan; +Cc: cpardo, acm, emacs-devel > From: Yuri Khan <yuri.v.khan@gmail.com> > Date: Wed, 30 Oct 2024 23:51:36 +0700 > Cc: cpardo@imayhem.com, acm@muc.de, emacs-devel@gnu.org > > On Wed, 30 Oct 2024 at 22:23, Eli Zaretskii <eliz@gnu.org> wrote: > > > > From: Yuri Khan <yuri.v.khan@gmail.com> > > > Look into modern terminal emulators such as kitty, wezterm, and > > > several others implementing an advanced keyboard protocol. > > > > We want to be able to use this on consoles, if possible, not just on > > terminal emulators (which are really GUI programs in disguise). > > You said it yourself that we don’t confine features to the lowest > common denominator. Which is why I think we should not try doing this on TTYs at all. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-29 19:31 ` Alan Mackenzie 2024-10-29 21:45 ` Cecilio Pardo @ 2024-10-30 3:27 ` Eli Zaretskii 1 sibling, 0 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-10-30 3:27 UTC (permalink / raw) To: Alan Mackenzie; +Cc: cpardo, emacs-devel > Date: Tue, 29 Oct 2024 19:31:02 +0000 > Cc: emacs-devel@gnu.org > From: Alan Mackenzie <acm@muc.de> > > > I was planning for GUI systems alone. > > That's a very un-Emacs like thing to do. Apart from specifically > graphicky things, the convention is that features are available > regardless of whether on a GUI or a TUI. For this reason, bindings like > C-<up> are frowned upon, although they are used in some places (hello, > Org!). Since we already have various keyboard-related features that don't work on text-mode terminals, I see no reason to object to another one. Eventually, an idea that Emacs should not have any features that cannot be had on TTY is an idea that will keep Emacs from acquiring very useful features. Our policy should be (and is, AFAIK) that we provide on TTY everything we can, but do not reject features which cannot work on TTYs. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-10-28 23:15 Physical keyboard events Cecilio Pardo 2024-10-29 13:40 ` Eli Zaretskii 2024-10-29 17:13 ` Alan Mackenzie @ 2024-11-03 23:44 ` Cecilio Pardo 2024-11-04 0:21 ` Po Lu 2 siblings, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-11-03 23:44 UTC (permalink / raw) To: emacs-devel [-- Attachment #1: Type: text/plain, Size: 1703 bytes --] Hello, This is my proposal as a draft patch. It allows to detect double (or triple) tap on shift, control, alt keys (left or right), and bind commands to those. Also can add Hyper modifier to next key. - The change in emacs core (keyboard.c) is to define a new event 'physical-key. That event is bound to 'ignore on special-event-map, so will not interfere with anything, unless rebound. - Then, particular term implementations add these events when needed. The patch includes that for X, PGTK and ms-windows. Only rshift, lshift, lctrl, etc. generate events. We could add a variable to completely disable this if required. - Finally, the function physkey-init in physkey.el rebinds the event to physkey-handle. Bindings for n-tap of the keys can be added with physkey-bind. One can bind functions, commands, or the 'hyper symbol to add hyper modifier to the next key. I made a bad choice by using the word 'physical'. This is using key identification provided by the OS input system, so no scan codes or raw data. I also think it is unobstrusive to those who don't want this. And some related use cases that may be interesting, or not. - Cycling over something with a key like C-tab, and only really selecting when releasing C, like the window cycling with Alt+Tab, which cycles trough thumbnails until your release Alt, and lets you cancel by pressing Esc. - Better support for key-chords, https://github.com/emacsorphanage/key-chord - MacOS style holding key to get accented version of character. - Stenography input with standard keyboards. - Feature request: Add support for key release event https://debbugs.gnu.org/cgi/bugreport.cgi?bug=22163 [-- Attachment #2: 0001-Add-events-for-key-press-and-key-release-on-gui-syst.patch --] [-- Type: text/plain, Size: 12278 bytes --] From fbd9b1da821634f0288327c8924bec9fd9960aa0 Mon Sep 17 00:00:00 2001 From: Cecilio Pardo <cpardo@imayhem.com> Date: Mon, 28 Oct 2024 23:57:35 +0100 Subject: [PATCH] Add events for key press and key release on gui systems. And detection of double/triple taps on modifier keys. --- lisp/physkey.el | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ src/keyboard.c | 21 +++++++++++++ src/pgtkterm.c | 34 ++++++++++++++++++++ src/termhooks.h | 1 + src/w32fns.c | 5 +++ src/w32term.c | 38 ++++++++++++++++++++++ src/w32term.h | 3 +- src/xterm.c | 39 +++++++++++++++++++++++ 8 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 lisp/physkey.el diff --git a/lisp/physkey.el b/lisp/physkey.el new file mode 100644 index 00000000000..5dd3f81d685 --- /dev/null +++ b/lisp/physkey.el @@ -0,0 +1,83 @@ +;;; -*- lexical-binding: t -*- +(require 'cl-lib) + +;; User options +(defvar physkey-tap-timeout 1000) +(defvar physkey-tap-count 2) +(defvar physkey-tap-keys + '(lshift rshift lctrl rctrl lalt ralt)) +(defvar physkey-bindings nil) + +(defun physkey-init () + (interactive) + (define-key special-event-map [physical-key] 'physkey-handle) + (setq physkey-bindings nil) + (physkey-bind 'tap 'lshift 'delete-other-windows) + (physkey-bind 'tap 'lctrl 'hyper)) + +;; For example: +;; (physkey-add-binding 'tap 'lshift 'delete-other-windows) +;; Can bind to a command, a function or the symbol 'hyper. +(defun physkey-bind (action key function) + (push (list action key function) physkey-bindings)) + +;; We store the last events here to test for multitap. +(defvar physkey-events nil) + +;; If positive, return key ('lshift, etc) else return nil. +(defun physkey-detect-n-tap (n timeout) + ;; The physical-key event is like this: + ;; (physical-key t lshift 90196265 #<frame>) + ;; The second element is t for a key press, nil for a key release + ;; The fourth element is the time in milliseconds + ;; The fifth is the frame, we don't use it yet. + + (let ((key (cl-third last-input-event))) + (if (not (member key physkey-tap-keys)) + ;; Key not in tap list, clear history + (setq physkey-events nil) + ;; Clear it also if the first element is from a different key + (and physkey-events + (not (equal (cl-third (car physkey-events)) key)) + (setq physkey-events nil)) + (push last-input-event physkey-events) + ;; Only care about last 2xN events + (ntake (* 2 n) physkey-events) + ;; If we have: + ;; - Exactly 2 * n events. + ;; - down, up, down, up, ... + ;; - not two much time between first and last + (and (eq (* 2 n) (length physkey-events)) + (cl-every 'eq + (ntake (* 2 n) + (list nil t nil t nil t nil t + nil t nil t nil t nil t)) + (mapcar 'cl-second physkey-events)) + (< (- (cl-fourth (cl-first physkey-events)) + (cl-fourth (car (last physkey-events)))) + timeout) + (progn + (setq physkey-events nil) + key))))) + +(defun physkey-handle () + (interactive) + (let ((tap-key (physkey-detect-n-tap + physkey-tap-count + physkey-tap-timeout))) + (when tap-key + (let ((func (cl-third + (seq-find + (lambda (b) + (and (eq (cl-first b) 'tap) + (eq (cl-second b) tap-key))) + physkey-bindings)))) + (cond + ((commandp func) (call-interactively func)) + ((functionp func) (funcall func)) + ((eq 'hyper func) + (message "H-...") + (let ((r (read-event))) + (setq unread-command-events + (list (event-apply-modifier + r 'hyper 24 "H-")))))))))) diff --git a/src/keyboard.c b/src/keyboard.c index 6d28dca9aeb..4c24e3c8bd3 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4274,6 +4274,7 @@ kbd_buffer_get_event (KBOARD **kbp, case CONFIG_CHANGED_EVENT: case FOCUS_OUT_EVENT: case SELECT_WINDOW_EVENT: + case PHYSICAL_KEY_EVENT: { obj = make_lispy_event (&event->ie); kbd_fetch_ptr = next_kbd_event (event); @@ -7118,6 +7119,14 @@ make_lispy_event (struct input_event *event) case PREEDIT_TEXT_EVENT: return list2 (Qpreedit_text, event->arg); + case PHYSICAL_KEY_EVENT: + return listn (5, + Qphysical_key, + XCAR (event->arg), /* Press or release. */ + XCAR (XCDR (event->arg)), /* The key symbol. */ + make_fixnum (event->timestamp), + event->frame_or_window); + /* The 'kind' field of the event is something we don't recognize. */ default: emacs_abort (); @@ -12931,6 +12940,14 @@ syms_of_keyboard (void) DEFSYM (Qfile_notify, "file-notify"); #endif /* USE_FILE_NOTIFY */ + DEFSYM (Qphysical_key, "physical-key"); + DEFSYM (Qlshift, "lshift"); + DEFSYM (Qrshift, "rshift"); + DEFSYM (Qlctrl, "lctrl"); + DEFSYM (Qrctrl, "rctrl"); + DEFSYM (Qlalt, "lalt"); + DEFSYM (Qralt, "ralt"); + DEFSYM (Qtouch_end, "touch-end"); /* Menu and tool bar item parts. */ @@ -14018,6 +14035,10 @@ keys_of_keyboard (void) "handle-focus-out"); initial_define_lispy_key (Vspecial_event_map, "move-frame", "handle-move-frame"); + initial_define_lispy_key (Vspecial_event_map, "physical-key", + "ignore"); + + } /* Mark the pointers in the kboard objects. diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 079945126e0..f4ade6c9f1c 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -5201,6 +5201,36 @@ pgtk_enqueue_preedit (struct frame *f, Lisp_Object preedit) evq_enqueue (&inev); } +static void +pgtk_maybe_send_physical_key_event (GdkEvent *event) +{ + Lisp_Object key; + switch (event->key.keyval) + { + case GDK_KEY_Shift_L: key = Qlshift; break; + case GDK_KEY_Shift_R: key = Qrshift; break; + case GDK_KEY_Control_L: key = Qlctrl; break; + case GDK_KEY_Control_R: key = Qrctrl; break; + case GDK_KEY_Alt_L: key = Qlalt; break; + case GDK_KEY_Alt_R: key = Qralt; break; + default: + return; + } + bool keypress = event->key.type == GDK_KEY_PRESS; + struct frame *f = pgtk_any_window_to_frame (event->key.window); + if (!f) + return; + + union buffered_input_event inev; + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = PHYSICAL_KEY_EVENT; + inev.ie.timestamp = event->key.time; + inev.ie.arg = list2 (keypress ? Qt : Qnil, key); + evq_enqueue (&inev); +} + static gboolean key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { @@ -5210,6 +5240,8 @@ key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) struct frame *f; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_physical_key_event(event); + f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); EVENT_INIT (inev.ie); hlinfo = MOUSE_HL_INFO (f); @@ -5454,6 +5486,8 @@ key_release_event (GtkWidget *widget, GdkDisplay *display; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_physical_key_event(event); + display = gtk_widget_get_display (widget); dpyinfo = pgtk_display_info_for_display (display); diff --git a/src/termhooks.h b/src/termhooks.h index d6a9300bac9..f03b6126a53 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -347,6 +347,7 @@ #define EMACS_TERMHOOKS_H /* In a NOTIFICATION_EVENT, .arg is a lambda to evaluate. */ , NOTIFICATION_EVENT #endif /* HAVE_ANDROID */ + , PHYSICAL_KEY_EVENT }; /* Bit width of an enum event_kind tag at the start of structs and unions. */ diff --git a/src/w32fns.c b/src/w32fns.c index eb42d3b61b2..ef98f2db7e7 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -4650,6 +4650,8 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_KEYUP: case WM_SYSKEYUP: record_keyup (wParam, lParam); + signal_user_input (); + my_post_msg( &wmsg, hwnd, WM_EMACS_PHYSICAL_KEY, wParam, lParam ); goto dflt; case WM_KEYDOWN: @@ -4676,6 +4678,9 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) if (w32_use_fallback_wm_chars_method) wParam = map_keypad_keys (wParam, (lParam & 0x1000000L) != 0); + signal_user_input (); + my_post_msg( &wmsg, hwnd, WM_EMACS_PHYSICAL_KEY, wParam, lParam ); + windows_translate = 0; switch (wParam) diff --git a/src/w32term.c b/src/w32term.c index 88622700386..c58391d4897 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -5230,6 +5230,44 @@ w32_read_socket (struct terminal *terminal, } break; + case WM_EMACS_PHYSICAL_KEY: + WORD key_flags = HIWORD (msg.msg.lParam); + BOOL is_wm_keyup = key_flags & KF_UP; + + if (is_wm_keyup || (key_flags & KF_REPEAT) == 0) /* WM_KEYDOWN, not repeating. */ + { + WORD scan_code = LOBYTE (key_flags); + if (key_flags & KF_EXTENDED) + scan_code = MAKEWORD (scan_code, 0xE0); + + UINT translated = MapVirtualKey (scan_code, MAPVK_VSC_TO_VK_EX); + WORD vk = LOWORD (msg.msg.wParam); + if (translated) + vk = LOWORD (translated); + + Lisp_Object key = Qnil; + + switch (vk) + { + case VK_LSHIFT: key = Qlshift; break; + case VK_RSHIFT: key = Qrshift; break; + case VK_LCONTROL: key = Qlctrl; break; + case VK_RCONTROL: key = Qrctrl; break; + case VK_LMENU: key = Qlalt; break; + case VK_RMENU: key = Qralt; break; + } + + if (!NILP (key)) + { + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + inev.kind = PHYSICAL_KEY_EVENT; + XSETFRAME (inev.frame_or_window, f); + inev.timestamp = msg.msg.time; + inev.arg = list2 (is_wm_keyup ? Qnil : Qt, key); + } + } + break; + case WM_UNICHAR: case WM_SYSCHAR: case WM_CHAR: diff --git a/src/w32term.h b/src/w32term.h index cad9fcf8cb1..24fdf5421f8 100644 --- a/src/w32term.h +++ b/src/w32term.h @@ -713,7 +713,8 @@ #define WM_EMACS_FILENOTIFY (WM_EMACS_START + 25) #define WM_EMACS_IME_STATUS (WM_EMACS_START + 26) #define WM_EMACS_DRAGOVER (WM_EMACS_START + 27) #define WM_EMACS_DROP (WM_EMACS_START + 28) -#define WM_EMACS_END (WM_EMACS_START + 29) +#define WM_EMACS_PHYSICAL_KEY (WM_EMACS_START + 29) +#define WM_EMACS_END (WM_EMACS_START + 30) #define WND_FONTWIDTH_INDEX (0) #define WND_LINEHEIGHT_INDEX (4) diff --git a/src/xterm.c b/src/xterm.c index 0c20d38b0f7..210c5a9ef50 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -17840,6 +17840,43 @@ #define STORE_KEYSYM_FOR_DEBUG(keysym) ((void)0) static struct x_display_info *next_noop_dpyinfo; +static void +x_maybe_send_physical_key_event (struct x_display_info *dpyinfo, XEvent *event) +{ +#ifdef HAVE_XKB + if (event->type != KeyPress && event->type != KeyRelease) + return; + bool keypress = (event->type == KeyPress); + KeySym keysym = XkbKeycodeToKeysym (dpyinfo->display, event->xkey.keycode, + 0, 0 ); + Lisp_Object key; + switch (keysym) + { + case XK_Shift_L: key = Qlshift; break; + case XK_Shift_R: key = Qrshift; break; + case XK_Control_L: key = Qlctrl; break; + case XK_Control_R: key = Qrctrl; break; + case XK_Alt_L: key = Qlalt; break; + case XK_Alt_R: key = Qralt; break; + default: + return; + } + + struct frame *f = x_any_window_to_frame (dpyinfo, event->xkey.window); + if (!f) + return; + + struct input_event ie; + + EVENT_INIT (ie); + XSETFRAME (ie.frame_or_window, f); + ie.kind = PHYSICAL_KEY_EVENT; + ie.timestamp = event->xkey.time; + ie.arg = list2 (keypress ? Qt : Qnil, key); + kbd_buffer_store_event (&ie); +#endif +} + /* Filter events for the current X input method. DPYINFO is the display this event is for. EVENT is the X event to filter. @@ -17859,6 +17896,8 @@ x_filter_event (struct x_display_info *dpyinfo, XEvent *event) struct frame *f1; + x_maybe_send_physical_key_event (dpyinfo, event); + #if defined HAVE_XINPUT2 && defined USE_GTK bool xinput_event = false; if (dpyinfo->supports_xi2 -- 2.35.1.windows.2 ^ permalink raw reply related [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-03 23:44 ` Cecilio Pardo @ 2024-11-04 0:21 ` Po Lu 2024-11-04 8:03 ` Cecilio Pardo ` (4 more replies) 0 siblings, 5 replies; 93+ messages in thread From: Po Lu @ 2024-11-04 0:21 UTC (permalink / raw) To: Cecilio Pardo; +Cc: emacs-devel Cecilio Pardo <cpardo@imayhem.com> writes: > +static void > +x_maybe_send_physical_key_event (struct x_display_info *dpyinfo, XEvent *event) > +{ > +#ifdef HAVE_XKB Thank you. A number of comments: this is simply unacceptable, as Emacs must not depend on XKB for such a basic feature. > + if (event->type != KeyPress && event->type != KeyRelease) > + return; > + bool keypress = (event->type == KeyPress); > + KeySym keysym = XkbKeycodeToKeysym (dpyinfo->display, event->xkey.keycode, > + 0, 0 ); > + Lisp_Object key; > + switch (keysym) > + { > + case XK_Shift_L: key = Qlshift; break; > + case XK_Shift_R: key = Qrshift; break; > + case XK_Control_L: key = Qlctrl; break; > + case XK_Control_R: key = Qrctrl; break; > + case XK_Alt_L: key = Qlalt; break; > + case XK_Alt_R: key = Qralt; break; > + default: > + return; > + } ??? Code producing such events should not relay modifier keysyms verbatim, but ought to detect the virtual modifiers to which they are mapped, and convert them into Emacs modifiers, namely, meta, alt, ctrl, hyper, super, and shift. Otherwise, Lisp will find it impossible reliably to establish a correspondence between "physical key events" and modifiers. > + struct frame *f = x_any_window_to_frame (dpyinfo, event->xkey.window); > + if (!f) > + return; > + > + struct input_event ie; > + > + EVENT_INIT (ie); > + XSETFRAME (ie.frame_or_window, f); > + ie.kind = PHYSICAL_KEY_EVENT; > + ie.timestamp = event->xkey.time; > + ie.arg = list2 (keypress ? Qt : Qnil, key); > + kbd_buffer_store_event (&ie); > +#endif > +} Please find some means of generating such events by saving them into the keyboard event buffer defined in handle_ome_xevent. > /* Filter events for the current X input method. > DPYINFO is the display this event is for. > EVENT is the X event to filter. > @@ -17859,6 +17896,8 @@ x_filter_event (struct x_display_info *dpyinfo, XEvent *event) > > struct frame *f1; > > + x_maybe_send_physical_key_event (dpyinfo, event); > + > #if defined HAVE_XINPUT2 && defined USE_GTK > bool xinput_event = false; > if (dpyinfo->supports_xi2 x_filter_events is not the proper location for this call. It is only invoked to filter an event through the active GTK or X input method context, and it is not invoked if Emacs is configured not to open input method contexts, or they are unavailable by happenstance. This call is properly placed in handle_one_xevent, beneath the labels for KeyPress, KeyRelease, XI_KeyPress, XI_KeyRelease, and the GTK keyboard callbacks in gtkutil.c. Pay attention to the different manners in which the key codes are converted into keysyms and the keysyms into modifiers, and imitate them closely or reuse them if possible. Lastly, you must decide whether to capture modifier key events before they are filtered by the input method, or after. Input methods have complete discretion over disposing of these events and will either discard them entirely or resend a possibly modified event to Emacs. In the latter scenario, you will receive duplicates of one and the same event, and in principle it is impossible to distinguish between the duplicates and the originals. Equally unsatisfactory is the option of generating these events after they are filtered by the input method, since none will be generated if the IM takes the other course. These are the same concerns that led me not to implement raw modifier key events when this feature was proposed some years ago. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 0:21 ` Po Lu @ 2024-11-04 8:03 ` Cecilio Pardo 2024-11-04 9:35 ` Po Lu 2024-11-04 12:27 ` Eli Zaretskii ` (3 subsequent siblings) 4 siblings, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-11-04 8:03 UTC (permalink / raw) To: Po Lu; +Cc: emacs-devel On 04/11/2024 1:21, Po Lu wrote: > Thank you. A number of comments: this is simply unacceptable, as Emacs > must not depend on XKB for such a basic feature. Ok. > ??? Code producing such events should not relay modifier keysyms > verbatim, but ought to detect the virtual modifiers to which they are > mapped, and convert them into Emacs modifiers, namely, meta, alt, ctrl, > hyper, super, and shift. Otherwise, Lisp will find it impossible > reliably to establish a correspondence between "physical key events" and > modifiers. This is about keys, not modifiers. For the use case of binding commands to keys, this remapping would be counterproductive. I can provide a function to make that conversion if needed. > Please find some means of generating such events by saving them into the > keyboard event buffer defined in handle_ome_xevent. Ok. > x_filter_events is not the proper location for this call. It is only > invoked to filter an event through the active GTK or X input method > context, and it is not invoked if Emacs is configured not to open input > method contexts, or they are unavailable by happenstance. > > This call is properly placed in handle_one_xevent, beneath the labels > for KeyPress, KeyRelease, XI_KeyPress, XI_KeyRelease, and the GTK > keyboard callbacks in gtkutil.c. Pay attention to the different manners > in which the key codes are converted into keysyms and the keysyms into > modifiers, and imitate them closely or reuse them if possible. > > Lastly, you must decide whether to capture modifier key events before > they are filtered by the input method, or after. Input methods have > complete discretion over disposing of these events and will either > discard them entirely or resend a possibly modified event to Emacs. In > the latter scenario, you will receive duplicates of one and the same > event, and in principle it is impossible to distinguish between the > duplicates and the originals. Equally unsatisfactory is the option of > generating these events after they are filtered by the input method, > since none will be generated if the IM takes the other course. > > These are the same concerns that led me not to implement raw modifier > key events when this feature was proposed some years ago. Then probably this should only support X11+gtk and pgtk. Do those look ok? Is it acceptable to have support for nonfree systems in this case? Thanks for your review. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 8:03 ` Cecilio Pardo @ 2024-11-04 9:35 ` Po Lu 2024-11-04 11:11 ` Cecilio Pardo 2024-11-04 13:18 ` Eli Zaretskii 0 siblings, 2 replies; 93+ messages in thread From: Po Lu @ 2024-11-04 9:35 UTC (permalink / raw) To: Cecilio Pardo; +Cc: emacs-devel Cecilio Pardo <cpardo@imayhem.com> writes: > On 04/11/2024 1:21, Po Lu wrote: > >> Thank you. A number of comments: this is simply unacceptable, as Emacs >> must not depend on XKB for such a basic feature. > > Ok. > >> ??? Code producing such events should not relay modifier keysyms >> verbatim, but ought to detect the virtual modifiers to which they are >> mapped, and convert them into Emacs modifiers, namely, meta, alt, ctrl, >> hyper, super, and shift. Otherwise, Lisp will find it impossible >> reliably to establish a correspondence between "physical key events" and >> modifiers. > > This is about keys, not modifiers. For the use case of binding > commands to keys, this remapping would be counterproductive. I can > provide a function to make that conversion if needed. I must disagree. When Emacs has enough information to decide which modifiers are produced by physical keys it receives, as it does here, it should not confuse Lisp programmers with a view of the keyboard state that runs contrary to their expectations, not to mention that the disparity between X keysyms and X modifiers is very great, and users who swap the positions of the Shift and Ctrl modifiers will not expect to receive raw keyboard events which disregard their keyboard configuration. BTW, if the intention is to forward just modifier key events to Lisp, don't let's refer to them as "physical keyboard events", but in more specific terms. >> Please find some means of generating such events by saving them into the >> keyboard event buffer defined in handle_ome_xevent. > > Ok. > >> x_filter_events is not the proper location for this call. It is only >> invoked to filter an event through the active GTK or X input method >> context, and it is not invoked if Emacs is configured not to open input >> method contexts, or they are unavailable by happenstance. >> This call is properly placed in handle_one_xevent, beneath the >> labels >> for KeyPress, KeyRelease, XI_KeyPress, XI_KeyRelease, and the GTK >> keyboard callbacks in gtkutil.c. Pay attention to the different manners >> in which the key codes are converted into keysyms and the keysyms into >> modifiers, and imitate them closely or reuse them if possible. >> Lastly, you must decide whether to capture modifier key events >> before >> they are filtered by the input method, or after. Input methods have >> complete discretion over disposing of these events and will either >> discard them entirely or resend a possibly modified event to Emacs. In >> the latter scenario, you will receive duplicates of one and the same >> event, and in principle it is impossible to distinguish between the >> duplicates and the originals. Equally unsatisfactory is the option of >> generating these events after they are filtered by the input method, >> since none will be generated if the IM takes the other course. >> These are the same concerns that led me not to implement raw >> modifier >> key events when this feature was proposed some years ago. > > Then probably this should only support X11+gtk and pgtk. Do those look > ok? Is it acceptable to have support for nonfree systems in this case? It's not acceptable only to support X11+GTK and PGTK, whether or not non-free systems are also supported, because there are numerous severe deficiencies with those configurations, and with them we are at the mercy of an upstream that is uncooperative at best. And above all, what I said applies just as well to the GTK configurations. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 9:35 ` Po Lu @ 2024-11-04 11:11 ` Cecilio Pardo 2024-11-04 11:49 ` Po Lu 2024-11-04 13:18 ` Eli Zaretskii 1 sibling, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-11-04 11:11 UTC (permalink / raw) To: Po Lu; +Cc: emacs-devel On 04/11/2024 10:35, Po Lu wrote: >> This is about keys, not modifiers. For the use case of binding >> commands to keys, this remapping would be counterproductive. I can >> provide a function to make that conversion if needed. > > I must disagree. When Emacs has enough information to decide which > modifiers are produced by physical keys it receives, as it does here, it > should not confuse Lisp programmers with a view of the keyboard state > that runs contrary to their expectations, not to mention that the > disparity between X keysyms and X modifiers is very great, and users who > swap the positions of the Shift and Ctrl modifiers will not expect to > receive raw keyboard events which disregard their keyboard > configuration. > > BTW, if the intention is to forward just modifier key events to Lisp, > don't let's refer to them as "physical keyboard events", but in more > specific terms. The intention right now is just to allow users to bind actions to multiple taps on shift, control and alt keys. Using these keys and not others is just because these keys don't normally do anything when pressed alone, not because they are modifiers. If an users' customization or his input method(s) makes this unconvenient or impossible, then this is not for that user, and should not use it. Lisp programmers will not have to deal with these events unless they are looking for them by binding in special-event-map, and replacing the function that looks for multiple taps with something else. Is this feature worth the changes I propose. For me it is. I agree 'physical keyboard events' is an unfortunate phrase. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 11:11 ` Cecilio Pardo @ 2024-11-04 11:49 ` Po Lu 2024-11-04 11:59 ` Cecilio Pardo 2024-11-04 13:24 ` Eli Zaretskii 0 siblings, 2 replies; 93+ messages in thread From: Po Lu @ 2024-11-04 11:49 UTC (permalink / raw) To: Cecilio Pardo; +Cc: emacs-devel Cecilio Pardo <cpardo@imayhem.com> writes: > The intention right now is just to allow users to bind actions to > multiple taps on shift, control and alt keys. Using these keys and not > others is just because these keys don't normally do anything when > pressed alone, not because they are modifiers. If an users' > customization or his input method(s) makes this unconvenient or > impossible, then this is not for that user, and should not use it. My point is that users frequently customize or exchange the positions of these modifier keys, and it is the resultant modifiers that they expect Emacs to report, not the keysyms, in special events or elsewhere. > Is this feature worth the changes I propose. For me it is. Well, for us that is contingent on whether they are implemented properly. A feature that is adequate for half of our users, will confuse many of them, or with the sacrifices entailed by GTK, does not measure up to this standard, alas. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 11:49 ` Po Lu @ 2024-11-04 11:59 ` Cecilio Pardo 2024-11-04 13:29 ` Eli Zaretskii 2024-11-04 13:24 ` Eli Zaretskii 1 sibling, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-11-04 11:59 UTC (permalink / raw) To: Po Lu; +Cc: emacs-devel >> Is this feature worth the changes I propose. For me it is. > > Well, for us that is contingent on whether they are implemented > properly. A feature that is adequate for half of our users, will > confuse many of them, or with the sacrifices entailed by GTK, does not > measure up to this standard, alas. Ok then. Thanks for your time. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 11:59 ` Cecilio Pardo @ 2024-11-04 13:29 ` Eli Zaretskii 2024-11-04 13:46 ` Cecilio Pardo 2024-11-04 13:54 ` Po Lu 0 siblings, 2 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-11-04 13:29 UTC (permalink / raw) To: Cecilio Pardo; +Cc: luangruo, emacs-devel > Date: Mon, 4 Nov 2024 12:59:54 +0100 > Cc: emacs-devel@gnu.org > From: Cecilio Pardo <cpardo@imayhem.com> > > >> Is this feature worth the changes I propose. For me it is. > > > > Well, for us that is contingent on whether they are implemented > > properly. A feature that is adequate for half of our users, will > > confuse many of them, or with the sacrifices entailed by GTK, does not > > measure up to this standard, alas. > > Ok then. > > Thanks for your time. I hope you didn't take Po Lu's response as a rejection of your proposed patch. Given that you can incorporate his review comments regarding the place to call x_maybe_send_physical_key_event, I see no reason for us not to accept such a feature, once cleaned up and properly documented. Also, please recall that I preferred these events to be blocked (a.k.a. "discarded") subject to a special variable, instead of being inserted into the input queue and then ignored due to some keymap. IOW, I'd prefer this to behave like track-mouse. Thanks. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 13:29 ` Eli Zaretskii @ 2024-11-04 13:46 ` Cecilio Pardo 2024-11-04 13:54 ` Po Lu 1 sibling, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-11-04 13:46 UTC (permalink / raw) To: Eli Zaretskii; +Cc: luangruo, emacs-devel On 04/11/2024 14:29, Eli Zaretskii wrote: > I hope you didn't take Po Lu's response as a rejection of your > proposed patch. Given that you can incorporate his review comments > regarding the place to call x_maybe_send_physical_key_event, I see no > reason for us not to accept such a feature, once cleaned up and > properly documented. > > Also, please recall that I preferred these events to be blocked > (a.k.a. "discarded") subject to a special variable, instead of being > inserted into the input queue and then ignored due to some keymap. > IOW, I'd prefer this to behave like track-mouse. > > Thanks. I'll do that then. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 13:29 ` Eli Zaretskii 2024-11-04 13:46 ` Cecilio Pardo @ 2024-11-04 13:54 ` Po Lu 1 sibling, 0 replies; 93+ messages in thread From: Po Lu @ 2024-11-04 13:54 UTC (permalink / raw) To: Eli Zaretskii; +Cc: Cecilio Pardo, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > I hope you didn't take Po Lu's response as a rejection of your > proposed patch. +1. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 11:49 ` Po Lu 2024-11-04 11:59 ` Cecilio Pardo @ 2024-11-04 13:24 ` Eli Zaretskii 2024-11-04 14:09 ` Po Lu 1 sibling, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-11-04 13:24 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: emacs-devel@gnu.org > Date: Mon, 04 Nov 2024 19:49:32 +0800 > > Cecilio Pardo <cpardo@imayhem.com> writes: > > > The intention right now is just to allow users to bind actions to > > multiple taps on shift, control and alt keys. Using these keys and not > > others is just because these keys don't normally do anything when > > pressed alone, not because they are modifiers. If an users' > > customization or his input method(s) makes this unconvenient or > > impossible, then this is not for that user, and should not use it. > > My point is that users frequently customize or exchange the positions of > these modifier keys, and it is the resultant modifiers that they expect > Emacs to report, not the keysyms, in special events or elsewhere. They should not expect that with these physical events, IMO. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 13:24 ` Eli Zaretskii @ 2024-11-04 14:09 ` Po Lu 2024-11-04 16:46 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Po Lu @ 2024-11-04 14:09 UTC (permalink / raw) To: Eli Zaretskii; +Cc: cpardo, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> My point is that users frequently customize or exchange the positions of >> these modifier keys, and it is the resultant modifiers that they expect >> Emacs to report, not the keysyms, in special events or elsewhere. > > They should not expect that with these physical events, IMO. Why not? If Caps Lock and Ctrl are exchanged, why should Emacs depart from every other program by not reporting Ctrl when the key labeled Caps Lock is depressed? Or, if another key than Hyper itself (which is not to be found on PC keyboards) is configured to serve as the Hyper modifier, surely we want it to be reported as Hyper? The only reasonable manner of reporting these key events is reporting the modifiers that Emacs will generate in response to them. The other correct approach, that of reporting X modifier bits uninterpreted, yields physical keys that are just as meaningless (ctrl, shift, mod1, mod2, mod3, and so forth) and with equally tenuous a relationship with the modifiers that users expect to receive from Emacs. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 14:09 ` Po Lu @ 2024-11-04 16:46 ` Eli Zaretskii 2024-11-05 1:31 ` Po Lu 0 siblings, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-11-04 16:46 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: cpardo@imayhem.com, emacs-devel@gnu.org > Date: Mon, 04 Nov 2024 22:09:33 +0800 > > Eli Zaretskii <eliz@gnu.org> writes: > > >> My point is that users frequently customize or exchange the positions of > >> these modifier keys, and it is the resultant modifiers that they expect > >> Emacs to report, not the keysyms, in special events or elsewhere. > > > > They should not expect that with these physical events, IMO. > > Why not? If Caps Lock and Ctrl are exchanged, why should Emacs depart > from every other program by not reporting Ctrl when the key labeled Caps > Lock is depressed? On _physical_ level, there can be no "exchange of keys". That exchange exists on the logical level, where Ctrl followed by A yield a single event whose value is 1, instead of 4 events (2 keypresses and 2 releases), none of which is 1. > Or, if another key than Hyper itself (which is not > to be found on PC keyboards) is configured to serve as the Hyper > modifier, surely we want it to be reported as Hyper? Again, not on this level. > The only reasonable manner of reporting these key events is reporting > the modifiers that Emacs will generate in response to them. The other > correct approach, that of reporting X modifier bits uninterpreted, > yields physical keys that are just as meaningless (ctrl, shift, mod1, > mod2, mod3, and so forth) and with equally tenuous a relationship with > the modifiers that users expect to receive from Emacs. They are presumably not meaningless to a Lisp program which wants to receive such events. Or at least this is my understanding. I can also accept that in addition we should have some intermediate level, whereby some key translations are performed. But then we'd need to agree which translations are or aren't taken into consideration. In the above example, does Ctrl+A produce 01 decimal? does Ctrl-[ produce ESC? what do the keys produce when the keyboard's language is not English? etc. And whether to perform this or not should be controllable by Lisp. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 16:46 ` Eli Zaretskii @ 2024-11-05 1:31 ` Po Lu 2024-11-05 7:15 ` Cecilio Pardo 2024-11-05 13:13 ` Eli Zaretskii 0 siblings, 2 replies; 93+ messages in thread From: Po Lu @ 2024-11-05 1:31 UTC (permalink / raw) To: Eli Zaretskii; +Cc: cpardo, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: Po Lu <luangruo@yahoo.com> >> Cc: cpardo@imayhem.com, emacs-devel@gnu.org >> Date: Mon, 04 Nov 2024 22:09:33 +0800 >> >> Eli Zaretskii <eliz@gnu.org> writes: >> >> >> My point is that users frequently customize or exchange the positions of >> >> these modifier keys, and it is the resultant modifiers that they expect >> >> Emacs to report, not the keysyms, in special events or elsewhere. >> > >> > They should not expect that with these physical events, IMO. >> >> Why not? If Caps Lock and Ctrl are exchanged, why should Emacs depart >> from every other program by not reporting Ctrl when the key labeled Caps >> Lock is depressed? > > On _physical_ level, there can be no "exchange of keys". That > exchange exists on the logical level, where Ctrl followed by A yield a > single event whose value is 1, instead of 4 events (2 keypresses and 2 > releases), none of which is 1. No, that is configuring Caps Lock to serve as a Ctrl latch. I'm speaking of remapping such a key to serve as _the_ Ctrl modifier, such that Caps_Lock+A yields an XK_Caps_Lock event that is discarded by Emacs as a modifier key, followed by a second event whose value is, yes, ^A, and with the Ctrl modifier bit set in its state. >> Or, if another key than Hyper itself (which is not >> to be found on PC keyboards) is configured to serve as the Hyper >> modifier, surely we want it to be reported as Hyper? > > Again, not on this level. > >> The only reasonable manner of reporting these key events is reporting >> the modifiers that Emacs will generate in response to them. The other >> correct approach, that of reporting X modifier bits uninterpreted, >> yields physical keys that are just as meaningless (ctrl, shift, mod1, >> mod2, mod3, and so forth) and with equally tenuous a relationship with >> the modifiers that users expect to receive from Emacs. > > They are presumably not meaningless to a Lisp program which wants to > receive such events. Or at least this is my understanding. I am interested to hear what use Lisp programs intend to make of a mod2, mod3, mod4, and mod5, whose meanings vary wildly from one machine to the next. On my system, for example: shift Shift_L (0x32), Shift_R (0x3e) lock Caps_Lock (0x42) control Control_L (0x25) mod1 Alt_L (0x40), Alt_L (0xcc), Meta_L (0xcd) mod2 Num_Lock (0x4d) mod3 ISO_Level5_Shift (0xcb) mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf) mod5 ISO_Level3_Shift (0x5c) while on another: shift 44 57 # Left Shift, Right Shift lock 30 # CapsLock control 58 64 # Control mod1 59 63 # Meta mod2 62 # AltGraph mod3 90 # NumLock mod4 60 # Left Alt mod5 65 # Compose Without consulting the X11 modifier keymap (which is impossible from Lisp), how should a Lisp program derive any meaning from these modifier bits, or the keys to which they are assigned? > I can also accept that in addition we should have some intermediate > level, whereby some key translations are performed. But then we'd > need to agree which translations are or aren't taken into > consideration. In the above example, does Ctrl+A produce 01 decimal? > does Ctrl-[ produce ESC? what do the keys produce when the keyboard's > language is not English? etc. And whether to perform this or not > should be controllable by Lisp. This reaches far beyond the scope of just reporting modifier activation that is usually discarded by Emacs (and which is always reported by the X server). ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 1:31 ` Po Lu @ 2024-11-05 7:15 ` Cecilio Pardo 2024-11-05 9:03 ` Po Lu 2024-11-05 13:13 ` Eli Zaretskii 1 sibling, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-11-05 7:15 UTC (permalink / raw) To: Po Lu, Eli Zaretskii; +Cc: emacs-devel On 05/11/2024 2:31, Po Lu wrote: > Eli Zaretskii <eliz@gnu.org> writes: > >>> From: Po Lu <luangruo@yahoo.com> >>> Cc: cpardo@imayhem.com, emacs-devel@gnu.org >>> Date: Mon, 04 Nov 2024 22:09:33 +0800 >>> >>> Eli Zaretskii <eliz@gnu.org> writes: >>> >>>>> My point is that users frequently customize or exchange the positions of >>>>> these modifier keys, and it is the resultant modifiers that they expect >>>>> Emacs to report, not the keysyms, in special events or elsewhere. >>>> >>>> They should not expect that with these physical events, IMO. >>> >>> Why not? If Caps Lock and Ctrl are exchanged, why should Emacs depart >>> from every other program by not reporting Ctrl when the key labeled Caps >>> Lock is depressed? >> >> On _physical_ level, there can be no "exchange of keys". That >> exchange exists on the logical level, where Ctrl followed by A yield a >> single event whose value is 1, instead of 4 events (2 keypresses and 2 >> releases), none of which is 1. > > No, that is configuring Caps Lock to serve as a Ctrl latch. I'm > speaking of remapping such a key to serve as _the_ Ctrl modifier, such > that Caps_Lock+A yields an XK_Caps_Lock event that is discarded by Emacs > as a modifier key, followed by a second event whose value is, yes, ^A, > and with the Ctrl modifier bit set in its state. > >>> Or, if another key than Hyper itself (which is not >>> to be found on PC keyboards) is configured to serve as the Hyper >>> modifier, surely we want it to be reported as Hyper? >> >> Again, not on this level. >> >>> The only reasonable manner of reporting these key events is reporting >>> the modifiers that Emacs will generate in response to them. The other >>> correct approach, that of reporting X modifier bits uninterpreted, >>> yields physical keys that are just as meaningless (ctrl, shift, mod1, >>> mod2, mod3, and so forth) and with equally tenuous a relationship with >>> the modifiers that users expect to receive from Emacs. >> >> They are presumably not meaningless to a Lisp program which wants to >> receive such events. Or at least this is my understanding. > > I am interested to hear what use Lisp programs intend to make of a mod2, > mod3, mod4, and mod5, whose meanings vary wildly from one machine to the > next. On my system, for example: > > shift Shift_L (0x32), Shift_R (0x3e) > lock Caps_Lock (0x42) > control Control_L (0x25) > mod1 Alt_L (0x40), Alt_L (0xcc), Meta_L (0xcd) > mod2 Num_Lock (0x4d) > mod3 ISO_Level5_Shift (0xcb) > mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf) > mod5 ISO_Level3_Shift (0x5c) > > while on another: > > shift 44 57 # Left Shift, Right Shift > lock 30 # CapsLock > control 58 64 # Control > mod1 59 63 # Meta > mod2 62 # AltGraph > mod3 90 # NumLock > mod4 60 # Left Alt > mod5 65 # Compose > > Without consulting the X11 modifier keymap (which is impossible from > Lisp), how should a Lisp program derive any meaning from these modifier > bits, or the keys to which they are assigned? The idea is to report Shift_L, Shift_R, Alt_L, Meta_L not shift or alt. Maybe those keys are mapped to modifiers, maybe not. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 7:15 ` Cecilio Pardo @ 2024-11-05 9:03 ` Po Lu 2024-11-05 9:20 ` Cecilio Pardo 0 siblings, 1 reply; 93+ messages in thread From: Po Lu @ 2024-11-05 9:03 UTC (permalink / raw) To: Cecilio Pardo; +Cc: Eli Zaretskii, emacs-devel Cecilio Pardo <cpardo@imayhem.com> writes: > The idea is to report Shift_L, Shift_R, Alt_L, Meta_L not shift or > alt. Maybe those keys are mapped to modifiers, maybe not. And this is improper, as the keysym does not reflect the meaning of the key. If someone who configures XK_Caps_Lock as a Ctrl modifier presses this key which is by all accounts Ctrl, no "physical keyboard event" will be reported, and by the same token, Ctrl will be reported even if it is the Caps Lock latch that is activated if their functions are exchanged. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 9:03 ` Po Lu @ 2024-11-05 9:20 ` Cecilio Pardo 2024-11-05 12:21 ` Po Lu 0 siblings, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-11-05 9:20 UTC (permalink / raw) To: Po Lu; +Cc: Eli Zaretskii, emacs-devel On 05/11/2024 10:03, Po Lu wrote: > Cecilio Pardo <g@imayhem.com> writes: > >> The idea is to report Shift_L, Shift_R, Alt_L, Meta_L not shift or >> alt. Maybe those keys are mapped to modifiers, maybe not. > > And this is improper, as the keysym does not reflect the meaning of the > key. If someone who configures XK_Caps_Lock as a Ctrl modifier presses > this key which is by all accounts Ctrl, no "physical keyboard event" > will be reported, and by the same token, Ctrl will be reported even if > it is the Caps Lock latch that is activated if their functions are > exchanged. We are implementing the ability to bind an action to the press of the key, regardless of its meaning as a modifier. This way, we can take advantage of double keys (Shift_L, Shift_R) and give more options to the user. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 9:20 ` Cecilio Pardo @ 2024-11-05 12:21 ` Po Lu 2024-11-05 13:30 ` Eli Zaretskii 2024-11-05 14:27 ` Cecilio Pardo 0 siblings, 2 replies; 93+ messages in thread From: Po Lu @ 2024-11-05 12:21 UTC (permalink / raw) To: Cecilio Pardo; +Cc: Eli Zaretskii, emacs-devel Cecilio Pardo <cpardo@imayhem.com> writes: > We are implementing the ability to bind an action to the press of the > key, regardless of its meaning as a modifier. This way, we can take > advantage of double keys (Shift_L, Shift_R) and give more options to > the user. It's not a matter of giving users more choice or finer control, but of correctness. How will you handle the case where the key labeled Caps Lock is, in all functional respects, Ctrl, and that labeled Ctrl serves another purpose that renders it simply unavailable to Emacs? But let me pose a different question: why do you need to distinguish between left and right modifier keys? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 12:21 ` Po Lu @ 2024-11-05 13:30 ` Eli Zaretskii 2024-11-05 14:27 ` Cecilio Pardo 1 sibling, 0 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-11-05 13:30 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org > Date: Tue, 05 Nov 2024 20:21:02 +0800 > > Cecilio Pardo <cpardo@imayhem.com> writes: > > > We are implementing the ability to bind an action to the press of the > > key, regardless of its meaning as a modifier. This way, we can take > > advantage of double keys (Shift_L, Shift_R) and give more options to > > the user. > > It's not a matter of giving users more choice or finer control, but of > correctness. How will you handle the case where the key labeled Caps > Lock is, in all functional respects, Ctrl, and that labeled Ctrl serves > another purpose that renders it simply unavailable to Emacs? Who said we should handle that case in any way, except reporting the key as CapsLock? > But let me pose a different question: why do you need to distinguish > between left and right modifier keys? Because they are separate keys. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 12:21 ` Po Lu 2024-11-05 13:30 ` Eli Zaretskii @ 2024-11-05 14:27 ` Cecilio Pardo 2024-11-06 0:10 ` Po Lu 2024-11-06 12:49 ` Po Lu 1 sibling, 2 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-11-05 14:27 UTC (permalink / raw) To: Po Lu; +Cc: Eli Zaretskii, emacs-devel On 05/11/2024 13:21, Po Lu wrote: > Cecilio Pardo <cpardo@imayhem.com> writes: > >> We are implementing the ability to bind an action to the press of the >> key, regardless of its meaning as a modifier. This way, we can take >> advantage of double keys (Shift_L, Shift_R) and give more options to >> the user. > > It's not a matter of giving users more choice or finer control, but of > correctness. How will you handle the case where the key labeled Caps > Lock is, in all functional respects, Ctrl, and that labeled Ctrl serves > another purpose that renders it simply unavailable to Emacs? We would respond to XK_Caps_Lock by sending an event with the symbol 'capslock, and to Control with lctrl and rctrl. If the control key is unavailable beacuse it serves another purpose, then user should not bind it to anything. I don't think this is incorrect in any way. We are giving the user the ability to detect this keypresses. It's up to the user to decide if she wants to do it or not. > But let me pose a different question: why do you need to distinguish > between left and right modifier keys? So I can bind a different command to each one of them. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 14:27 ` Cecilio Pardo @ 2024-11-06 0:10 ` Po Lu 2024-11-06 12:49 ` Po Lu 1 sibling, 0 replies; 93+ messages in thread From: Po Lu @ 2024-11-06 0:10 UTC (permalink / raw) To: Cecilio Pardo; +Cc: Eli Zaretskii, emacs-devel ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 14:27 ` Cecilio Pardo 2024-11-06 0:10 ` Po Lu @ 2024-11-06 12:49 ` Po Lu 2024-11-06 13:31 ` Eli Zaretskii 1 sibling, 1 reply; 93+ messages in thread From: Po Lu @ 2024-11-06 12:49 UTC (permalink / raw) To: Cecilio Pardo; +Cc: Eli Zaretskii, emacs-devel I posted this twelve hours ago, but it was truncated in transmission. Cecilio Pardo <cpardo@imayhem.com> writes: > We would respond to XK_Caps_Lock by sending an event with the symbol > 'capslock, and to Control with lctrl and rctrl. If the control key is > unavailable beacuse it serves another purpose, then user should not > bind it to anything. This would prevent any portable Lisp program from being written that exercises this feature. For example, your patch only handles Alt_L and Alt_R, while the key _labeled_ alt on the right side of my keyboard is mapped to ISO_Level3_Shift. The purpose of detecting left and right keys is defeated anyway by such rudimentary treatment of the X keyboard system. > I don't think this is incorrect in any way. We are giving the user the > ability to detect this keypresses. It's up to the user to decide if > she wants to do it or not. I don't agree. Users may be completely excluded from deciding this question if they are installing another person's Lisp, for example. > So I can bind a different command to each one of them. Why can't you read the modifier map _first_ (by keycode, not keysym), before trying to distinguish between left and right keys by inference from the keysym, or by reading the keyboard geometry if XKB is available? And report key events such as `meta' and `right-meta'? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-06 12:49 ` Po Lu @ 2024-11-06 13:31 ` Eli Zaretskii 2024-11-07 0:25 ` Po Lu 0 siblings, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-11-06 13:31 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org > Date: Wed, 06 Nov 2024 20:49:37 +0800 > > I posted this twelve hours ago, but it was truncated in transmission. > > Cecilio Pardo <cpardo@imayhem.com> writes: > > > We would respond to XK_Caps_Lock by sending an event with the symbol > > 'capslock, and to Control with lctrl and rctrl. If the control key is > > unavailable beacuse it serves another purpose, then user should not > > bind it to anything. > > This would prevent any portable Lisp program from being written that > exercises this feature. For example, your patch only handles Alt_L and > Alt_R, while the key _labeled_ alt on the right side of my keyboard is > mapped to ISO_Level3_Shift. The purpose of detecting left and right > keys is defeated anyway by such rudimentary treatment of the X keyboard > system. > > > I don't think this is incorrect in any way. We are giving the user the > > ability to detect this keypresses. It's up to the user to decide if > > she wants to do it or not. > > I don't agree. Users may be completely excluded from deciding this > question if they are installing another person's Lisp, for example. > > > So I can bind a different command to each one of them. > > Why can't you read the modifier map _first_ (by keycode, not keysym), > before trying to distinguish between left and right keys by inference > from the keysym, or by reading the keyboard geometry if XKB is > available? And report key events such as `meta' and `right-meta'? I already said that we could have both what you want and what Cecilio considers TRT. Why do you keep arguing for your concept as the only one? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-06 13:31 ` Eli Zaretskii @ 2024-11-07 0:25 ` Po Lu 2024-11-07 6:41 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Po Lu @ 2024-11-07 0:25 UTC (permalink / raw) To: Eli Zaretskii; +Cc: cpardo, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > I already said that we could have both what you want and what Cecilio > considers TRT. Why do you keep arguing for your concept as the only > one? Because the former is fatally hamstrung for many users, from whom we will undoubtedly receive a spate of issues reporting that their modifier keys are not producing activation events. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-07 0:25 ` Po Lu @ 2024-11-07 6:41 ` Eli Zaretskii 2024-11-07 14:36 ` Po Lu 0 siblings, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-11-07 6:41 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: cpardo@imayhem.com, emacs-devel@gnu.org > Date: Thu, 07 Nov 2024 08:25:53 +0800 > > Eli Zaretskii <eliz@gnu.org> writes: > > > I already said that we could have both what you want and what Cecilio > > considers TRT. Why do you keep arguing for your concept as the only > > one? > > Because the former is fatally hamstrung for many users, from whom we > will undoubtedly receive a spate of issues reporting that their modifier > keys are not producing activation events. I understand what you are saying, but Cecilio and myself clearly disagree, so why not have both abilities in Emacs (assuming that it is possible and practical)? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-07 6:41 ` Eli Zaretskii @ 2024-11-07 14:36 ` Po Lu 2024-11-07 15:47 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Po Lu @ 2024-11-07 14:36 UTC (permalink / raw) To: Eli Zaretskii; +Cc: cpardo, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: Po Lu <luangruo@yahoo.com> >> Cc: cpardo@imayhem.com, emacs-devel@gnu.org >> Date: Thu, 07 Nov 2024 08:25:53 +0800 >> >> Eli Zaretskii <eliz@gnu.org> writes: >> >> > I already said that we could have both what you want and what Cecilio >> > considers TRT. Why do you keep arguing for your concept as the only >> > one? >> >> Because the former is fatally hamstrung for many users, from whom we >> will undoubtedly receive a spate of issues reporting that their modifier >> keys are not producing activation events. > > I understand what you are saying, but Cecilio and myself clearly > disagree, so why not have both abilities in Emacs (assuming that it is > possible and practical)? That would be redundant, since there is no key binding inexpressible with the events I propose that can be expressed with events generated from keysyms. For most users, it will be just a simple matter of writing `meta-left' or `meta-right' rather than `alt-right', and so forth. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-07 14:36 ` Po Lu @ 2024-11-07 15:47 ` Eli Zaretskii 2024-11-07 16:58 ` Cecilio Pardo 2024-11-08 0:36 ` Po Lu 0 siblings, 2 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-11-07 15:47 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: cpardo@imayhem.com, emacs-devel@gnu.org > Date: Thu, 07 Nov 2024 22:36:57 +0800 > > Eli Zaretskii <eliz@gnu.org> writes: > > >> From: Po Lu <luangruo@yahoo.com> > >> Cc: cpardo@imayhem.com, emacs-devel@gnu.org > >> Date: Thu, 07 Nov 2024 08:25:53 +0800 > >> > >> Eli Zaretskii <eliz@gnu.org> writes: > >> > >> > I already said that we could have both what you want and what Cecilio > >> > considers TRT. Why do you keep arguing for your concept as the only > >> > one? > >> > >> Because the former is fatally hamstrung for many users, from whom we > >> will undoubtedly receive a spate of issues reporting that their modifier > >> keys are not producing activation events. > > > > I understand what you are saying, but Cecilio and myself clearly > > disagree, so why not have both abilities in Emacs (assuming that it is > > possible and practical)? > > That would be redundant, since there is no key binding inexpressible > with the events I propose that can be expressed with events generated > from keysyms. It isn't redundant, since it reports different events from the same keys. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-07 15:47 ` Eli Zaretskii @ 2024-11-07 16:58 ` Cecilio Pardo 2024-11-08 0:36 ` Po Lu 1 sibling, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-11-07 16:58 UTC (permalink / raw) To: Eli Zaretskii, Po Lu; +Cc: emacs-devel On 07/11/2024 16:47, Eli Zaretskii wrote: >> From: Po Lu <luangruo@yahoo.com> >> Cc: cpardo@imayhem.com, emacs-devel@gnu.org >> Date: Thu, 07 Nov 2024 22:36:57 +0800 >> >> Eli Zaretskii <eliz@gnu.org> writes: >> >>>> From: Po Lu <luangruo@yahoo.com> >>>> Cc: cpardo@imayhem.com, emacs-devel@gnu.org >>>> Date: Thu, 07 Nov 2024 08:25:53 +0800 >>>> >>>> Eli Zaretskii <eliz@gnu.org> writes: >>>> >>>>> I already said that we could have both what you want and what Cecilio >>>>> considers TRT. Why do you keep arguing for your concept as the only >>>>> one? >>>> >>>> Because the former is fatally hamstrung for many users, from whom we >>>> will undoubtedly receive a spate of issues reporting that their modifier >>>> keys are not producing activation events. >>> >>> I understand what you are saying, but Cecilio and myself clearly >>> disagree, so why not have both abilities in Emacs (assuming that it is >>> possible and practical)? >> >> That would be redundant, since there is no key binding inexpressible >> with the events I propose that can be expressed with events generated >> from keysyms. > > It isn't redundant, since it reports different events from the same > keys. What about reporting an event, but with all pieces of information? Alt_L Meta Meta_L The 'keysym', the 'modifier' or nil, the 'sided-modifier' with an _L because the keysym has an _L, or nil. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-07 15:47 ` Eli Zaretskii 2024-11-07 16:58 ` Cecilio Pardo @ 2024-11-08 0:36 ` Po Lu 1 sibling, 0 replies; 93+ messages in thread From: Po Lu @ 2024-11-08 0:36 UTC (permalink / raw) To: Eli Zaretskii; +Cc: cpardo, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> That would be redundant, since there is no key binding inexpressible >> with the events I propose that can be expressed with events generated >> from keysyms. > > It isn't redundant, since it reports different events from the same > keys. But ultimately it is, literally, only a difference in name that makes a world of difference for reliability. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 1:31 ` Po Lu 2024-11-05 7:15 ` Cecilio Pardo @ 2024-11-05 13:13 ` Eli Zaretskii 1 sibling, 0 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-11-05 13:13 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: cpardo@imayhem.com, emacs-devel@gnu.org > Date: Tue, 05 Nov 2024 09:31:14 +0800 > > Eli Zaretskii <eliz@gnu.org> writes: > > > On _physical_ level, there can be no "exchange of keys". That > > exchange exists on the logical level, where Ctrl followed by A yield a > > single event whose value is 1, instead of 4 events (2 keypresses and 2 > > releases), none of which is 1. > > No, that is configuring Caps Lock to serve as a Ctrl latch. I'm > speaking of remapping such a key to serve as _the_ Ctrl modifier, such > that Caps_Lock+A yields an XK_Caps_Lock event that is discarded by Emacs > as a modifier key, followed by a second event whose value is, yes, ^A, > and with the Ctrl modifier bit set in its state. I'm talking about a program that doesn't want to see ^A, since there's no such key. > >> The only reasonable manner of reporting these key events is reporting > >> the modifiers that Emacs will generate in response to them. The other > >> correct approach, that of reporting X modifier bits uninterpreted, > >> yields physical keys that are just as meaningless (ctrl, shift, mod1, > >> mod2, mod3, and so forth) and with equally tenuous a relationship with > >> the modifiers that users expect to receive from Emacs. > > > > They are presumably not meaningless to a Lisp program which wants to > > receive such events. Or at least this is my understanding. > > I am interested to hear what use Lisp programs intend to make of a mod2, > mod3, mod4, and mod5, whose meanings vary wildly from one machine to the > next. A program which wants to show the pressed key, for example? > On my system, for example: > > shift Shift_L (0x32), Shift_R (0x3e) > lock Caps_Lock (0x42) > control Control_L (0x25) > mod1 Alt_L (0x40), Alt_L (0xcc), Meta_L (0xcd) > mod2 Num_Lock (0x4d) > mod3 ISO_Level5_Shift (0xcb) > mod4 Super_L (0x85), Super_R (0x86), Super_L (0xce), Hyper_L (0xcf) > mod5 ISO_Level3_Shift (0x5c) > > while on another: > > shift 44 57 # Left Shift, Right Shift > lock 30 # CapsLock > control 58 64 # Control > mod1 59 63 # Meta > mod2 62 # AltGraph > mod3 90 # NumLock > mod4 60 # Left Alt > mod5 65 # Compose I don't understand what this means, sorry. (You seem to be too deep in the particulars of the X keyboard handling.) I never said anything about mod1 etc. > Without consulting the X11 modifier keymap (which is impossible from > Lisp), how should a Lisp program derive any meaning from these modifier > bits, or the keys to which they are assigned? I don't know. I didn't write the patch. All I'm saying is that if the key is labeled Alt, I can envision some Lisp programs which will want to know the key pressed was Alt, not Meta. > > I can also accept that in addition we should have some intermediate > > level, whereby some key translations are performed. But then we'd > > need to agree which translations are or aren't taken into > > consideration. In the above example, does Ctrl+A produce 01 decimal? > > does Ctrl-[ produce ESC? what do the keys produce when the keyboard's > > language is not English? etc. And whether to perform this or not > > should be controllable by Lisp. > > This reaches far beyond the scope of just reporting modifier activation > that is usually discarded by Emacs (and which is always reported by the > X server). So? Emacs reaches far beyond many things. We should ideally decide what we want this feature to do, and then see how to implement it, not the other way around. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 9:35 ` Po Lu 2024-11-04 11:11 ` Cecilio Pardo @ 2024-11-04 13:18 ` Eli Zaretskii 2024-11-04 14:37 ` Po Lu 1 sibling, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-11-04 13:18 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: emacs-devel@gnu.org > Date: Mon, 04 Nov 2024 17:35:52 +0800 > > Cecilio Pardo <cpardo@imayhem.com> writes: > > >> ??? Code producing such events should not relay modifier keysyms > >> verbatim, but ought to detect the virtual modifiers to which they are > >> mapped, and convert them into Emacs modifiers, namely, meta, alt, ctrl, > >> hyper, super, and shift. Otherwise, Lisp will find it impossible > >> reliably to establish a correspondence between "physical key events" and > >> modifiers. > > > > This is about keys, not modifiers. For the use case of binding > > commands to keys, this remapping would be counterproductive. I can > > provide a function to make that conversion if needed. > > I must disagree. When Emacs has enough information to decide which > modifiers are produced by physical keys it receives, as it does here, it > should not confuse Lisp programmers with a view of the keyboard state > that runs contrary to their expectations, not to mention that the > disparity between X keysyms and X modifiers is very great, and users who > swap the positions of the Shift and Ctrl modifiers will not expect to > receive raw keyboard events which disregard their keyboard > configuration. I don't think I agree with you. I think I'm with Cecilio here. Physical key events means that Emacs doesn't perform any conversions, it leaves that to the Lisp program which receives such events. E.g., when I press the Alt key, I expect to see an event of pressing the Alt key, not a (non-existent) Meta. And if I press the Right Alt key, I expect to see AltGr, not Alt and not Meta. > BTW, if the intention is to forward just modifier key events to Lisp, > don't let's refer to them as "physical keyboard events", but in more > specific terms. Yes, I think the idea is to generate modifier key events and expose them to Lisp. What is more specific than "physical keyboard events"? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 13:18 ` Eli Zaretskii @ 2024-11-04 14:37 ` Po Lu 2024-11-04 16:49 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Po Lu @ 2024-11-04 14:37 UTC (permalink / raw) To: Eli Zaretskii; +Cc: cpardo, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > I don't think I agree with you. I think I'm with Cecilio here. > Physical key events means that Emacs doesn't perform any conversions, > it leaves that to the Lisp program which receives such events. E.g., > when I press the Alt key, I expect to see an event of pressing the Alt > key, not a (non-existent) Meta. And if I press the Right Alt key, I > expect to see AltGr, not Alt and not Meta. The issue is that X does not attach enough significance to keysyms reliably to tell AltGr from Alt, or the like, with a keysym alone. A keysym is just that--a symbol printed on a key cap, and it is up to users to bind these keys to modifier bits, all of which but Shift, Caps Lock, and Ctrl are completely nondescript and for client programs such as Emacs to interpret. E.g., AltGr could be configured on the OS level to serve as a level 3 shift key, as it typically does, or as an entirely different modifier, without affecting the keysym (XK_Alt_R _or_ XK_ISO_Level3_Shift) or even the modifier bit (on my system, Mod5) generated. Likewise, Caps Lock could be configured to Control while continuing to be reported as XK_Caps_Lock. Emacs already tries to establish a sensible relationship between modifier bits and Emacs modifiers, and clearly, the reasonable way forward is to write a small quantity of code to deduce the modifier bits produced by a keysym, and reuse the existing mechanism to report a modifier that users will expect, instead of engineering a different mechanism for the purposes of reporting modifier activation and deactivation, or, as Cecilio's patch currently does, reporting keysyms totally independent of the modifiers actually bound to their keys. >> BTW, if the intention is to forward just modifier key events to Lisp, >> don't let's refer to them as "physical keyboard events", but in more >> specific terms. > > Yes, I think the idea is to generate modifier key events and expose > them to Lisp. What is more specific than "physical keyboard events"? "Modifier activation and release events", perhaps? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 14:37 ` Po Lu @ 2024-11-04 16:49 ` Eli Zaretskii 2024-11-05 1:03 ` Po Lu 0 siblings, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-11-04 16:49 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: cpardo@imayhem.com, emacs-devel@gnu.org > Date: Mon, 04 Nov 2024 22:37:44 +0800 > > Emacs already tries to establish a sensible relationship between > modifier bits and Emacs modifiers, and clearly, the reasonable way > forward is to write a small quantity of code to deduce the modifier bits > produced by a keysym, and reuse the existing mechanism to report a > modifier that users will expect, instead of engineering a different > mechanism for the purposes of reporting modifier activation and > deactivation, or, as Cecilio's patch currently does, reporting keysyms > totally independent of the modifiers actually bound to their keys. I can see a place for both. It all depends on what the Lisp program wants to do. So I guess we should allow Lisp programs to receive one or the other. > >> BTW, if the intention is to forward just modifier key events to Lisp, > >> don't let's refer to them as "physical keyboard events", but in more > >> specific terms. > > > > Yes, I think the idea is to generate modifier key events and expose > > them to Lisp. What is more specific than "physical keyboard events"? > > "Modifier activation and release events", perhaps? That's a mouthful. How about "low-level keyboard events"? ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 16:49 ` Eli Zaretskii @ 2024-11-05 1:03 ` Po Lu 2024-11-05 7:09 ` Cecilio Pardo 2024-11-05 13:06 ` Eli Zaretskii 0 siblings, 2 replies; 93+ messages in thread From: Po Lu @ 2024-11-05 1:03 UTC (permalink / raw) To: Eli Zaretskii; +Cc: cpardo, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: >> From: Po Lu <luangruo@yahoo.com> >> Cc: cpardo@imayhem.com, emacs-devel@gnu.org >> Date: Mon, 04 Nov 2024 22:37:44 +0800 >> >> Emacs already tries to establish a sensible relationship between >> modifier bits and Emacs modifiers, and clearly, the reasonable way >> forward is to write a small quantity of code to deduce the modifier bits >> produced by a keysym, and reuse the existing mechanism to report a >> modifier that users will expect, instead of engineering a different >> mechanism for the purposes of reporting modifier activation and >> deactivation, or, as Cecilio's patch currently does, reporting keysyms >> totally independent of the modifiers actually bound to their keys. > > I can see a place for both. It all depends on what the Lisp program > wants to do. So I guess we should allow Lisp programs to receive one > or the other. What _is_ the place for the second? Binding commands to a modifier key is possible whatever may be the name of the key to which they are bound (alt or meta). Besides, the patch as written doesn't implement either behavior reliably, because it tests against a few specific keysym names, which is not a reliable means of detecting modifier keys on X. >> >> BTW, if the intention is to forward just modifier key events to Lisp, >> >> don't let's refer to them as "physical keyboard events", but in more >> >> specific terms. >> > >> > Yes, I think the idea is to generate modifier key events and expose >> > them to Lisp. What is more specific than "physical keyboard events"? >> >> "Modifier activation and release events", perhaps? > > That's a mouthful. How about "low-level keyboard events"? This still misleadingly implies that keys beyond modifiers will be reported. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 1:03 ` Po Lu @ 2024-11-05 7:09 ` Cecilio Pardo 2024-11-05 13:06 ` Eli Zaretskii 1 sibling, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-11-05 7:09 UTC (permalink / raw) To: Po Lu, Eli Zaretskii; +Cc: emacs-devel On 05/11/2024 2:03, Po Lu wrote: >>>>> BTW, if the intention is to forward just modifier key events to Lisp, >>>>> don't let's refer to them as "physical keyboard events", but in more >>>>> specific terms. >>>> >>>> Yes, I think the idea is to generate modifier key events and expose >>>> them to Lisp. What is more specific than "physical keyboard events"? >>> >>> "Modifier activation and release events", perhaps? >> >> That's a mouthful. How about "low-level keyboard events"? > > This still misleadingly implies that keys beyond modifiers will be > reported. The only use case right now is detecting taps in modifier keys, maybe long presses. If more use cases appear, the list of reported keys would be expanded. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-05 1:03 ` Po Lu 2024-11-05 7:09 ` Cecilio Pardo @ 2024-11-05 13:06 ` Eli Zaretskii 1 sibling, 0 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-11-05 13:06 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: cpardo@imayhem.com, emacs-devel@gnu.org > Date: Tue, 05 Nov 2024 09:03:00 +0800 > > Eli Zaretskii <eliz@gnu.org> writes: > > >> From: Po Lu <luangruo@yahoo.com> > >> Cc: cpardo@imayhem.com, emacs-devel@gnu.org > >> Date: Mon, 04 Nov 2024 22:37:44 +0800 > >> > >> Emacs already tries to establish a sensible relationship between > >> modifier bits and Emacs modifiers, and clearly, the reasonable way > >> forward is to write a small quantity of code to deduce the modifier bits > >> produced by a keysym, and reuse the existing mechanism to report a > >> modifier that users will expect, instead of engineering a different > >> mechanism for the purposes of reporting modifier activation and > >> deactivation, or, as Cecilio's patch currently does, reporting keysyms > >> totally independent of the modifiers actually bound to their keys. > > > > I can see a place for both. It all depends on what the Lisp program > > wants to do. So I guess we should allow Lisp programs to receive one > > or the other. > > What _is_ the place for the second? Which one is the second? > Binding commands to a modifier key > is possible whatever may be the name of the key to which they are bound > (alt or meta). But if there's no Meta key on the keyboard, emitting Meta will be misleading, at least in some use cases. > Besides, the patch as written doesn't implement either > behavior reliably, because it tests against a few specific keysym names, > which is not a reliable means of detecting modifier keys on X. That's a separate issue. We are discussing the principles here, and principles don't depend on the particulars of some implementation. > >> >> BTW, if the intention is to forward just modifier key events to Lisp, > >> >> don't let's refer to them as "physical keyboard events", but in more > >> >> specific terms. > >> > > >> > Yes, I think the idea is to generate modifier key events and expose > >> > them to Lisp. What is more specific than "physical keyboard events"? > >> > >> "Modifier activation and release events", perhaps? > > > > That's a mouthful. How about "low-level keyboard events"? > > This still misleadingly implies that keys beyond modifiers will be > reported. If you were talking about modifier keys only, why do these events need a separate name? I suggested the above as the name for all raw keyboard events, no matter which key is pressed/released. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 0:21 ` Po Lu 2024-11-04 8:03 ` Cecilio Pardo @ 2024-11-04 12:27 ` Eli Zaretskii 2024-11-04 13:09 ` Po Lu 2024-11-16 8:42 ` Cecilio Pardo ` (2 subsequent siblings) 4 siblings, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-11-04 12:27 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: emacs-devel@gnu.org > Date: Mon, 04 Nov 2024 08:21:09 +0800 > > Cecilio Pardo <cpardo@imayhem.com> writes: > > > +static void > > +x_maybe_send_physical_key_event (struct x_display_info *dpyinfo, XEvent *event) > > +{ > > +#ifdef HAVE_XKB > > Thank you. A number of comments: this is simply unacceptable, as Emacs > must not depend on XKB for such a basic feature. This is not a basic feature, from where I stand. It's an enhancement. My evidence is that Emacs has lived for 4 decades without it. So please explain why would you reject an implementation of this which requires XKB. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 12:27 ` Eli Zaretskii @ 2024-11-04 13:09 ` Po Lu 2024-11-04 13:33 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Po Lu @ 2024-11-04 13:09 UTC (permalink / raw) To: Eli Zaretskii; +Cc: cpardo, emacs-devel Eli Zaretskii <eliz@gnu.org> writes: > This is not a basic feature, from where I stand. It's an enhancement. > My evidence is that Emacs has lived for 4 decades without it. > > So please explain why would you reject an implementation of this which > requires XKB. It is a basic feature in that it can easily be implemented for systems without XKB. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 13:09 ` Po Lu @ 2024-11-04 13:33 ` Eli Zaretskii 0 siblings, 0 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-11-04 13:33 UTC (permalink / raw) To: Po Lu; +Cc: cpardo, emacs-devel > From: Po Lu <luangruo@yahoo.com> > Cc: cpardo@imayhem.com, emacs-devel@gnu.org > Date: Mon, 04 Nov 2024 21:09:56 +0800 > > Eli Zaretskii <eliz@gnu.org> writes: > > > This is not a basic feature, from where I stand. It's an enhancement. > > My evidence is that Emacs has lived for 4 decades without it. > > > > So please explain why would you reject an implementation of this which > > requires XKB. > > It is a basic feature in that it can easily be implemented for systems > without XKB. That's a completely different motivation. I agree that if a feature can be easily implemented without extensions, and thus work in more configurations, we should try implementing it without extensions. ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 0:21 ` Po Lu 2024-11-04 8:03 ` Cecilio Pardo 2024-11-04 12:27 ` Eli Zaretskii @ 2024-11-16 8:42 ` Cecilio Pardo 2024-11-17 0:05 ` Po Lu 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo 2024-11-18 20:38 ` Physical keyboard events Cecilio Pardo 4 siblings, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-11-16 8:42 UTC (permalink / raw) To: Po Lu; +Cc: emacs-devel On 04/11/2024 1:21, Po Lu wrote: > x_filter_events is not the proper location for this call. It is only > invoked to filter an event through the active GTK or X input method > context, and it is not invoked if Emacs is configured not to open input > method contexts, or they are unavailable by happenstance. > > This call is properly placed in handle_one_xevent, beneath the labels > for KeyPress, KeyRelease, XI_KeyPress, XI_KeyRelease, and the GTK > keyboard callbacks in gtkutil.c. Pay attention to the different manners > in which the key codes are converted into keysyms and the keysyms into > modifiers, and imitate them closely or reuse them if possible. In what configuration does the gtk callback get called? (xg_widget_key_press_event_cb in gtktutil.c). Not with this one: GNU Emacs 31.0.50 (build 33, x86_64-pc-linux-gnu, GTK+ Version 3.24.33, cairo version 1.16.0) of 2024-11-16 ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-16 8:42 ` Cecilio Pardo @ 2024-11-17 0:05 ` Po Lu 0 siblings, 0 replies; 93+ messages in thread From: Po Lu @ 2024-11-17 0:05 UTC (permalink / raw) To: Cecilio Pardo; +Cc: emacs-devel Cecilio Pardo <cpardo@imayhem.com> writes: > On 04/11/2024 1:21, Po Lu wrote: > >> x_filter_events is not the proper location for this call. It is only >> invoked to filter an event through the active GTK or X input method >> context, and it is not invoked if Emacs is configured not to open input >> method contexts, or they are unavailable by happenstance. >> This call is properly placed in handle_one_xevent, beneath the >> labels >> for KeyPress, KeyRelease, XI_KeyPress, XI_KeyRelease, and the GTK >> keyboard callbacks in gtkutil.c. Pay attention to the different manners >> in which the key codes are converted into keysyms and the keysyms into >> modifiers, and imitate them closely or reuse them if possible. > > In what configuration does the gtk callback get called? > (xg_widget_key_press_event_cb in gtktutil.c). Configurations where x-gtk-use-native-input is enabled. > Not with this one: > > GNU Emacs 31.0.50 (build 33, x86_64-pc-linux-gnu, GTK+ Version > 3.24.33, cairo version 1.16.0) of 2024-11-16 You'll need to enable that variable. ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-11-04 0:21 ` Po Lu ` (2 preceding siblings ...) 2024-11-16 8:42 ` Cecilio Pardo @ 2024-11-18 20:35 ` Cecilio Pardo 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors ` (2 more replies) 2024-11-18 20:38 ` Physical keyboard events Cecilio Pardo 4 siblings, 3 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-11-18 20:35 UTC (permalink / raw) To: 74423 [-- Attachment #1: Type: text/plain, Size: 1798 bytes --] Here is a still incomplete patch for this, renamed from "physical keyboard events", following up to https://lists.gnu.org/archive/html/emacs-devel/2024-11/msg00085.html It provides events for press/release of keys, independently of the normal keyboard events. These events are bound int the special-event-map. Some lisp is included to implement detection of multiple tapping on keys, and running commands or simulating modifiers. Currently, only modifier keys are available, because other keys would send their own normal event. This could change when an use case for other keys appears. To provide events for 'logical' modifiers Ctrl, Meta, etc., now there is a new event low-level-modifier, in addition to low-level-key. If you press Shift_L, two events will be generated, one for 'shift as low-level-modifier, and one for 'lshift as low-level-key. The lisp part handles those events separately. I added a variable that enables/disables everything, enable-low-level-key-events. This are some of Po Lu's notes on the previous patch: > [...] Emacs must not depend on XKB for such a basic feature. Fixed. > Please find some means of generating such events by saving them into the > keyboard event buffer defined in handle_ome_xevent. As we may generate two events, that is not possible. > x_filter_events is not the proper location for this call. It is only > invoked to filter an event through the active GTK or X input method > context, and it is not invoked if Emacs is configured not to open input > method contexts, or they are unavailable by happenstance. Done. If XINPUT is enabled, the XI_KeyPress event used, over the gtk one. If not, the gtk event is used unless there is no input method. In this case KeyPress event is used. The pgtk version is not updated. [-- Attachment #2: 0001-Add-events-for-key-press-and-key-release-on-gui-syst.patch --] [-- Type: text/plain, Size: 24075 bytes --] From d2403d9fa14b498263abb242324c21441a4246aa Mon Sep 17 00:00:00 2001 From: Cecilio Pardo <cpardo@imayhem.com> Date: Mon, 28 Oct 2024 23:57:35 +0100 Subject: [PATCH] Add events for key press and key release on gui systems. And detection of double/triple taps on modifier keys. * lisp/low-level-key.el (llk-tap-timeout): (llk-tap-count): (llk-tap-keys): (llk-bindings): (llk-modifier-bindings): (llk-init): (llk-bind): (llk-modifier-bind): (llk-events): (llk-modifier-events): (llm-handle): * src/gtkutil.c (xg_create_frame_widgets): (xg_maybe_send_low_level_key_event): (xg_widget_key_press_event_cb): (xg_widget_key_release_event_cb): * src/xterm.c (x_maybe_send_physical_key_event): (x_filter_event): (handle_one_xevent): (syms_of_xterm): --- lisp/low-level-key.el | 141 +++++++++++++++++++++++++++++++++++++++++ src/gtkutil.c | 133 ++++++++++++++++++++++++++++++++++++++ src/keyboard.c | 38 +++++++++++ src/pgtkterm.c | 37 +++++++++++ src/termhooks.h | 2 + src/w32fns.c | 11 ++++ src/w32term.c | 69 ++++++++++++++++++++ src/w32term.h | 3 +- src/xterm.c | 144 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 lisp/low-level-key.el diff --git a/lisp/low-level-key.el b/lisp/low-level-key.el new file mode 100644 index 00000000000..639c082da15 --- /dev/null +++ b/lisp/low-level-key.el @@ -0,0 +1,141 @@ +;;; -*- lexical-binding: t -*- +(require 'cl-lib) + +;; User options +(defvar llk-tap-timeout 1000) +(defvar llk-tap-count 2) +(defvar llk-tap-keys + '(lshift rshift lctrl rctrl lalt ralt shift ctrl alt)) +(defvar llk-bindings nil) +(defvar llm-bindings nil) + +(defun llk-init () + (interactive) + (define-key special-event-map [low-level-key] 'llk-handle) + (define-key special-event-map [low-level-modifier] 'llm-handle) + + (setq llk-bindings nil) + (setq llm-bindings nil) + + ;; (llm-bind 'tap 'shift 'delete-other-windows) + ;; (llk-bind 'tap 'lctrl 'hyper) + (setq enable-low-level-key-events t)) + +;; For example: +;; (llk-add-binding 'tap 'lshift 'delete-other-windows) +;; Can bind to a command, a function or the symbol 'hyper. +(defun llk-bind (action key function) + (push (list action key function) llk-bindings)) + +(defun llm-bind (action key function) + (push (list action key function) llm-bindings)) + +;; We store the last events here to test for multitap. +(defvar llk-events nil) +(defvar llm-events nil) + +;; If positive, return key ('lshift, etc) else return nil. +(defun llk-detect-n-tap (n timeout) + ;; The physical-key event is like this: + ;; (physical-key t lshift 90196265 #<frame>) + ;; The second element is t for a key press, nil for a key release + ;; The fourth element is the time in milliseconds + ;; The fifth is the frame, we don't use it yet. + + (let ((key (cl-third last-input-event))) + (if (not (member key llk-tap-keys)) + ;; Key not in tap list, clear history + (setq llk-events nil) + ;; Clear it also if the first element is from a different key + (and llk-events + (not (equal (cl-third (car llk-events)) key)) + (setq llk-events nil)) + (push last-input-event llk-events) + ;; Only care about last 2xN events + (ntake (* 2 n) llk-events) + ;; If we have: + ;; - Exactly 2 * n events. + ;; - down, up, down, up, ... + ;; - not two much time between first and last + (and (eq (* 2 n) (length llk-events)) + (cl-every 'eq + (ntake (* 2 n) + (list nil t nil t nil t nil t + nil t nil t nil t nil t)) + (mapcar 'cl-second llk-events)) + (< (- (cl-fourth (cl-first llk-events)) + (cl-fourth (car (last llk-events)))) + timeout) + (progn + (setq llk-events nil) + key))))) + + +;; this function is a copy of llk-detect-n-tap, but for llm-events +(defun llm-detect-n-tap (n timeout) + (let ((key (cl-third last-input-event))) + (if (not (member key llk-tap-keys)) + (setq llm-events nil) + (and llm-events + (not (equal (cl-third (car llm-events)) key)) + (setq llm-events nil)) + (push last-input-event llm-events) + (ntake (* 2 n) llm-events) + (and (eq (* 2 n) (length llm-events)) + (cl-every 'eq + (ntake (* 2 n) + (list nil t nil t nil t nil t + nil t nil t nil t nil t)) + (mapcar 'cl-second llm-events)) + (< (- (cl-fourth (cl-first llm-events)) + (cl-fourth (car (last llm-events)))) + timeout) + (progn + (setq llm-events nil) + key))))) + +(defun llk-handle () + (interactive) + + (let ((tap-key (llk-detect-n-tap + llk-tap-count + llk-tap-timeout))) + (when tap-key + (let ((func (cl-third + (seq-find + (lambda (b) + (and (eq (cl-first b) 'tap) + (eq (cl-second b) tap-key))) + llk-bindings)))) + (cond + ((commandp func) (call-interactively func)) + ((functionp func) (funcall func)) + ((eq 'hyper func) + (message "H-...") + (let ((r (read-event))) + (setq unread-command-events + (list (event-apply-modifier + r 'hyper 24 "H-")))))))))) + +(defun llm-handle() + (interactive) + + (let ((tap-key (llm-detect-n-tap + llk-tap-count + llk-tap-timeout))) + (when tap-key + (let ((func (cl-third + (seq-find + (lambda (b) + (and (eq (cl-first b) 'tap) + (eq (cl-second b) tap-key))) + llm-bindings)))) + (cond + ((commandp func) (call-interactively func)) + ((functionp func) (funcall func)) + ((eq 'hyper func) + (message "H-...") + (let ((r (read-event))) + (setq unread-command-events + (list (event-apply-modifier + r 'hyper 24 "H-")))))))))) diff --git a/src/gtkutil.c b/src/gtkutil.c index d57627f152f..6ca4e313929 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -98,6 +98,7 @@ G_DEFINE_TYPE (EmacsMenuBar, emacs_menu_bar, GTK_TYPE_MENU_BAR) static void xg_im_context_preedit_changed (GtkIMContext *, gpointer); static void xg_im_context_preedit_end (GtkIMContext *, gpointer); static bool xg_widget_key_press_event_cb (GtkWidget *, GdkEvent *, gpointer); +static bool xg_widget_key_release_event_cb (GtkWidget *, GdkEvent *, gpointer); #endif #if GTK_CHECK_VERSION (3, 10, 0) @@ -1749,6 +1750,12 @@ xg_create_frame_widgets (struct frame *f) g_signal_connect (G_OBJECT (wfixed), "key-press-event", G_CALLBACK (xg_widget_key_press_event_cb), NULL); + + g_signal_connect (G_OBJECT (wfixed), "key-release-event", + G_CALLBACK (xg_widget_key_release_event_cb), + NULL); + + #endif { @@ -6376,6 +6383,105 @@ xg_im_context_preedit_end (GtkIMContext *imc, gpointer user_data) kbd_buffer_store_event (&inev); } +static void +xg_maybe_send_low_level_key_event (struct frame *f, + GdkEvent *xev) +{ + GdkEventKey xkey = xev->key; + bool is_press; + int keysym; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (!Venable_low_level_key_events) + return; + switch (xev->type) + { + case GDK_KEY_PRESS: + is_press = true; + break; + case GDK_KEY_RELEASE: + is_press = false; + break; + default: + return; + } + + keysym = xkey.keyval; + + switch (keysym) + { + case GDK_KEY_Shift_L: key = Qlshift; break; + case GDK_KEY_Shift_R: key = Qrshift; break; + case GDK_KEY_Control_L: key = Qlctrl; break; + case GDK_KEY_Control_R: key = Qrctrl; break; + case GDK_KEY_Alt_L: key = Qlalt; break; + case GDK_KEY_Alt_R: key = Qralt; break; + default: + key = Qnil; + } + + switch (keysym) + { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + modifier = Qshift; + break; + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + modifier = Vx_ctrl_keysym; + if (NILP (modifier)) + modifier = Qctrl; + break; + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + modifier = Vx_meta_keysym; + if (NILP (modifier)) + modifier = Qalt; + break; + case GDK_KEY_Meta_L: + case GDK_KEY_Meta_R: + modifier = Vx_meta_keysym; + if (NILP (modifier)) + modifier = Qmeta; + break; + case GDK_KEY_Hyper_L: + case GDK_KEY_Hyper_R: + modifier = Vx_hyper_keysym; + if (NILP (modifier)) + modifier = Qhyper; + break; + case GDK_KEY_Super_L: + case GDK_KEY_Super_R: + modifier = Vx_super_keysym; + if (NILP (modifier)) + modifier = Qsuper; + break; + default: + modifier = Qnil; + } + + if (!NILP (key)) + { + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = xkey.time; + inev.ie.arg = list2 (is_press ? Qt : Qnil, key); + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); + } + + if (!NILP (modifier)) + { + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_MODIFIER_KEY_EVENT; + inev.ie.timestamp = xkey.time; + inev.ie.arg = list2 (is_press ? Qt : Qnil, modifier); + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); + } +} + static bool xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) @@ -6404,6 +6510,10 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, if (!f) return true; +#ifndef HAVE_XINPUT2 + xg_maybe_send_low_level_key_event (f, event); +#endif + if (popup_activated ()) return true; @@ -6557,6 +6667,29 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, return true; } +static bool +xg_widget_key_release_event_cb (GtkWidget *widget, GdkEvent *event, + gpointer user_data) +{ +#ifndef HAVE_XINPUT2 + Lisp_Object tail, tem; + struct frame *f = NULL; + + FOR_EACH_FRAME (tail, tem) + { + if (FRAME_X_P (XFRAME (tem)) + && (FRAME_GTK_WIDGET (XFRAME (tem)) == widget)) + { + f = XFRAME (tem); + break; + } + } + if (f) + xg_maybe_send_low_level_key_event (f, event); +#endif + return true; +} + bool xg_filter_key (struct frame *frame, XEvent *xkey) { diff --git a/src/keyboard.c b/src/keyboard.c index 6d28dca9aeb..532a1d5dbfe 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4274,6 +4274,8 @@ kbd_buffer_get_event (KBOARD **kbp, case CONFIG_CHANGED_EVENT: case FOCUS_OUT_EVENT: case SELECT_WINDOW_EVENT: + case LOW_LEVEL_KEY_EVENT: + case LOW_LEVEL_MODIFIER_KEY_EVENT: { obj = make_lispy_event (&event->ie); kbd_fetch_ptr = next_kbd_event (event); @@ -7118,6 +7120,22 @@ make_lispy_event (struct input_event *event) case PREEDIT_TEXT_EVENT: return list2 (Qpreedit_text, event->arg); + case LOW_LEVEL_KEY_EVENT: + return listn (5, + Qlow_level_key, + XCAR (event->arg), /* Press or release. */ + XCAR (XCDR (event->arg)), /* The key symbol. */ + make_fixnum (event->timestamp), + event->frame_or_window); + + case LOW_LEVEL_MODIFIER_KEY_EVENT: + return listn (5, + Qlow_level_modifier, + XCAR (event->arg), /* Press or release. */ + XCAR (XCDR (event->arg)), /* The key symbol. */ + make_fixnum (event->timestamp), + event->frame_or_window); + /* The 'kind' field of the event is something we don't recognize. */ default: emacs_abort (); @@ -12931,6 +12949,20 @@ syms_of_keyboard (void) DEFSYM (Qfile_notify, "file-notify"); #endif /* USE_FILE_NOTIFY */ + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, + doc: /* Enabled the recepcion of low level key events. +This includes 'low-level-key' and 'low-level-modifier' events. */); + Venable_low_level_key_events = false; + + DEFSYM (Qlow_level_key, "low-level-key"); + DEFSYM (Qlow_level_modifier, "low-level-modifier"); + DEFSYM (Qlshift, "lshift"); + DEFSYM (Qrshift, "rshift"); + DEFSYM (Qlctrl, "lctrl"); + DEFSYM (Qrctrl, "rctrl"); + DEFSYM (Qlalt, "lalt"); + DEFSYM (Qralt, "ralt"); + DEFSYM (Qtouch_end, "touch-end"); /* Menu and tool bar item parts. */ @@ -14018,6 +14050,12 @@ keys_of_keyboard (void) "handle-focus-out"); initial_define_lispy_key (Vspecial_event_map, "move-frame", "handle-move-frame"); + initial_define_lispy_key (Vspecial_event_map, "low-level-key", + "ignore"); + initial_define_lispy_key (Vspecial_event_map, "low-level-modifier", + "ignore"); + + } /* Mark the pointers in the kboard objects. diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 079945126e0..7091e9ef4c3 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -5201,6 +5201,39 @@ pgtk_enqueue_preedit (struct frame *f, Lisp_Object preedit) evq_enqueue (&inev); } +static void +pgtk_maybe_send_low_level_key_event (GdkEvent *event) +{ + if (!Venable_low_level_key_events) + return; + + Lisp_Object key; + switch (event->key.keyval) + { + case GDK_KEY_Shift_L: key = Qlshift; break; + case GDK_KEY_Shift_R: key = Qrshift; break; + case GDK_KEY_Control_L: key = Qlctrl; break; + case GDK_KEY_Control_R: key = Qrctrl; break; + case GDK_KEY_Alt_L: key = Qlalt; break; + case GDK_KEY_Alt_R: key = Qralt; break; + default: + return; + } + bool keypress = event->key.type == GDK_KEY_PRESS; + struct frame *f = pgtk_any_window_to_frame (event->key.window); + if (!f) + return; + + union buffered_input_event inev; + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = event->key.time; + inev.ie.arg = list2 (keypress ? Qt : Qnil, key); + evq_enqueue (&inev); +} + static gboolean key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { @@ -5210,6 +5243,8 @@ key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) struct frame *f; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_low_level_key_event(event); + f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); EVENT_INIT (inev.ie); hlinfo = MOUSE_HL_INFO (f); @@ -5454,6 +5489,8 @@ key_release_event (GtkWidget *widget, GdkDisplay *display; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_low_level_key_event(event); + display = gtk_widget_get_display (widget); dpyinfo = pgtk_display_info_for_display (display); diff --git a/src/termhooks.h b/src/termhooks.h index d6a9300bac9..006b74809a3 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -347,6 +347,8 @@ #define EMACS_TERMHOOKS_H /* In a NOTIFICATION_EVENT, .arg is a lambda to evaluate. */ , NOTIFICATION_EVENT #endif /* HAVE_ANDROID */ + , LOW_LEVEL_KEY_EVENT + , LOW_LEVEL_MODIFIER_KEY_EVENT }; /* Bit width of an enum event_kind tag at the start of structs and unions. */ diff --git a/src/w32fns.c b/src/w32fns.c index e2455b9271e..f1850a5f2f3 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -4669,6 +4669,11 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_KEYUP: case WM_SYSKEYUP: record_keyup (wParam, lParam); + if (Venable_low_level_key_events) + { + signal_user_input (); + my_post_msg( &wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam ); + } goto dflt; case WM_KEYDOWN: @@ -4695,6 +4700,12 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) if (w32_use_fallback_wm_chars_method) wParam = map_keypad_keys (wParam, (lParam & 0x1000000L) != 0); + if (Venable_low_level_key_events) + { + signal_user_input (); + my_post_msg( &wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam ); + } + windows_translate = 0; switch (wParam) diff --git a/src/w32term.c b/src/w32term.c index e18f39dd2a8..9fae9701daf 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -5270,6 +5270,75 @@ w32_read_socket (struct terminal *terminal, } break; + case WM_EMACS_LOW_LEVEL_KEY: + WORD key_flags = HIWORD (msg.msg.lParam); + BOOL is_wm_keyup = key_flags & KF_UP; + + if (is_wm_keyup || (key_flags & KF_REPEAT) == 0) /* WM_KEYDOWN, not repeating. */ + { + WORD scan_code = LOBYTE (key_flags); + if (key_flags & KF_EXTENDED) + scan_code = MAKEWORD (scan_code, 0xE0); + + UINT translated = MapVirtualKey (scan_code, MAPVK_VSC_TO_VK_EX); + WORD vk = LOWORD (msg.msg.wParam); + if (translated) + vk = LOWORD (translated); + + Lisp_Object key = Qnil; + Lisp_Object modifier = Qnil; + + switch (vk) + { + case VK_LSHIFT: key = Qlshift; break; + case VK_RSHIFT: key = Qrshift; break; + case VK_LCONTROL: key = Qlctrl; break; + case VK_RCONTROL: key = Qrctrl; break; + case VK_LMENU: key = Qlalt; break; + case VK_RMENU: key = Qralt; break; + } + + switch (vk) + { + case VK_LSHIFT: + case VK_RSHIFT: + modifier = Qshift; + break; + case VK_LCONTROL: + case VK_RCONTROL: + modifier = Qctrl; + break; + case VK_LMENU: + case VK_RMENU: + modifier = Qmeta; + break; + } + + if (!NILP (key)) + { + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + inev.kind = LOW_LEVEL_KEY_EVENT; + XSETFRAME (inev.frame_or_window, f); + inev.timestamp = msg.msg.time; + inev.arg = list2 (is_wm_keyup ? Qnil : Qt, key); + kbd_buffer_store_event_hold (&inev, hold_quit); + + } + + if (!NILP (modifier)) + { + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + inev.kind = LOW_LEVEL_MODIFIER_KEY_EVENT; + XSETFRAME (inev.frame_or_window, f); + inev.timestamp = msg.msg.time; + inev.arg = list2 (is_wm_keyup ? Qnil : Qt, modifier); + kbd_buffer_store_event_hold (&inev, hold_quit); + } + inev.kind = NO_EVENT; + + } + break; + case WM_UNICHAR: case WM_SYSCHAR: case WM_CHAR: diff --git a/src/w32term.h b/src/w32term.h index cad9fcf8cb1..88f7dfeef8b 100644 --- a/src/w32term.h +++ b/src/w32term.h @@ -713,7 +713,8 @@ #define WM_EMACS_FILENOTIFY (WM_EMACS_START + 25) #define WM_EMACS_IME_STATUS (WM_EMACS_START + 26) #define WM_EMACS_DRAGOVER (WM_EMACS_START + 27) #define WM_EMACS_DROP (WM_EMACS_START + 28) -#define WM_EMACS_END (WM_EMACS_START + 29) +#define WM_EMACS_LOW_LEVEL_KEY (WM_EMACS_START + 29) +#define WM_EMACS_END (WM_EMACS_START + 30) #define WND_FONTWIDTH_INDEX (0) #define WND_LINEHEIGHT_INDEX (4) diff --git a/src/xterm.c b/src/xterm.c index 0c20d38b0f7..97028c32336 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -17840,6 +17840,143 @@ #define STORE_KEYSYM_FOR_DEBUG(keysym) ((void)0) static struct x_display_info *next_noop_dpyinfo; +static void +x_maybe_send_low_level_key_event (struct x_display_info *dpyinfo, + const XEvent *xev) +{ + XKeyEvent xkey; + bool is_press; + KeySym keysym; + Lisp_Object key, modifier; + struct input_event ie; + + if (!Venable_low_level_key_events) + return; + + switch (xev->type) + { + case KeyPress: + is_press = true; + xkey = xev->xkey; + break; + case KeyRelease: + is_press = false; + xkey = xev->xkey; + break; +#ifdef HAVE_XINPUT2 + case GenericEvent: + XIDeviceEvent *xiev = xev->xcookie.data; + switch (xev->xgeneric.evtype) + { + case XI_KeyPress: + is_press = true; + break; + case XI_KeyRelease: + is_press = false; + break; + default: + return; + } + + xkey.serial = xiev->serial; + xkey.send_event = xiev->send_event; + xkey.display = xiev->display; + xkey.window = xiev->event; + xkey.root = xiev->root; + xkey.subwindow = xiev->child; + xkey.time = xiev->time; + xkey.x = xiev->event_x; + xkey.y = xiev->event_y; + xkey.x_root = xiev->root_x; + xkey.y_root = xiev->root_y; + xkey.state = xiev->mods.effective; + xkey.keycode = xiev->detail; + xkey.same_screen = 1; + break; +#endif + default: + return; + } + + struct frame *f = x_any_window_to_frame (dpyinfo, xkey.window); + if (!f) + return; + + XLookupString (&xkey, NULL, 0, &keysym, NULL); + + switch (keysym) + { + case XK_Shift_L: key = Qlshift; break; + case XK_Shift_R: key = Qrshift; break; + case XK_Control_L: key = Qlctrl; break; + case XK_Control_R: key = Qrctrl; break; + case XK_Alt_L: key = Qlalt; break; + case XK_Alt_R: key = Qralt; break; + default: + key = Qnil; + } + + switch (keysym) + { + case XK_Shift_L: + case XK_Shift_R: + modifier = Qshift; + break; + case XK_Control_L: + case XK_Control_R: + modifier = Vx_ctrl_keysym; + if (NILP (modifier)) + modifier = Qctrl; + break; + case XK_Alt_L: + case XK_Alt_R: + modifier = Vx_meta_keysym; + if (NILP (modifier)) + modifier = Qalt; + break; + case XK_Meta_L: + case XK_Meta_R: + modifier = Vx_meta_keysym; + if (NILP (modifier)) + modifier = Qmeta; + break; + case XK_Hyper_L: + case XK_Hyper_R: + modifier = Vx_hyper_keysym; + if (NILP (modifier)) + modifier = Qhyper; + break; + case XK_Super_L: + case XK_Super_R: + modifier = Vx_super_keysym; + if (NILP (modifier)) + modifier = Qsuper; + break; + default: + modifier = Qnil; + } + + if (!NILP (key)) + { + EVENT_INIT (ie); + XSETFRAME (ie.frame_or_window, f); + ie.kind = LOW_LEVEL_KEY_EVENT; + ie.timestamp = xkey.time; + ie.arg = list2 (is_press ? Qt : Qnil, key); + kbd_buffer_store_event (&ie); + } + + if (!NILP (modifier)) + { + EVENT_INIT (ie); + XSETFRAME (ie.frame_or_window, f); + ie.kind = LOW_LEVEL_MODIFIER_KEY_EVENT; + ie.timestamp = xkey.time; + ie.arg = list2 (is_press ? Qt : Qnil, key); + kbd_buffer_store_event (&ie); + } +} + /* Filter events for the current X input method. DPYINFO is the display this event is for. EVENT is the X event to filter. @@ -20206,6 +20343,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case KeyPress: + x_maybe_send_low_level_key_event (dpyinfo, event); x_display_set_last_user_time (dpyinfo, event->xkey.time, event->xkey.send_event, true); @@ -20715,6 +20853,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event); #ifdef HAVE_X_I18N /* Don't dispatch this event since XtDispatchEvent calls XFilterEvent, and two calls in a row may freeze the @@ -23970,6 +24109,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, struct xi_device_t *device, *source; XKeyPressedEvent xkey; + x_maybe_send_low_level_key_event (dpyinfo, event); + coding = Qlatin_1; /* The code under this label is quite desultory. There @@ -24586,6 +24727,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case XI_KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event); + #if defined HAVE_X_I18N || defined USE_GTK || defined USE_LUCID { XKeyPressedEvent xkey; @@ -32662,6 +32805,7 @@ syms_of_xterm (void) Vx_toolkit_scroll_bars = Qnil; #endif + DEFSYM (Qshift, "shift"); DEFSYM (Qmodifier_value, "modifier-value"); DEFSYM (Qctrl, "ctrl"); Fput (Qctrl, Qmodifier_value, make_fixnum (ctrl_modifier)); -- 2.35.1.windows.2 ^ permalink raw reply related [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo @ 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-23 12:08 ` Cecilio Pardo 2024-11-19 15:29 ` Eli Zaretskii 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2 siblings, 1 reply; 93+ messages in thread From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-18 23:49 UTC (permalink / raw) To: Cecilio Pardo; +Cc: 74423 Cecilio Pardo <cpardo@imayhem.com> writes: > + switch (keysym) > + { > + case GDK_KEY_Shift_L: > + case GDK_KEY_Shift_R: > + modifier = Qshift; > + break; > + case GDK_KEY_Control_L: > + case GDK_KEY_Control_R: > + modifier = Vx_ctrl_keysym; > + if (NILP (modifier)) > + modifier = Qctrl; > + break; > + case GDK_KEY_Alt_L: > + case GDK_KEY_Alt_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qalt; > + break; > + case GDK_KEY_Meta_L: > + case GDK_KEY_Meta_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qmeta; > + break; > + case GDK_KEY_Hyper_L: > + case GDK_KEY_Hyper_R: > + modifier = Vx_hyper_keysym; > + if (NILP (modifier)) > + modifier = Qhyper; > + break; > + case GDK_KEY_Super_L: > + case GDK_KEY_Super_R: > + modifier = Vx_super_keysym; > + if (NILP (modifier)) > + modifier = Qsuper; > + break; > + default: > + modifier = Qnil; > + } This is not the proper means of establishing the modifier bound to a key symbol in GTK, but I cannot immediately tell you what the alternative is. > + my_post_msg( &wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam ); > + } > + my_post_msg( &wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam ); > + } Stylistic issues. > +static void > +x_maybe_send_low_level_key_event (struct x_display_info *dpyinfo, > + const XEvent *xev) > +{ > + XKeyEvent xkey; > + bool is_press; > + KeySym keysym; > + Lisp_Object key, modifier; > + struct input_event ie; > + > + if (!Venable_low_level_key_events) > + return; > + > + switch (xev->type) > + { > + case KeyPress: > + is_press = true; > + xkey = xev->xkey; > + break; > + case KeyRelease: > + is_press = false; > + xkey = xev->xkey; > + break; > +#ifdef HAVE_XINPUT2 > + case GenericEvent: > + XIDeviceEvent *xiev = xev->xcookie.data; > + switch (xev->xgeneric.evtype) > + { > + case XI_KeyPress: > + is_press = true; > + break; > + case XI_KeyRelease: > + is_press = false; > + break; > + default: > + return; > + } > + > + xkey.serial = xiev->serial; > + xkey.send_event = xiev->send_event; > + xkey.display = xiev->display; > + xkey.window = xiev->event; > + xkey.root = xiev->root; > + xkey.subwindow = xiev->child; > + xkey.time = xiev->time; > + xkey.x = xiev->event_x; > + xkey.y = xiev->event_y; > + xkey.x_root = xiev->root_x; > + xkey.y_root = xiev->root_y; > + xkey.state = xiev->mods.effective; > + xkey.keycode = xiev->detail; > + xkey.same_screen = 1; > + break; > +#endif > + default: > + return; > + } > + > + struct frame *f = x_any_window_to_frame (dpyinfo, xkey.window); > + if (!f) > + return; > + > + XLookupString (&xkey, NULL, 0, &keysym, NULL); > + > + switch (keysym) > + { > + case XK_Shift_L: key = Qlshift; break; > + case XK_Shift_R: key = Qrshift; break; > + case XK_Control_L: key = Qlctrl; break; > + case XK_Control_R: key = Qrctrl; break; > + case XK_Alt_L: key = Qlalt; break; > + case XK_Alt_R: key = Qralt; break; > + default: > + key = Qnil; > + } This doesn't generate `ralt' events on my system, as that key produces the keysym ISO_Level3_Shift/ > + switch (keysym) > + { > + case XK_Shift_L: > + case XK_Shift_R: > + modifier = Qshift; > + break; > + case XK_Control_L: > + case XK_Control_R: > + modifier = Vx_ctrl_keysym; > + if (NILP (modifier)) > + modifier = Qctrl; > + break; > + case XK_Alt_L: > + case XK_Alt_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qalt; > + break; > + case XK_Meta_L: > + case XK_Meta_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qmeta; > + break; > + case XK_Hyper_L: > + case XK_Hyper_R: > + modifier = Vx_hyper_keysym; > + if (NILP (modifier)) > + modifier = Qhyper; > + break; > + case XK_Super_L: > + case XK_Super_R: > + modifier = Vx_super_keysym; > + if (NILP (modifier)) > + modifier = Qsuper; > + break; > + default: > + modifier = Qnil; > + } You are computing the modifier event to be generated incorrectly. You should search the modifier map (dpyinfo->modmap) for columns containing received keysyms, and compare modifier bits derived from the rows where they appear against meta_mod_mask, shift_lock_mask, alt_mod_mask, super_mod_mask, and hyper_mod_mask in the display structure, or CtrlMask and ShiftMask. > case KeyPress: > + x_maybe_send_low_level_key_event (dpyinfo, event); > x_display_set_last_user_time (dpyinfo, event->xkey.time, > event->xkey.send_event, > true); > @@ -20715,6 +20853,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, > #endif > > case KeyRelease: > + x_maybe_send_low_level_key_event (dpyinfo, event); > #ifdef HAVE_X_I18N > /* Don't dispatch this event since XtDispatchEvent calls > XFilterEvent, and two calls in a row may freeze the > @@ -23970,6 +24109,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, > struct xi_device_t *device, *source; > XKeyPressedEvent xkey; > > + x_maybe_send_low_level_key_event (dpyinfo, event); > + > coding = Qlatin_1; > > /* The code under this label is quite desultory. There > @@ -24586,6 +24727,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, > #endif > > case XI_KeyRelease: > + x_maybe_send_low_level_key_event (dpyinfo, event); Would you rearrange the locations of calls to x_any_window_to_frame in handle_one_xevent so that you need not redundantly call the same function in x_maybe_send_low_level_key_event? Thanks for your interest in Emacs. ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-23 12:08 ` Cecilio Pardo 0 siblings, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-11-23 12:08 UTC (permalink / raw) To: Po Lu; +Cc: 74423 On 19/11/2024 0:49, Po Lu wrote: > You are computing the modifier event to be generated incorrectly. You > should search the modifier map (dpyinfo->modmap) for columns containing > received keysyms, and compare modifier bits derived from the rows where > they appear against meta_mod_mask, shift_lock_mask, alt_mod_mask, > super_mod_mask, and hyper_mod_mask in the display structure, or CtrlMask > and ShiftMask. Thanks for the guidance. Does this look ok? Lisp_Object x_get_modifier_for_keycode (struct x_display_info *dpyinfo, int keycode) { #ifdef HAVE_XKB if (dpyinfo->xkb_desc) for (int mod = 0; mod < XkbNumModifiers; mod++) { int mask = (1 << mod); if (dpyinfo->xkb_desc->map->modmap[keycode] & mask) { if (mask == ShiftMask) return Qshift; if (mask == ControlMask) return Qctrl; if (mask == dpyinfo->meta_mod_mask) return Qmeta; if (mask == dpyinfo->alt_mod_mask) return Qalt; if (mask == dpyinfo->super_mod_mask) return Qsuper; if (mask == dpyinfo->hyper_mod_mask) return Qhyper; } } #endif XModifierKeymap *map = dpyinfo->modmap; if (map) for (int mod = 0; mod < 8; mod++) { int mask = (1 << mod); for (int key = 0; key < map->max_keypermod; key++) if (map->modifiermap[mod * map->max_keypermod + key] == keycode) { if (mask == ShiftMask) return Qshift; if (mask == ControlMask) return Qctrl; if (mask == dpyinfo->meta_mod_mask) return Qmeta; if (mask == dpyinfo->alt_mod_mask) return Qalt; if (mask == dpyinfo->super_mod_mask) return Qsuper; if (mask == dpyinfo->hyper_mod_mask) return Qhyper; } } return Qnil; } ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-19 15:29 ` Eli Zaretskii 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2 siblings, 0 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-11-19 15:29 UTC (permalink / raw) To: Cecilio Pardo, Stefan Monnier; +Cc: 74423 > Date: Mon, 18 Nov 2024 21:35:40 +0100 > From: Cecilio Pardo <cpardo@imayhem.com> > > Here is a still incomplete patch for this, renamed from "physical > keyboard events", following up to > https://lists.gnu.org/archive/html/emacs-devel/2024-11/msg00085.html > > It provides events for press/release of keys, independently of the > normal keyboard events. These events are bound int the > special-event-map. Some lisp is included to implement detection of > multiple tapping on keys, and running commands or simulating modifiers. Thanks, I have some minor comments, and also added Stefan to the discussion, in case he will also have comments. > + if (!Venable_low_level_key_events) You treat this variable as a C boolean, i.e. assume it was defined with DEFVAR_BOOL, but in that case our convention is to call the C variables without the leading 'V'. OTOH, if you envision that this variable could be something other than nil or t in Lisp, then you need to use NILP here. > + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, > + doc: /* Enabled the recepcion of low level key events. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ "If non-nil, reception of low-level key events is enabled." > +static void > +pgtk_maybe_send_low_level_key_event (GdkEvent *event) > +{ > + if (!Venable_low_level_key_events) > + return; Same problem with the test here. > --- a/src/w32fns.c > +++ b/src/w32fns.c > @@ -4669,6 +4669,11 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) > case WM_KEYUP: > case WM_SYSKEYUP: > record_keyup (wParam, lParam); > + if (Venable_low_level_key_events) > + { > + signal_user_input (); > + my_post_msg( &wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam ); > + } And here (and in several other places in the patch). ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-19 15:29 ` Eli Zaretskii @ 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-19 20:05 ` Cecilio Pardo 2 siblings, 1 reply; 93+ messages in thread From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-19 16:43 UTC (permalink / raw) To: Cecilio Pardo; +Cc: 74423 > It provides events for press/release of keys, independently of the normal > keyboard events. These events are bound int the special-event-map. Some lisp > is included to implement detection of multiple tapping on keys, and running > commands or simulating modifiers. I hadn't followed the discussion over at emacs-devel, but this is cool. I used to have a local patch which generated extra events for all key presses/releases. I never wrote any useful Lisp-level code for it (all I had were bindings to ignore those events so Emacs was still usable 🙂), but I really think it would make for fun new hacks. I haven't had time to look at your whole patch, but here are some comments. > + (let ((key (cl-third last-input-event))) Please try and use `event-*` functions. If none serve (as will often be the case), define local "replacements" until they can be promoted to `subr.el`. > + ((functionp func) (funcall func)) > + ((eq 'hyper func) > + (message "H-...") > + (let ((r (read-event))) > + (setq unread-command-events > + (list (event-apply-modifier > + r 'hyper 24 "H-")))))))))) Move that code to a function, so you can get rid of this `hyper` special case. BTW, any reason why you couldn't use `event-apply-hyper-modifier`? [ BTW, this becomes more ... interesting ... when you want to be able to cumulative that for several modifiers, in which case your `read-event` might return an event which is not the one to which you want to add `H-`. ] > + switch (keysym) > + { > + case GDK_KEY_Shift_L: key = Qlshift; break; > + case GDK_KEY_Shift_R: key = Qrshift; break; > + case GDK_KEY_Control_L: key = Qlctrl; break; > + case GDK_KEY_Control_R: key = Qrctrl; break; > + case GDK_KEY_Alt_L: key = Qlalt; break; > + case GDK_KEY_Alt_R: key = Qralt; break; > + default: > + key = Qnil; > + } > + > + switch (keysym) > + { > + case GDK_KEY_Shift_L: > + case GDK_KEY_Shift_R: > + modifier = Qshift; > + break; > + case GDK_KEY_Control_L: > + case GDK_KEY_Control_R: > + modifier = Vx_ctrl_keysym; > + if (NILP (modifier)) > + modifier = Qctrl; > + break; > + case GDK_KEY_Alt_L: > + case GDK_KEY_Alt_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qalt; > + break; > + case GDK_KEY_Meta_L: > + case GDK_KEY_Meta_R: > + modifier = Vx_meta_keysym; > + if (NILP (modifier)) > + modifier = Qmeta; > + break; > + case GDK_KEY_Hyper_L: > + case GDK_KEY_Hyper_R: > + modifier = Vx_hyper_keysym; > + if (NILP (modifier)) > + modifier = Qhyper; > + break; > + case GDK_KEY_Super_L: > + case GDK_KEY_Super_R: > + modifier = Vx_super_keysym; > + if (NILP (modifier)) > + modifier = Qsuper; > + break; > + default: > + modifier = Qnil; > + } I think the list of low-level keys handled here should not be hard-coded. IOW, maybe `enable-low-level-keys` should not be a boolean but a list/map/table indicating which keys to handle. > + if (!NILP (key)) > + { > + EVENT_INIT (inev.ie); > + XSETFRAME (inev.ie.frame_or_window, f); > + inev.ie.kind = LOW_LEVEL_KEY_EVENT; > + inev.ie.timestamp = xkey.time; > + inev.ie.arg = list2 (is_press ? Qt : Qnil, key); > + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); > + } > + > + if (!NILP (modifier)) > + { > + EVENT_INIT (inev.ie); > + XSETFRAME (inev.ie.frame_or_window, f); > + inev.ie.kind = LOW_LEVEL_MODIFIER_KEY_EVENT; > + inev.ie.timestamp = xkey.time; > + inev.ie.arg = list2 (is_press ? Qt : Qnil, modifier); > + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); > + } > +} So, IIUC you might generate 2 low-level events for a single key press? Why? Other note: in the distant past (back around Emacs-21) I seem to remember Gerd making changes to the event structure so as to avoid allocating Lisp objects for this code. I think it was related to problems due to running this low-level event-handler code from within a C signal handler, which is a practice was have since stopped, luckily, but maybe there are still good reasons to try and avoid involving allocating objects into the Lisp heap in this low-level code. Stefan ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-19 20:05 ` Cecilio Pardo 2024-11-20 4:21 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 0 siblings, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-11-19 20:05 UTC (permalink / raw) To: Stefan Monnier; +Cc: 74423 On 19/11/2024 17:43, Stefan Monnier wrote: >> + ((functionp func) (funcall func)) >> + ((eq 'hyper func) >> + (message "H-...") >> + (let ((r (read-event))) >> + (setq unread-command-events >> + (list (event-apply-modifier >> + r 'hyper 24 "H-")))))))))) > > Move that code to a function, so you can get rid of this `hyper` > special case. BTW, any reason why you couldn't use > `event-apply-hyper-modifier`? No, no reason. I will change that. > I think the list of low-level keys handled here should not be hard-coded. > IOW, maybe `enable-low-level-keys` should not be a boolean but > a list/map/table indicating which keys to handle. I was sending the keys for which I have an immediate use case. Using a list looks got, adding some special symbols to choose groups, such as "all modifier keys". > So, IIUC you might generate 2 low-level events for a single key press? > Why? We want to allow to detect modifier keys, regardless of the key that is used. For example, when you press Shift_L we generate an event for 'lshift and other (of different type) for the modifier 'shift. > Other note: in the distant past (back around Emacs-21) I seem to > remember Gerd making changes to the event structure so as to avoid > allocating Lisp objects for this code. I think it was related to > problems due to running this low-level event-handler code from within > a C signal handler, which is a practice was have since stopped, luckily, > but maybe there are still good reasons to try and avoid involving > allocating objects into the Lisp heap in this low-level code. I will look into this. Thanks for your comments! ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-11-19 20:05 ` Cecilio Pardo @ 2024-11-20 4:21 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-02 16:54 ` Cecilio Pardo 0 siblings, 1 reply; 93+ messages in thread From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-11-20 4:21 UTC (permalink / raw) To: Cecilio Pardo; +Cc: 74423 >> Move that code to a function, so you can get rid of this `hyper` >> special case. BTW, any reason why you couldn't use >> `event-apply-hyper-modifier`? > No, no reason. I will change that. To be clear: I don't know that it works here. >> I think the list of low-level keys handled here should not be hard-coded. >> IOW, maybe `enable-low-level-keys` should not be a boolean but >> a list/map/table indicating which keys to handle. > I was sending the keys for which I have an immediate use case. Using a list > looks got, adding some special symbols to choose groups, such as "all > modifier keys". I was also thinking that maybe the list/table could be used to avoid having to define in the C code what is a "modifier key" and things like that. >> So, IIUC you might generate 2 low-level events for a single key press? >> Why? > We want to allow to detect modifier keys, regardless of the key that is > used. For example, when you press Shift_L we generate an event for 'lshift > and other (of different type) for the modifier 'shift. But my question is about the fact that you generate two events, not about the two pieces of information. IOW you could also generate a single event with both pieces of information (and if use the "normal" keymaps instead of `special-event-map` then you might even have a `function-key-map` rule which converts a `Shift_L` down to a `shift` if there was no binding for the `Shift_L` event). Stefan ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-11-20 4:21 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-02 16:54 ` Cecilio Pardo 2024-12-04 20:01 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-12-02 16:54 UTC (permalink / raw) To: Stefan Monnier, Eli Zaretskii, Po Lu; +Cc: 74423 [-- Attachment #1: Type: text/plain, Size: 1643 bytes --] Here is a new version of the patch. Sorry it took so long. It addresses most of the notes I got: Notes by Po Lu: - I think now the associated modifier for a key is computed properly, with or without XKB for plain X, and for gtk. - 'ralt' not generated for ISO_Level3_Shift. This will have to stay like this. What we know about the keys if what the system lets us. - Don't call x_any_window_to_frame if already called for the event. Notes by Eli: - Change testing for Venable_low_level_key_events. This variable now is more complex. Notes by Stefan: - Created and used event-* functions to access event. - Only one event with both pieces of information. - Selection of keys. Now the variable enable-low-level-key-events can be used to select exactly which keys we want to process. - About creating lisp object in handle_one_xevent, it is done in other events too. The lisp interface is as follows: - A list of variables with keysym values is initialized (xk-*), so the user can select keys, not just modifiers. - Users can bind globally commands or functions with llk-bind to taps. - To use a tapped key as a new modifier, this works: (llk-bind 'tap 'xk-shift-r (lambda () (message "H-...") (setq unread-command-events (append (event-apply-hyper-modifier nil) nil)))) - Added the function describe-low-level-key to see what keysym/modifier it triggers. This is implemented for X, pgtkw and MS-Windows. The pgtj implementation detects that a key is a modifier to filter it, but can't decide which one it its. [-- Attachment #2: 0001-Send-event-for-key-presses-and-key-releases.patch --] [-- Type: text/plain, Size: 36568 bytes --] From 863068f4e1e187ba33382c0debb8caee5bbe5cbe Mon Sep 17 00:00:00 2001 From: Cecilio Pardo <cpardo@imayhem.com> Date: Mon, 2 Dec 2024 17:30:42 +0100 Subject: [PATCH] Send event for key presses and key releases. Detect double/tripe taps. * src/gtkutil.c (xg_create_frame_widgets): Modified to handle key release events. (xg_maybe_send_low_level_key_event): New function that sends key events. (xg_widget_key_press_event_cb): Modified to send low level key events. (xg_widget_key_release_event_cb):New function to send low level key events on key release. * src/keyboard.c (kbd_buffer_get_event): Modified to handle low level keyboard events. (make_lispy_event): Modified to handle low level keyboard events. (kbd_low_level_key_is_enabled): New function to decice if a particular key should generate events, looking at configuration, (syms_of_keyboard): Added symbols and varialbles. (keys_of_keyboard): Modified to map low level key events to 'special-event-map'. * src/keyboard.h: Modified with function prototype. * src/pgtkterm.c (pgtk_maybe_send_low_level_key_event): New function that sends key events. (key_press_event): Modified to handle low level keyboard events. (key_release_event): Modified to handle low level keyboard events. * src/termhooks.h: Modified to define constant. * src/w32fns.c (w32_wnd_proc): Modified to generate low level key events. * src/w32term.c (w32_read_socket): Modified to generate low level key events. * src/w32term.h (WM_EMACS_IME_STATUS): Modified to define constants. * src/xterm.c (x_get_modifier_for_keycode): New function to decide which (if any) modifier corresponds to a given key. (x_maybe_send_low_level_key_event): New function that sends key events. (handle_one_xevent): Modified to handle low level key events. (syms_of_xterm): Modified to definde symbol. * src/xterm.h: Modified with function prototype * lisp/low-level-key.el (New file). (llk-bindings): User bindings for low level key events. (llk-tap-count): User option. (llk-tap-timeout): User option. (llk-tap-keys): User option. (llk-keysyms): List of available keysyms. (define-xk): Macro for defining keysyms. (llk-define-keysyms): Build llk-keysyms. (llk-init): Function to initialize low level key handling. (event-is-key-press): Get field from event. (event-keysym): Get field from event. (event-modifier): Get field from event. (event-time): Get field from event. (llk-bind): Function to create a binding. (llk-events): Event history for tap detection. (llk-detect-n-tap): Function to detect taps. (describe-low-level-key): Command to get information about a key. (llk-show-event-description): Show help buffer with information about an event. (llk-handle): Handler for key events. --- lisp/low-level-key.el | 335 ++++++++++++++++++++++++++++++++++++++++++ src/gtkutil.c | 81 ++++++++++ src/keyboard.c | 61 ++++++++ src/keyboard.h | 1 + src/pgtkterm.c | 55 +++++++ src/termhooks.h | 1 + src/w32fns.c | 11 ++ src/w32term.c | 49 ++++++ src/w32term.h | 3 +- src/xterm.c | 142 ++++++++++++++++++ src/xterm.h | 2 + 11 files changed, 740 insertions(+), 1 deletion(-) create mode 100644 lisp/low-level-key.el diff --git a/lisp/low-level-key.el b/lisp/low-level-key.el new file mode 100644 index 00000000000..85cdaaaf0f4 --- /dev/null +++ b/lisp/low-level-key.el @@ -0,0 +1,335 @@ +;;; -*- lexical-binding: t -*- + +;; The physical-key event is like this: +;; (physical-key IS-KEY-PRESS KEY MODIFIER TIME FRAME) +;; IS-KEY-PRESS is t if the key has been pressed, nil if it has been released. +;; KEY is the keysym number. +;; MODIFIER is the modifier associated with this key. It is nil if the key is +;; not a modifier. It can be one of the following symbols: shift, control, meta, +;; super, hyper, alt. It can also be t if the key is a modifier but it can't be +;; identified. +;; TIME is the timestamp in milliseconds of the event. +;; FRAME is the frame where the event happened. +;; +;; After calling 'llk-init' and setting a non-nil value for +;; 'enable-low-level-key-events', events begin to be handled by 'llk-handler', +;; which tries to detect n-taps and calls the corresponding function. + +(require 'cl-lib) + +;; User options + +(defvar llk-bindings nil + "Bindings for low level key events (press/release/tap). +Use the `llk-bind' function to add bindings. See its documentation for +a description of the binding information.") + +(defvar llk-tap-count 2 + "Number or key press/releases to consider a tap.") + +(defvar llk-tap-timeout 1000 + "Time in milliseconds between consecutive key presses/releases to +consider a tap.") + +(defvar llk-tap-keys + '(xk-shift-l xk-shift-r xk-control-l xk-control-r meta) + "Keys that can generate taps.") + +(defvar llk-keysyms nil + "List of keysym numbers and their corresponding symbols. +Each element has the form (KEYSYM . SYMBOL). The variable value for +each symbol is the keysym. This list is initialized by `llk-init'.") + +(defvar llk-describe-next-press nil + "Internal variable to mark that next key press should be described.") + +(defmacro define-xk (name x-keysym w32-keysym) + "Internal macro to define keysyms." + `(let ((ksym (pcase (window-system) + ('pgtk ,x-keysym) + ('x ,x-keysym) + ('w32 ,w32-keysym)))) + (defconst ,name ksym "Constant for a keysym value.") + (push (cons ksym ',name) llk-keysyms))) + +(defun llk-define-keysyms () + "Initialize the keysym list, `llk-keysyms'. Called from `llk-init'." + (setq llk-keysyms nil) + + ;; tty keys + (define-xk xk-backspace #xff08 #x08) ;; XK_BackSpace VK_BACK + (define-xk xk-tab #xff09 #x09) ;; XK_Tab VK_TAB + (define-xk xk-clear #xff0b #x0C) ;; XK_Clear VK_CLEAR + (define-xk xk-return #xff0d #x0D) ;; XK_Return VK_RETURN + (define-xk xk-pause #xff13 #x13) ;; XK_Pause VK_PAUSE + (define-xk xk-scroll-lock #xff14 #x91) ;; XK_Scroll_Lock VK_SCROLL + (define-xk xk-escape #xff1B #x1B) ;; XK_Escape VK_ESCAPE + (define-xk xk-delete #xffff #x2E) ;; XK_Delete VK_DELETE + + ;; Cursor control and motion + (define-xk xk-home #xff50 #x24) ;; XK_Home VK_HOME + (define-xk xk-left #xff51 #x25) ;; XK_Left VK_LEFT + (define-xk xk-up #xff52 #x26) ;; XK_Up VK_UP + (define-xk xk-right #xff53 #x27) ;; XK_Right VK_RIGHT + (define-xk xk-down #xff54 #x28) ;; XK_Down VK_DOWN + (define-xk xk-page-up #xff55 #x21) ;; XK_Page_Up VK_PRIOR + (define-xk xk-page-down #xff56 #x22) ;; XK_Page_Down VK_NEXT + (define-xk xk-end #xff57 #x23) ;; XK_End VK_END + (define-xk xk-begin #xff58 #x24) ;; XK_Begin VK_HOME + + ;; Special Windows keyboard keys + (define-xk xk-win-l #xFF5B #x5B) ;; XK_Win_L VK_LWIN + (define-xk xk-win-r #xFF5C #x5C) ;; XK_Win_R VK_RWIN + (define-xk xk-app #xFF5D #x5D) ;; XK_App VK_APPS + + ;; Misc functions + (define-xk xk-select #xff60 #x29) ;; XK_Select VK_SELECT + (define-xk xk-print #xff61 #x2A) ;; XK_Print VK_PRINT + (define-xk xk-insert #xff64 #x2D) ;; XK_Insert VK_INSERT + (define-xk xk-num-lock #xff7f #x90) ;; XK_Num_Lock VK_NUMLOCK + + ;; Keypad + ;; TODO: Check values for MS-Windows + (define-xk xk-kp-enter #xff8d nil) ;; XK_KP_Enter ??? + (define-xk xk-kp-multiply #xffaa nil) ;; XK_KP_Multiply ??? + (define-xk xk-kp-add #xffab nil) ;; XK_KP_Add ??? + (define-xk xk-kp-subtract #xffad nil) ;; XK_KP_Subtract ??? + (define-xk xk-kp-decimal #xffae nil) ;; XK_KP_Decimal ??? + (define-xk xk-kp-divide #xffaf nil) ;; XK_KP_Divide ??? + (define-xk xk-kp-0 #xffb0 #x60) ;; XK_KP_0 VK_NUMPAD0 + (define-xk xk-kp-1 #xffb1 #x61) ;; XK_KP_1 VK_NUMPAD1 + (define-xk xk-kp-2 #xffb2 #x62) ;; XK_KP_2 VK_NUMPAD2 + (define-xk xk-kp-3 #xffb3 #x63) ;; XK_KP_3 VK_NUMPAD3 + (define-xk xk-kp-4 #xffb4 #x64) ;; XK_KP_4 VK_NUMPAD4 + (define-xk xk-kp-5 #xffb5 #x65) ;; XK_KP_5 VK_NUMPAD5 + (define-xk xk-kp-6 #xffb6 #x66) ;; XK_KP_6 VK_NUMPAD6 + (define-xk xk-kp-7 #xffb7 #x67) ;; XK_KP_7 VK_NUMPAD7 + (define-xk xk-kp-8 #xffb8 #x68) ;; XK_KP_8 VK_NUMPAD8 + (define-xk xk-kp-9 #xffb9 #x69) ;; XK_KP_9 VK_NUMPAD9 + + ;; Function keys + (define-xk xk-f1 #xffbe #x70) ;; XK_F1 VK_F1 + (define-xk xk-f2 #xffbf #x71) ;; XK_F2 VK_F2 + (define-xk xk-f3 #xffc0 #x72) ;; XK_F3 VK_F3 + (define-xk xk-f4 #xffc1 #x73) ;; XK_F4 VK_F4 + (define-xk xk-f5 #xffc2 #x74) ;; XK_F5 VK_F5 + (define-xk xk-f6 #xffc3 #x75) ;; XK_F6 VK_F6 + (define-xk xk-f7 #xffc4 #x76) ;; XK_F7 VK_F7 + (define-xk xk-f8 #xffc5 #x77) ;; XK_F8 VK_F8 + (define-xk xk-f9 #xffc6 #x78) ;; XK_F9 VK_F9 + (define-xk xk-f10 #xffc7 #x79) ;; XK_F10 VK_F10 + (define-xk xk-f11 #xffc8 #x7A) ;; XK_F11 VK_F11 + (define-xk xk-f12 #xffc9 #x7B) ;; XK_F12 VK_F12 + (define-xk xk-f13 #xffca #x7C) ;; XK_F13 VK_F13 + (define-xk xk-f14 #xffcb #x7D) ;; XK_F14 VK_F14 + (define-xk xk-f15 #xffcc #x7E) ;; XK_F15 VK_F15 + (define-xk xk-f16 #xffcd #x7F) ;; XK_F16 VK_F16 + (define-xk xk-f17 #xffce #x80) ;; XK_F17 VK_F17 + (define-xk xk-f18 #xffcf #x81) ;; XK_F18 VK_F18 + (define-xk xk-f19 #xffd0 #x82) ;; XK_F19 VK_F19 + (define-xk xk-f20 #xffd1 #x83) ;; XK_F20 VK_F20 + (define-xk xk-f21 #xffd2 #x84) ;; XK_F21 VK_F21 + (define-xk xk-f22 #xffd3 #x85) ;; XK_F22 VK_F22 + (define-xk xk-f23 #xffd4 #x86) ;; XK_F23 VK_F23 + (define-xk xk-f24 #xffd5 #x87) ;; XK_F24 VK_F24 + + ;; Modifier keys + (define-xk xk-shift-l #xffe1 #xA0) ;; XK_Shift_L VK_LSHIFT + (define-xk xk-shift-r #xffe2 #xA1) ;; XK_Shift_R VK_RSHIFT + (define-xk xk-control-l #xffe3 #xA2) ;; XK_Control_L VK_LCONTROL + (define-xk xk-control-r #xffe4 #xA3) ;; XK_Control_R VK_RCONTROL + (define-xk xk-caps-lock #xffe5 #x14) ;; XK_Caps_Lock VK_CAPITAL + (define-xk xk-metal-l #xffe7 nil) ;; XK_Meta_L + (define-xk xk-metal-t #xffee nil) ;; XK_Meta_R + (define-xk xk-alt-l #xffe9 #xA4) ;; XK_Alt_L VK_LMENU + (define-xk xk-alt-r #xffea #xA5) ;; XK_Alt_R VK_RMENU + (define-xk xk-super-l #xffeb nil) ;; XK_Super_L + (define-xk xk-super-r #xffec nil) ;; XK_Super_R + (define-xk xk-hyper-l #xffed nil) ;; XK_Hyper_L + (define-xk xk-hyper-r #xffee nil) ;; XK_Hyper_R + + ;; Latin 1 + ;; For numbers and letters, MS-Windows does not define constant names. + ;; X11 defines distinct keysyms for lowercase and uppercase + ;; letters. We use only the uppercase ones. Events with lowercase + ;; letters are converted to uppercase. + (define-xk xk-space #x0020 #x20) ;; XK_space VK_SPACE + (define-xk xk-0 #x0030 #x30) ;; XK_0 + (define-xk xk-1 #x0031 #x31) ;; XK_1 + (define-xk xk-2 #x0032 #x32) ;; XK_2 + (define-xk xk-3 #x0033 #x33) ;; XK_3 + (define-xk xk-4 #x0034 #x34) ;; XK_4 + (define-xk xk-5 #x0035 #x35) ;; XK_5 + (define-xk xk-6 #x0036 #x36) ;; XK_6 + (define-xk xk-7 #x0037 #x37) ;; XK_7 + (define-xk xk-8 #x0038 #x38) ;; XK_8 + (define-xk xk-9 #x0039 #x39) ;; XK_9 + (define-xk xk-a #x0041 #x41) ;; XK_A + (define-xk xk-b #x0042 #x42) ;; XK_B + (define-xk xk-c #x0043 #x43) ;; XK_C + (define-xk xk-d #x0044 #x44) ;; XK_D + (define-xk xk-e #x0045 #x45) ;; XK_E + (define-xk xk-f #x0046 #x46) ;; XK_F + (define-xk xk-g #x0047 #x47) ;; XK_G + (define-xk xk-h #x0048 #x48) ;; XK_H + (define-xk xk-i #x0049 #x49) ;; XK_I + (define-xk xk-j #x004A #x4A) ;; XK_J + (define-xk xk-k #x004B #x4B) ;; XK_K + (define-xk xk-l #x004C #x4C) ;; XK_L + (define-xk xk-m #x004D #x4D) ;; XK_M + (define-xk xk-n #x004E #x4E) ;; XK_N + (define-xk xk-o #x004F #x4F) ;; XK_O + (define-xk xk-p #x0050 #x50) ;; XK_P + (define-xk xk-q #x0051 #x51) ;; XK_Q + (define-xk xk-r #x0052 #x52) ;; XK_R + (define-xk xk-s #x0053 #x53) ;; XK_S + (define-xk xk-t #x0054 #x54) ;; XK_T + (define-xk xk-u #x0055 #x55) ;; XK_U + (define-xk xk-v #x0056 #x56) ;; XK_V + (define-xk xk-w #x0057 #x57) ;; XK_W + (define-xk xk-x #x0058 #x58) ;; XK_X + (define-xk xk-y #x0059 #x59) ;; XK_Y + (define-xk xk-z #x005A #x5A));; XK_Z + +(defun llk-init () + "Initialize low-level key events. +Fills the `llk-keysyms' list, and binds the `low-level-key' event +to the `llk-handle' function. Resets the `llk-bindings' list. +Besides calling this function, you need to set `enable-low-level-key-events' +to a non-nil value" + (interactive) + (llk-define-keysyms) + (define-key special-event-map [low-level-key] 'llk-handle) + (setq llk-bindings nil)) + +(defsubst event-is-key-press (event) + "Return the value of the IS-KEY-PRESS field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 1 event))) + +(defsubst event-keysym (event) + "Return the value of the KEY field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 2 event))) + +(defsubst event-modifier (event) + "Return the value of the MODIFIER field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 3 event))) + +(defsubst event-time (event) + "Return the value of the TIME field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 4 event))) + +;; For example: +;; Bind key tap to command +;; (llk-bind 'tap 'xk-shift-l 'delete-other-windows) +;; Bind modifiry tap to command +;; (llk-bind 'tap 'shift 'delete-other-windows) +;; Bind tap to hyper modifier +;; (llk-bind 'tap 'xk-shift-r (lambda () +;; (message "H-...") +;; (setq unread-command-events +;; (append (event-apply-hyper-modifier nil) nil)))) +;; Can bind to a command or function +(defun llk-bind (action key function) + "Bind a command a function to a low level key event. +The only action supported currently is `tap'. The key can be a keysym +symbol, or a modifier symbol (shift, control, alt, meta, hyper, super). +If there is no keysym symbol for a key, use the keysym number. " + (push (list action key function) llk-bindings)) + +;; We store the last events (key/modifier is-press timestamp) here to +;; test for multitap. +(defvar llk-events nil + "Internal variable for detecting taps.") + +;; If positive, return key (xk-shift-l, etc) else return nil. +(defun llk-detect-n-tap (n timeout) + "Internal function to detect n-tap keys." + (let (key + (is-press (event-is-key-press last-input-event)) + ;; convert number to keysym symbol + (keysym (cdr (assoc (event-keysym last-input-event) llk-keysyms))) + (timestamp (event-time last-input-event)) + (modifier (event-modifier last-input-event))) + + ;; if ehte is no symbol for this key, use its keysym number + (unless keysym (setq keysym (event-keysym last-input-event))) + + ;; look in llk-tap-keys for the key, then the modifier + (if (member keysym llk-tap-keys) + (setq key keysym) + (if (member modifier llk-tap-keys) + (setq key modifier))) + + (if (not key) + ;; Key not in tap list, clear history + (setq llk-events nil) + ;; Clear it also if the first element is from a different key + (and llk-events + (not (equal (car (car llk-events)) key)) + (setq llk-events nil)) + (push (list key is-press timestamp) llk-events) + ;; Only care about last 2xN events + (ntake (* 2 n) llk-events) + ;; If we have: + ;; - Exactly 2 * n events. + ;; - down, up, down, up, ... + ;; - not two much time between first and last + (and (eq (* 2 n) (length llk-events)) + (cl-every 'eq + (ntake (* 2 n) + (list nil t nil t nil t nil t + nil t nil t nil t nil t)) + (mapcar 'cl-second llk-events)) + (< (- (cl-third (cl-first llk-events)) + (cl-third (car (last llk-events)))) + timeout) + (progn + (setq llk-events nil) + key))))) + +(defun describe-low-level-key () + "Wait for the next key press and describe the low level key event it +generates." + (interactive) + (setq llk-describe-next-press t)) + +(defun llk-show-event-description () + "Shoe information about the last low level key event." + (setq llk-describe-next-press nil) + (with-help-window (help-buffer) + (insert "\n") + (let* ((xk (event-keysym last-input-event)) + (sym (assoc xk llk-keysyms))) + (insert (format "Keysym number: %d (#x%X),\n" xk xk)) + (if sym + (insert (format "which corresponds to named key %s.\n\n" (cdr sym))) + (insert "which does not correspond to any known named key.\n\n")) + (if (event-modifier last-input-event) + (insert (format "This key corresponds to the %s modifier.\n\n" + (event-modifier last-input-event))) + (insert "This key does not correspond to a modifier.\n\n")) + (insert "See the value of the `llk-keysyms' variable for a list of known keys.\n")))) + +(defun llk-handle () + "Internal function to handle low level key events." + (interactive) + (if (and (event-is-key-press last-input-event) + llk-describe-next-press) + (llk-show-event-description) + (let ((tap-key (llk-detect-n-tap + llk-tap-count + llk-tap-timeout))) + (when tap-key + (let ((func (cl-third + (seq-find + (lambda (b) + (and (eq (cl-first b) 'tap) + (eq (cl-second b) tap-key))) + llk-bindings)))) + (cond + ((commandp func) (call-interactively func)) + ((functionp func) (funcall func)))))))) diff --git a/src/gtkutil.c b/src/gtkutil.c index d57627f152f..86d4321cf35 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -98,6 +98,7 @@ G_DEFINE_TYPE (EmacsMenuBar, emacs_menu_bar, GTK_TYPE_MENU_BAR) static void xg_im_context_preedit_changed (GtkIMContext *, gpointer); static void xg_im_context_preedit_end (GtkIMContext *, gpointer); static bool xg_widget_key_press_event_cb (GtkWidget *, GdkEvent *, gpointer); +static bool xg_widget_key_release_event_cb (GtkWidget *, GdkEvent *, gpointer); #endif #if GTK_CHECK_VERSION (3, 10, 0) @@ -1749,6 +1750,12 @@ xg_create_frame_widgets (struct frame *f) g_signal_connect (G_OBJECT (wfixed), "key-press-event", G_CALLBACK (xg_widget_key_press_event_cb), NULL); + + g_signal_connect (G_OBJECT (wfixed), "key-release-event", + G_CALLBACK (xg_widget_key_release_event_cb), + NULL); + + #endif { @@ -6376,6 +6383,53 @@ xg_im_context_preedit_end (GtkIMContext *imc, gpointer user_data) kbd_buffer_store_event (&inev); } +#ifndef HAVE_XINPUT2 +static void +xg_maybe_send_low_level_key_event (struct frame *f, + GdkEvent *xev) +{ + GdkEventKey xkey = xev->key; + bool is_press; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (xev->type) + { + case GDK_KEY_PRESS: + is_press = true; + break; + case GDK_KEY_RELEASE: + is_press = false; + break; + default: + return; + } + + modifier = x_get_modifier_for_keycode (FRAME_OUTPUT_DATA (f)->display_info, + xev->key.hardware_keycode); + + int keysym = xkey.keyval; + + if (keysym >= GDK_KEY_a && keysym <= GDK_KEY_z) + keysym -= GDK_KEY_a - GDK_KEY_A; + + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = xkey.time; + inev.ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); +} +#endif + static bool xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) @@ -6404,6 +6458,10 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, if (!f) return true; +#ifndef HAVE_XINPUT2 + xg_maybe_send_low_level_key_event (f, event); +#endif + if (popup_activated ()) return true; @@ -6557,6 +6615,29 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, return true; } +static bool +xg_widget_key_release_event_cb (GtkWidget *widget, GdkEvent *event, + gpointer user_data) +{ +#ifndef HAVE_XINPUT2 + Lisp_Object tail, tem; + struct frame *f = NULL; + + FOR_EACH_FRAME (tail, tem) + { + if (FRAME_X_P (XFRAME (tem)) + && (FRAME_GTK_WIDGET (XFRAME (tem)) == widget)) + { + f = XFRAME (tem); + break; + } + } + if (f) + xg_maybe_send_low_level_key_event (f, event); +#endif + return true; +} + bool xg_filter_key (struct frame *frame, XEvent *xkey) { diff --git a/src/keyboard.c b/src/keyboard.c index 6d28dca9aeb..442bf7cee34 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4274,6 +4274,7 @@ kbd_buffer_get_event (KBOARD **kbp, case CONFIG_CHANGED_EVENT: case FOCUS_OUT_EVENT: case SELECT_WINDOW_EVENT: + case LOW_LEVEL_KEY_EVENT: { obj = make_lispy_event (&event->ie); kbd_fetch_ptr = next_kbd_event (event); @@ -7118,12 +7119,47 @@ make_lispy_event (struct input_event *event) case PREEDIT_TEXT_EVENT: return list2 (Qpreedit_text, event->arg); + case LOW_LEVEL_KEY_EVENT: + return listn (6, Qlow_level_key, + XCAR (event->arg), /* Press or release. */ + XCAR (XCDR (event->arg)), /* The key symbol. */ + XCAR (XCDR (XCDR (event->arg))), /* The modifier. */ + make_fixnum (event->timestamp), + event->frame_or_window); + /* The 'kind' field of the event is something we don't recognize. */ default: emacs_abort (); } } +bool +kbd_low_level_key_is_enabled (int keysym, Lisp_Object modifier) +{ + if (Venable_low_level_key_events == Qt) + return true; + + if (Venable_low_level_key_events == Qnil) + return false; + + if (FIXNUMP (Venable_low_level_key_events)) + return keysym == XFIXNUM (Venable_low_level_key_events); + + if (Venable_low_level_key_events == Qmodifiers) + return modifier != Qnil; + + for (Lisp_Object e = Venable_low_level_key_events; CONSP (e); e = XCDR (e)) + { + Lisp_Object c = XCAR (e); + if (FIXNUMP (c) && XFIXNUM (c) == keysym) + return true; + if (c == Qmodifiers && modifier != Qnil) + return true; + } + + return false; +} + static Lisp_Object make_lispy_movement (struct frame *frame, Lisp_Object bar_window, enum scroll_bar_part part, Lisp_Object x, Lisp_Object y, Time t) @@ -12931,6 +12967,29 @@ syms_of_keyboard (void) DEFSYM (Qfile_notify, "file-notify"); #endif /* USE_FILE_NOTIFY */ + + DEFSYM (Qmodifiers, "modifiers"); + + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, + doc: /* If non-nil, reception of low-level key events is enabled. + +The value configures the set of keys that are handled: + +If t, send events for all keys. + +If a number, send events for the corresponding keysym. When calling +'llk-init', a set of variables with the xk- prefix is initialized with +the numeric values for keysyms. This numbers are platform dependent. + +If a symbol, a predefined set of keys is selected. The only currently +valid symbol is 'modifiers. + +If a list of numbers and/or symbols, the corresponding keysyms and sets +are selected. */); + Venable_low_level_key_events = Qnil; + + DEFSYM (Qlow_level_key, "low-level-key"); + DEFSYM (Qtouch_end, "touch-end"); /* Menu and tool bar item parts. */ @@ -14018,6 +14077,8 @@ keys_of_keyboard (void) "handle-focus-out"); initial_define_lispy_key (Vspecial_event_map, "move-frame", "handle-move-frame"); + initial_define_lispy_key (Vspecial_event_map, "low-level-key", + "ignore"); } /* Mark the pointers in the kboard objects. diff --git a/src/keyboard.h b/src/keyboard.h index 387501c9f88..83f9a0f141a 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -511,6 +511,7 @@ kbd_buffer_store_event_hold (struct input_event *event, extern Lisp_Object menu_item_eval_property (Lisp_Object); extern bool kbd_buffer_events_waiting (void); extern void add_user_signal (int, const char *); +extern bool kbd_low_level_key_is_enabled (int, Lisp_Object); extern int tty_read_avail_input (struct terminal *, struct input_event *); extern struct timespec timer_check (void); diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 079945126e0..fba81f5ec0e 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -5201,6 +5201,56 @@ pgtk_enqueue_preedit (struct frame *f, Lisp_Object preedit) evq_enqueue (&inev); } +static void +pgtk_maybe_send_low_level_key_event (struct frame *f, GdkEvent *event) +{ + GdkEventKey xkey = event->key; + bool is_press; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (event->type) + { + case GDK_KEY_PRESS: + is_press = true; + break; + case GDK_KEY_RELEASE: + is_press = false; + break; + default: + return; + } + + /* We don't support modifier identification on PGTK. We only can tell + if the key corresponds to a modifier or not, which is used for + filtering enabled keys with kbd_low_level_key_is_enabled. */ + modifier = event->key.is_modifier ? Qt : Qnil; + + int keysym = xkey.keyval; + + if (keysym >= GDK_KEY_a && keysym <= GDK_KEY_z) + keysym -= GDK_KEY_a - GDK_KEY_A; + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + if (!f) + f = pgtk_any_window_to_frame (event->key.window); + if (!f) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = event->key.time; + inev.ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + evq_enqueue (&inev); +} + static gboolean key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { @@ -5211,6 +5261,9 @@ key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) struct pgtk_display_info *dpyinfo; f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); + + pgtk_maybe_send_low_level_key_event (f, event); + EVENT_INIT (inev.ie); hlinfo = MOUSE_HL_INFO (f); nbytes = 0; @@ -5454,6 +5507,8 @@ key_release_event (GtkWidget *widget, GdkDisplay *display; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_low_level_key_event (NULL, event); + display = gtk_widget_get_display (widget); dpyinfo = pgtk_display_info_for_display (display); diff --git a/src/termhooks.h b/src/termhooks.h index d6a9300bac9..966a6492f69 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -347,6 +347,7 @@ #define EMACS_TERMHOOKS_H /* In a NOTIFICATION_EVENT, .arg is a lambda to evaluate. */ , NOTIFICATION_EVENT #endif /* HAVE_ANDROID */ + , LOW_LEVEL_KEY_EVENT }; /* Bit width of an enum event_kind tag at the start of structs and unions. */ diff --git a/src/w32fns.c b/src/w32fns.c index e2455b9271e..1d18bf408e1 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -4669,6 +4669,11 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_KEYUP: case WM_SYSKEYUP: record_keyup (wParam, lParam); + if (!NILP (Venable_low_level_key_events)) + { + signal_user_input (); + my_post_msg (&wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam); + } goto dflt; case WM_KEYDOWN: @@ -4695,6 +4700,12 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) if (w32_use_fallback_wm_chars_method) wParam = map_keypad_keys (wParam, (lParam & 0x1000000L) != 0); + if (!NILP (Venable_low_level_key_events)) + { + signal_user_input (); + my_post_msg (&wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam); + } + windows_translate = 0; switch (wParam) diff --git a/src/w32term.c b/src/w32term.c index e18f39dd2a8..a2e0a4b0fa0 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -5270,6 +5270,55 @@ w32_read_socket (struct terminal *terminal, } break; + case WM_EMACS_LOW_LEVEL_KEY: + WORD key_flags = HIWORD (msg.msg.lParam); + BOOL is_wm_keyup = key_flags & KF_UP; + + if (is_wm_keyup || (key_flags & KF_REPEAT) == 0) /* WM_KEYDOWN, not repeating. */ + { + WORD scan_code = LOBYTE (key_flags); + if (key_flags & KF_EXTENDED) + scan_code = MAKEWORD (scan_code, 0xE0); + + UINT translated = MapVirtualKey (scan_code, MAPVK_VSC_TO_VK_EX); + WORD vk = LOWORD (msg.msg.wParam); + if (translated) + vk = LOWORD (translated); + + Lisp_Object key = make_fixnum (vk); + Lisp_Object modifier = Qnil; + + switch (vk) + { + case VK_LSHIFT: + case VK_RSHIFT: + modifier = Qshift; + break; + case VK_LCONTROL: + case VK_RCONTROL: + modifier = Qctrl; + break; + case VK_LMENU: + case VK_RMENU: + modifier = Qmeta; + break; + } + + if (kbd_low_level_key_is_enabled (vk, modifier)) + { + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + inev.kind = LOW_LEVEL_KEY_EVENT; + XSETFRAME (inev.frame_or_window, f); + inev.timestamp = msg.msg.time; + inev.arg = list3 (is_wm_keyup ? Qnil : Qt, key, modifier); + kbd_buffer_store_event_hold (&inev, hold_quit); + } + + inev.kind = NO_EVENT; + + } + break; + case WM_UNICHAR: case WM_SYSCHAR: case WM_CHAR: diff --git a/src/w32term.h b/src/w32term.h index cad9fcf8cb1..88f7dfeef8b 100644 --- a/src/w32term.h +++ b/src/w32term.h @@ -713,7 +713,8 @@ #define WM_EMACS_FILENOTIFY (WM_EMACS_START + 25) #define WM_EMACS_IME_STATUS (WM_EMACS_START + 26) #define WM_EMACS_DRAGOVER (WM_EMACS_START + 27) #define WM_EMACS_DROP (WM_EMACS_START + 28) -#define WM_EMACS_END (WM_EMACS_START + 29) +#define WM_EMACS_LOW_LEVEL_KEY (WM_EMACS_START + 29) +#define WM_EMACS_END (WM_EMACS_START + 30) #define WND_FONTWIDTH_INDEX (0) #define WND_LINEHEIGHT_INDEX (4) diff --git a/src/xterm.c b/src/xterm.c index 0c20d38b0f7..72715e0ed73 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -17840,6 +17840,141 @@ #define STORE_KEYSYM_FOR_DEBUG(keysym) ((void)0) static struct x_display_info *next_noop_dpyinfo; +Lisp_Object +x_get_modifier_for_keycode (struct x_display_info *dpyinfo, + int keycode) +{ +#ifdef HAVE_XKB + if (dpyinfo->xkb_desc) + for (int mod = 0; mod < XkbNumModifiers; mod++) + { + int mask = (1 << mod); + if (dpyinfo->xkb_desc->map->modmap[keycode] & mask) + { + if (mask == ShiftMask) + return Qshift; + if (mask == ControlMask) + return Qctrl; + if (mask == dpyinfo->meta_mod_mask) + return Qmeta; + if (mask == dpyinfo->alt_mod_mask) + return Qalt; + if (mask == dpyinfo->super_mod_mask) + return Qsuper; + if (mask == dpyinfo->hyper_mod_mask) + return Qhyper; + } + } +#endif + XModifierKeymap *map = dpyinfo->modmap; + if (map) + for (int mod = 0; mod < 8; mod++) + { + int mask = (1 << mod); + for (int key = 0; key < map->max_keypermod; key++) + if (map->modifiermap[mod * map->max_keypermod + key] == keycode) + { + if (mask == ShiftMask) + return Qshift; + if (mask == ControlMask) + return Qctrl; + if (mask == dpyinfo->meta_mod_mask) + return Qmeta; + if (mask == dpyinfo->alt_mod_mask) + return Qalt; + if (mask == dpyinfo->super_mod_mask) + return Qsuper; + if (mask == dpyinfo->hyper_mod_mask) + return Qhyper; + } + } + return Qnil; +} + +static void +x_maybe_send_low_level_key_event (struct x_display_info *dpyinfo, + const XEvent *xev, struct frame *f) +{ + XKeyEvent xkey; + bool is_press; + KeySym keysym; + Lisp_Object key, modifier; + struct input_event ie; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (xev->type) + { + case KeyPress: + is_press = true; + xkey = xev->xkey; + break; + case KeyRelease: + is_press = false; + xkey = xev->xkey; + break; +#ifdef HAVE_XINPUT2 + case GenericEvent: + XIDeviceEvent *xiev = xev->xcookie.data; + switch (xev->xgeneric.evtype) + { + case XI_KeyPress: + is_press = true; + break; + case XI_KeyRelease: + is_press = false; + break; + default: + return; + } + + xkey.serial = xiev->serial; + xkey.send_event = xiev->send_event; + xkey.display = xiev->display; + xkey.window = xiev->event; + xkey.root = xiev->root; + xkey.subwindow = xiev->child; + xkey.time = xiev->time; + xkey.x = xiev->event_x; + xkey.y = xiev->event_y; + xkey.x_root = xiev->root_x; + xkey.y_root = xiev->root_y; + xkey.state = xiev->mods.effective; + xkey.keycode = xiev->detail; + xkey.same_screen = 1; + break; +#endif + default: + return; + } + + if (!f) + f = x_any_window_to_frame (dpyinfo, xkey.window); + if (!f) + return; + + XLookupString (&xkey, NULL, 0, &keysym, NULL); + + modifier = x_get_modifier_for_keycode (dpyinfo, xkey.keycode); + + /* Convert lowercase latin letter to uppercase. */ + if (keysym >= XK_a && keysym <= XK_z) + keysym -= XK_a - XK_A; + + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (ie); + XSETFRAME (ie.frame_or_window, f); + ie.kind = LOW_LEVEL_KEY_EVENT; + ie.timestamp = xkey.time; + ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + kbd_buffer_store_event (&ie); +} + /* Filter events for the current X input method. DPYINFO is the display this event is for. EVENT is the X event to filter. @@ -20206,6 +20341,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case KeyPress: + x_maybe_send_low_level_key_event (dpyinfo, event, any); x_display_set_last_user_time (dpyinfo, event->xkey.time, event->xkey.send_event, true); @@ -20715,6 +20851,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event, any); #ifdef HAVE_X_I18N /* Don't dispatch this event since XtDispatchEvent calls XFilterEvent, and two calls in a row may freeze the @@ -23970,6 +24107,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, struct xi_device_t *device, *source; XKeyPressedEvent xkey; + x_maybe_send_low_level_key_event (dpyinfo, event, any); + coding = Qlatin_1; /* The code under this label is quite desultory. There @@ -24586,6 +24725,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case XI_KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event, any); + #if defined HAVE_X_I18N || defined USE_GTK || defined USE_LUCID { XKeyPressedEvent xkey; @@ -32662,6 +32803,7 @@ syms_of_xterm (void) Vx_toolkit_scroll_bars = Qnil; #endif + DEFSYM (Qshift, "shift"); DEFSYM (Qmodifier_value, "modifier-value"); DEFSYM (Qctrl, "ctrl"); Fput (Qctrl, Qmodifier_value, make_fixnum (ctrl_modifier)); diff --git a/src/xterm.h b/src/xterm.h index 8d5c9917749..66e052e7acd 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -1906,6 +1906,8 @@ x_mutable_colormap (XVisualInfo *visual) extern void tear_down_x_back_buffer (struct frame *f); extern void initial_set_up_x_back_buffer (struct frame *f); +extern Lisp_Object x_get_modifier_for_keycode (struct x_display_info *, int); + /* Defined in xfns.c. */ extern void x_real_positions (struct frame *, int *, int *); extern void x_change_tab_bar_height (struct frame *, int); -- 2.35.1.windows.2 ^ permalink raw reply related [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-02 16:54 ` Cecilio Pardo @ 2024-12-04 20:01 ` Eli Zaretskii 2024-12-04 21:25 ` Cecilio Pardo 0 siblings, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-12-04 20:01 UTC (permalink / raw) To: Cecilio Pardo; +Cc: luangruo, 74423, monnier > Date: Mon, 2 Dec 2024 17:54:03 +0100 > Cc: 74423@debbugs.gnu.org > From: Cecilio Pardo <cpardo@imayhem.com> > > Here is a new version of the patch. Sorry it took so long. It > addresses most of the notes I got: Thanks. There are several minor issues with this (first line of doc strings not a single complete sentence, one space between sentences, etc.), but I guess this is too early for such details. Other than that, I have only one comment: > + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, > + doc: /* If non-nil, reception of low-level key events is enabled. > + > +The value configures the set of keys that are handled: > + > +If t, send events for all keys. > + > +If a number, send events for the corresponding keysym. When calling > +'llk-init', a set of variables with the xk- prefix is initialized with > +the numeric values for keysyms. This numbers are platform dependent. This seems to say that it is impossible to make the value do the same on all platforms? If so, I think it's less useful than it could be, because Emacs generally tries to abstract platform-specific issues as much as possible, to facilitate platform-independent Lisp programs that work the same on all supported systems. Also, there's no information here where to find the list of these xk- numbers. ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-04 20:01 ` Eli Zaretskii @ 2024-12-04 21:25 ` Cecilio Pardo 2024-12-05 5:41 ` Eli Zaretskii 0 siblings, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-12-04 21:25 UTC (permalink / raw) To: Eli Zaretskii; +Cc: luangruo, 74423, monnier On 04/12/2024 21:01, Eli Zaretskii wrote: >> + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, >> + doc: /* If non-nil, reception of low-level key events is enabled. >> + >> +The value configures the set of keys that are handled: >> + >> +If t, send events for all keys. >> + >> +If a number, send events for the corresponding keysym. When calling >> +'llk-init', a set of variables with the xk- prefix is initialized with >> +the numeric values for keysyms. This numbers are platform dependent. > > This seems to say that it is impossible to make the value do the same > on all platforms? If so, I think it's less useful than it could be, > because Emacs generally tries to abstract platform-specific issues as > much as possible, to facilitate platform-independent Lisp programs > that work the same on all supported systems. > > Also, there's no information here where to find the list of these xk- > numbers. Those are variables, like xk-shift-l, xk-a, xk-f1. There is also llk-keysyms, that has a list of all xk-* symbols and their numeric values. llk-init initializes all of these variables, using numbers that are platform dependent (XK_* constant KeySyms for X, VK_* "virtual keys" for windows. GDK keys match X keys. Numbers are different, but users should use the variables, not the numbers, for binding and comparing to event values. I can translate the Windows numbers to the X equivalents, so that they are equal on both platforms. In any case, I will document this better, with a manual change proposal. ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-04 21:25 ` Cecilio Pardo @ 2024-12-05 5:41 ` Eli Zaretskii 2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-13 22:55 ` Cecilio Pardo 0 siblings, 2 replies; 93+ messages in thread From: Eli Zaretskii @ 2024-12-05 5:41 UTC (permalink / raw) To: Cecilio Pardo; +Cc: luangruo, 74423, monnier > Date: Wed, 4 Dec 2024 22:25:32 +0100 > Cc: monnier@iro.umontreal.ca, luangruo@yahoo.com, 74423@debbugs.gnu.org > From: Cecilio Pardo <cpardo@imayhem.com> > > On 04/12/2024 21:01, Eli Zaretskii wrote: > >> + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, > >> + doc: /* If non-nil, reception of low-level key events is enabled. > >> + > >> +The value configures the set of keys that are handled: > >> + > >> +If t, send events for all keys. > >> + > >> +If a number, send events for the corresponding keysym. When calling > >> +'llk-init', a set of variables with the xk- prefix is initialized with > >> +the numeric values for keysyms. This numbers are platform dependent. > > > > This seems to say that it is impossible to make the value do the same > > on all platforms? If so, I think it's less useful than it could be, > > because Emacs generally tries to abstract platform-specific issues as > > much as possible, to facilitate platform-independent Lisp programs > > that work the same on all supported systems. > > > > Also, there's no information here where to find the list of these xk- > > numbers. > > Those are variables, like xk-shift-l, xk-a, xk-f1. There is also > llk-keysyms, that has a list of all xk-* symbols and their numeric values. > > llk-init initializes all of these variables, using numbers that are > platform dependent (XK_* constant KeySyms for X, VK_* "virtual keys" for > windows. GDK keys match X keys. > > Numbers are different, but users should use the variables, not the > numbers, for binding and comparing to event values. This should be explicitly told in the doc string. Also, if the xk-* variables are platform-independent, the rule to map their names to keyboard keys should be explained somewhere, perhaps in the manual, so that Lisp programmers could easily know how to identify the keys which they stand for. > I can translate the Windows numbers to the X equivalents, so that they > are equal on both platforms. I'm not sure this is needed, if the xk-* names are the same. > In any case, I will document this better, with a manual change > proposal. Thanks. This facility definitely needs to be in the ELisp manual before we install it. ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-05 5:41 ` Eli Zaretskii @ 2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-07 21:52 ` Cecilio Pardo 2024-12-13 22:55 ` Cecilio Pardo 1 sibling, 1 reply; 93+ messages in thread From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-06 1:01 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 74423, Cecilio Pardo, monnier Eli Zaretskii <eliz@gnu.org> writes: >> Date: Wed, 4 Dec 2024 22:25:32 +0100 >> Cc: monnier@iro.umontreal.ca, luangruo@yahoo.com, 74423@debbugs.gnu.org >> From: Cecilio Pardo <cpardo@imayhem.com> >> >> On 04/12/2024 21:01, Eli Zaretskii wrote: >> >> + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, >> >> + doc: /* If non-nil, reception of low-level key events is enabled. >> >> + >> >> +The value configures the set of keys that are handled: >> >> + >> >> +If t, send events for all keys. >> >> + >> >> +If a number, send events for the corresponding keysym. When calling >> >> +'llk-init', a set of variables with the xk- prefix is initialized with >> >> +the numeric values for keysyms. This numbers are platform dependent. >> > >> > This seems to say that it is impossible to make the value do the same >> > on all platforms? If so, I think it's less useful than it could be, >> > because Emacs generally tries to abstract platform-specific issues as >> > much as possible, to facilitate platform-independent Lisp programs >> > that work the same on all supported systems. >> > >> > Also, there's no information here where to find the list of these xk- >> > numbers. >> >> Those are variables, like xk-shift-l, xk-a, xk-f1. There is also >> llk-keysyms, that has a list of all xk-* symbols and their numeric values. >> >> llk-init initializes all of these variables, using numbers that are >> platform dependent (XK_* constant KeySyms for X, VK_* "virtual keys" for >> windows. GDK keys match X keys. >> >> Numbers are different, but users should use the variables, not the >> numbers, for binding and comparing to event values. > > This should be explicitly told in the doc string. Also, if the xk-* > variables are platform-independent, the rule to map their names to > keyboard keys should be explained somewhere, perhaps in the manual, so > that Lisp programmers could easily know how to identify the keys which > they stand for. > >> I can translate the Windows numbers to the X equivalents, so that they >> are equal on both platforms. > > I'm not sure this is needed, if the xk-* names are the same. > >> In any case, I will document this better, with a manual change >> proposal. > > Thanks. This facility definitely needs to be in the ELisp manual > before we install it. I suggest calling XKeysymToString rather than interning the numerical value of keysyms received. This gives users some indication of the identity of these keysyms without tediously referring to (at times vendor-specific) keysymdef.h files. ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-07 21:52 ` Cecilio Pardo 0 siblings, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-12-07 21:52 UTC (permalink / raw) To: 74423 On 06/12/2024 2:01, Po Lu: > I suggest calling XKeysymToString rather than interning the numerical > value of keysyms received. This gives users some indication of the > identity of these keysyms without tediously referring to (at times > vendor-specific) keysymdef.h files. Do you mean replacing variable names like xk-backspace, xk-tab with the strings returned by XKeysymToString? ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-05 5:41 ` Eli Zaretskii 2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-13 22:55 ` Cecilio Pardo 2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-14 11:14 ` Eli Zaretskii 1 sibling, 2 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-12-13 22:55 UTC (permalink / raw) To: Eli Zaretskii; +Cc: luangruo, 74423, monnier [-- Attachment #1: Type: text/plain, Size: 84 bytes --] This new version includes a manual entry, and corrected doc strings and comments. [-- Attachment #2: 0001-Send-events-for-key-presses-and-key-releases.patch --] [-- Type: text/plain, Size: 39113 bytes --] From f2381ff38157387c4b3e8ca35009675976fd9717 Mon Sep 17 00:00:00 2001 From: Cecilio Pardo <cpardo@imayhem.com> Date: Mon, 2 Dec 2024 17:30:42 +0100 Subject: [PATCH] Send events for key presses and key releases. Detect double/triple taps. * doc/lispref/commands.texi: Added information about new event. * src/gtkutil.c (xg_create_frame_widgets): Modified to handle key release events. (xg_maybe_send_low_level_key_event): New function that sends key events. (xg_widget_key_press_event_cb): Modified to send low level key events. (xg_widget_key_release_event_cb): New function to send low level key events on key release. * src/keyboard.c (kbd_buffer_get_event): Modified to handle low level keyboard events. (make_lispy_event): Modified to handle low level keyboard events. (kbd_low_level_key_is_enabled): New function to decide if a particular key should generate events, looking at configuration. (syms_of_keyboard): Added symbols and varialbles. (keys_of_keyboard): Modified to map low level key events to 'special-event-map'. * src/keyboard.h: Modified with function prototype. * src/pgtkterm.c (pgtk_maybe_send_low_level_key_event): New function that sends key events. (key_press_event): Modified to handle low level keyboard events. (key_release_event): Modified to handle low level keyboard events. * src/termhooks.h: Modified to define constant. * src/w32fns.c (w32_wnd_proc): Modified to generate low level key events. * src/w32term.c (w32_read_socket): Modified to generate low level key events. * src/w32term.h: Modified to define constants. * src/xterm.c (x_get_modifier_for_keycode): New function to decide which (if any) modifier corresponds to a given key. (x_maybe_send_low_level_key_event): New function that sends key events. (handle_one_xevent): Modified to handle low level key events. (syms_of_xterm): Modified to define symbol. * src/xterm.h: Modified with function prototype * lisp/low-level-key.el (New file). (llk-bindings): User bindings for low level key events. (llk-tap-count): User option. (llk-tap-timeout): User option. (llk-tap-keys): User option. (llk-keysyms): List of available keysyms. (define-xk): Macro for defining keysyms. (llk-define-keysyms): Build llk-keysyms. (llk-init): Function to initialize low level key handling. (event-is-key-press): Get field from event. (event-keysym): Get field from event. (event-modifier): Get field from event. (event-time): Get field from event. (llk-bind): Function to create a binding. (llk-events): Event history for tap detection. (llk-detect-n-tap): Function to detect taps. (describe-low-level-key): Command to get information about a key. (llk-show-event-description): Show help buffer with information about an event. (llk-handle): Handler for key events. --- doc/lispref/commands.texi | 29 ++++ lisp/low-level-key.el | 342 ++++++++++++++++++++++++++++++++++++++ src/gtkutil.c | 81 +++++++++ src/keyboard.c | 65 ++++++++ src/keyboard.h | 1 + src/pgtkterm.c | 55 ++++++ src/termhooks.h | 1 + src/w32fns.c | 11 ++ src/w32term.c | 49 ++++++ src/w32term.h | 3 +- src/xterm.c | 142 ++++++++++++++++ src/xterm.h | 2 + 12 files changed, 780 insertions(+), 1 deletion(-) create mode 100644 lisp/low-level-key.el diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index df8266a7157..8a1f17689c9 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2658,6 +2658,35 @@ Misc Events respectively. They are usually interpreted as being relative to the size of the object beneath the gesture: image, window, etc. +@cindex @code{low-level-key} event +@item (low-level-key @var{is-key-press} @var{key} @var{modifier} @var{time} @var{frame}) +This event is sent on the physical press or release of keys, only on GUI +systems, currently X, MS-Windows and PGTK. + +@var{is-key-press} is @code{t} for a key press, @code{nil} for a key release. +@var{time} is the event's time in milliseconds, @var{frame} is the +frame receiving it. @var{modifier} is @code{nil} if the key is not a +modifier key, @code{t} if it is, but it is unknown which one, or one of +@code{shift}, @code{control}, @code{meta}, @code{alt}, @code{super}, +@code{hyper}. + +@var{key}, an integer, identifies the key pressed or released. This +number is platform dependent, but there are variables for most keys that +can be used in place of the numbers to identify them. For example, the +variable @code{xk-backspace} identifies the @key{backspace} key. + +The names are parallel to those for KeySyms on X, as defined in +@file{xkeysymdef.h}. For example, @code{XK_Shift_L} (the left shift +key), corresponds to @code{xk-shift-l}. The @file{xkeysymdef.h} file +defines different KeySyms for capital and small versions of letters. +For this event, only the capital version is used, with the variables +@code{xk-a}, @code{xk-b}, etc. + +These variables are initialized by calling the function @code{llk-init}. +This function also binds a handler to this event, which allows to detect +double taps on keys (normally modifier keys) and bind commands or +functions to be run. See the function @code{llk-bind}. + @cindex @code{preedit-text} event @item (preedit-text @var{arg}) This event is sent when a system input method tells Emacs to display diff --git a/lisp/low-level-key.el b/lisp/low-level-key.el new file mode 100644 index 00000000000..bc9311f82d0 --- /dev/null +++ b/lisp/low-level-key.el @@ -0,0 +1,342 @@ +;;; -*- lexical-binding: t -*- + +;; The physical-key event is like this: +;; (low-level-key IS-KEY-PRESS KEY MODIFIER TIME FRAME) +;; IS-KEY-PRESS is t if the key has been pressed, nil if it has been released. +;; KEY is the keysym number. +;; MODIFIER is the modifier associated with this key. It is nil if the key is +;; not a modifier. It can be one of the following symbols: shift, control, meta, +;; super, hyper, alt. It can also be t if the key is a modifier but it can't be +;; identified, as in the PGTK backend. +;; TIME is the timestamp in milliseconds of the event. +;; FRAME is the frame where the event happened. +;; +;; After calling 'llk-init' and setting a non-nil value for +;; 'enable-low-level-key-events', events begin to be handled by 'llk-handler', +;; which tries to detect n-taps and calls the corresponding function. +;; +;; To implement other functionalities, you can replace llk-handler with +;; your own function. + +(require 'cl-lib) + +;; User options + +(defvar llk-bindings nil + "List of bindings for low level key events (press/release/tap). + +Use the `llk-bind' function to add bindings. See its documentation for +a description of the binding information.") + +(defvar llk-tap-count 2 + "Number or key press/releases to consider a tap.") + +(defvar llk-tap-timeout 1000 + "Time (ms) between consecutive key presses/releases to consider a tap.") + +(defvar llk-tap-keys + '(xk-shift-l xk-shift-r xk-control-l xk-control-r meta) + "Keys that can generate taps.") + +(defvar llk-keysyms nil + "List of keysym numbers and their corresponding symbols. + +Each element has the form (KEYSYM . SYMBOL). The variable value for +each symbol is the keysym. This list is initialized by `llk-init'.") + +(defvar llk-describe-next-press nil + "Internal variable to mark that next key press should be described.") + +(defmacro define-xk (name x-keysym w32-keysym) + "Internal macro to define keysyms." + `(let ((ksym (pcase (window-system) + ('pgtk ,x-keysym) + ('x ,x-keysym) + ('w32 ,w32-keysym)))) + (defconst ,name ksym "Constant for a keysym value.") + (push (cons ksym ',name) llk-keysyms))) + +(defun llk-define-keysyms () + "Initialize the keysym list, `llk-keysyms'. + +Called from `llk-init'." + (setq llk-keysyms nil) + + ;; tty keys + (define-xk xk-backspace #xff08 #x08) ;; XK_BackSpace VK_BACK + (define-xk xk-tab #xff09 #x09) ;; XK_Tab VK_TAB + (define-xk xk-clear #xff0b #x0C) ;; XK_Clear VK_CLEAR + (define-xk xk-return #xff0d #x0D) ;; XK_Return VK_RETURN + (define-xk xk-pause #xff13 #x13) ;; XK_Pause VK_PAUSE + (define-xk xk-scroll-lock #xff14 #x91) ;; XK_Scroll_Lock VK_SCROLL + (define-xk xk-escape #xff1B #x1B) ;; XK_Escape VK_ESCAPE + (define-xk xk-delete #xffff #x2E) ;; XK_Delete VK_DELETE + + ;; Cursor control and motion + (define-xk xk-home #xff50 #x24) ;; XK_Home VK_HOME + (define-xk xk-left #xff51 #x25) ;; XK_Left VK_LEFT + (define-xk xk-up #xff52 #x26) ;; XK_Up VK_UP + (define-xk xk-right #xff53 #x27) ;; XK_Right VK_RIGHT + (define-xk xk-down #xff54 #x28) ;; XK_Down VK_DOWN + (define-xk xk-page-up #xff55 #x21) ;; XK_Page_Up VK_PRIOR + (define-xk xk-page-down #xff56 #x22) ;; XK_Page_Down VK_NEXT + (define-xk xk-end #xff57 #x23) ;; XK_End VK_END + (define-xk xk-begin #xff58 #x24) ;; XK_Begin VK_HOME + + ;; Special Windows keyboard keys + (define-xk xk-win-l #xFF5B #x5B) ;; XK_Win_L VK_LWIN + (define-xk xk-win-r #xFF5C #x5C) ;; XK_Win_R VK_RWIN + (define-xk xk-app #xFF5D #x5D) ;; XK_App VK_APPS + + ;; Misc functions + (define-xk xk-select #xff60 #x29) ;; XK_Select VK_SELECT + (define-xk xk-print #xff61 #x2A) ;; XK_Print VK_PRINT + (define-xk xk-insert #xff64 #x2D) ;; XK_Insert VK_INSERT + (define-xk xk-num-lock #xff7f #x90) ;; XK_Num_Lock VK_NUMLOCK + + ;; Keypad + ;; TODO: Check values for MS-Windows + (define-xk xk-kp-enter #xff8d nil) ;; XK_KP_Enter ??? + (define-xk xk-kp-multiply #xffaa nil) ;; XK_KP_Multiply ??? + (define-xk xk-kp-add #xffab nil) ;; XK_KP_Add ??? + (define-xk xk-kp-subtract #xffad nil) ;; XK_KP_Subtract ??? + (define-xk xk-kp-decimal #xffae nil) ;; XK_KP_Decimal ??? + (define-xk xk-kp-divide #xffaf nil) ;; XK_KP_Divide ??? + (define-xk xk-kp-0 #xffb0 #x60) ;; XK_KP_0 VK_NUMPAD0 + (define-xk xk-kp-1 #xffb1 #x61) ;; XK_KP_1 VK_NUMPAD1 + (define-xk xk-kp-2 #xffb2 #x62) ;; XK_KP_2 VK_NUMPAD2 + (define-xk xk-kp-3 #xffb3 #x63) ;; XK_KP_3 VK_NUMPAD3 + (define-xk xk-kp-4 #xffb4 #x64) ;; XK_KP_4 VK_NUMPAD4 + (define-xk xk-kp-5 #xffb5 #x65) ;; XK_KP_5 VK_NUMPAD5 + (define-xk xk-kp-6 #xffb6 #x66) ;; XK_KP_6 VK_NUMPAD6 + (define-xk xk-kp-7 #xffb7 #x67) ;; XK_KP_7 VK_NUMPAD7 + (define-xk xk-kp-8 #xffb8 #x68) ;; XK_KP_8 VK_NUMPAD8 + (define-xk xk-kp-9 #xffb9 #x69) ;; XK_KP_9 VK_NUMPAD9 + + ;; Function keys + (define-xk xk-f1 #xffbe #x70) ;; XK_F1 VK_F1 + (define-xk xk-f2 #xffbf #x71) ;; XK_F2 VK_F2 + (define-xk xk-f3 #xffc0 #x72) ;; XK_F3 VK_F3 + (define-xk xk-f4 #xffc1 #x73) ;; XK_F4 VK_F4 + (define-xk xk-f5 #xffc2 #x74) ;; XK_F5 VK_F5 + (define-xk xk-f6 #xffc3 #x75) ;; XK_F6 VK_F6 + (define-xk xk-f7 #xffc4 #x76) ;; XK_F7 VK_F7 + (define-xk xk-f8 #xffc5 #x77) ;; XK_F8 VK_F8 + (define-xk xk-f9 #xffc6 #x78) ;; XK_F9 VK_F9 + (define-xk xk-f10 #xffc7 #x79) ;; XK_F10 VK_F10 + (define-xk xk-f11 #xffc8 #x7A) ;; XK_F11 VK_F11 + (define-xk xk-f12 #xffc9 #x7B) ;; XK_F12 VK_F12 + (define-xk xk-f13 #xffca #x7C) ;; XK_F13 VK_F13 + (define-xk xk-f14 #xffcb #x7D) ;; XK_F14 VK_F14 + (define-xk xk-f15 #xffcc #x7E) ;; XK_F15 VK_F15 + (define-xk xk-f16 #xffcd #x7F) ;; XK_F16 VK_F16 + (define-xk xk-f17 #xffce #x80) ;; XK_F17 VK_F17 + (define-xk xk-f18 #xffcf #x81) ;; XK_F18 VK_F18 + (define-xk xk-f19 #xffd0 #x82) ;; XK_F19 VK_F19 + (define-xk xk-f20 #xffd1 #x83) ;; XK_F20 VK_F20 + (define-xk xk-f21 #xffd2 #x84) ;; XK_F21 VK_F21 + (define-xk xk-f22 #xffd3 #x85) ;; XK_F22 VK_F22 + (define-xk xk-f23 #xffd4 #x86) ;; XK_F23 VK_F23 + (define-xk xk-f24 #xffd5 #x87) ;; XK_F24 VK_F24 + + ;; Modifier keys + (define-xk xk-shift-l #xffe1 #xA0) ;; XK_Shift_L VK_LSHIFT + (define-xk xk-shift-r #xffe2 #xA1) ;; XK_Shift_R VK_RSHIFT + (define-xk xk-control-l #xffe3 #xA2) ;; XK_Control_L VK_LCONTROL + (define-xk xk-control-r #xffe4 #xA3) ;; XK_Control_R VK_RCONTROL + (define-xk xk-caps-lock #xffe5 #x14) ;; XK_Caps_Lock VK_CAPITAL + (define-xk xk-metal-l #xffe7 nil) ;; XK_Meta_L + (define-xk xk-metal-t #xffee nil) ;; XK_Meta_R + (define-xk xk-alt-l #xffe9 #xA4) ;; XK_Alt_L VK_LMENU + (define-xk xk-alt-r #xffea #xA5) ;; XK_Alt_R VK_RMENU + (define-xk xk-super-l #xffeb nil) ;; XK_Super_L + (define-xk xk-super-r #xffec nil) ;; XK_Super_R + (define-xk xk-hyper-l #xffed nil) ;; XK_Hyper_L + (define-xk xk-hyper-r #xffee nil) ;; XK_Hyper_R + + ;; Latin 1 + ;; For numbers and letters, MS-Windows does not define constant names. + ;; X11 defines distinct keysyms for lowercase and uppercase + ;; letters. We use only the uppercase ones. Events with lowercase + ;; letters are converted to uppercase. + (define-xk xk-space #x0020 #x20) ;; XK_space VK_SPACE + (define-xk xk-0 #x0030 #x30) ;; XK_0 + (define-xk xk-1 #x0031 #x31) ;; XK_1 + (define-xk xk-2 #x0032 #x32) ;; XK_2 + (define-xk xk-3 #x0033 #x33) ;; XK_3 + (define-xk xk-4 #x0034 #x34) ;; XK_4 + (define-xk xk-5 #x0035 #x35) ;; XK_5 + (define-xk xk-6 #x0036 #x36) ;; XK_6 + (define-xk xk-7 #x0037 #x37) ;; XK_7 + (define-xk xk-8 #x0038 #x38) ;; XK_8 + (define-xk xk-9 #x0039 #x39) ;; XK_9 + (define-xk xk-a #x0041 #x41) ;; XK_A + (define-xk xk-b #x0042 #x42) ;; XK_B + (define-xk xk-c #x0043 #x43) ;; XK_C + (define-xk xk-d #x0044 #x44) ;; XK_D + (define-xk xk-e #x0045 #x45) ;; XK_E + (define-xk xk-f #x0046 #x46) ;; XK_F + (define-xk xk-g #x0047 #x47) ;; XK_G + (define-xk xk-h #x0048 #x48) ;; XK_H + (define-xk xk-i #x0049 #x49) ;; XK_I + (define-xk xk-j #x004A #x4A) ;; XK_J + (define-xk xk-k #x004B #x4B) ;; XK_K + (define-xk xk-l #x004C #x4C) ;; XK_L + (define-xk xk-m #x004D #x4D) ;; XK_M + (define-xk xk-n #x004E #x4E) ;; XK_N + (define-xk xk-o #x004F #x4F) ;; XK_O + (define-xk xk-p #x0050 #x50) ;; XK_P + (define-xk xk-q #x0051 #x51) ;; XK_Q + (define-xk xk-r #x0052 #x52) ;; XK_R + (define-xk xk-s #x0053 #x53) ;; XK_S + (define-xk xk-t #x0054 #x54) ;; XK_T + (define-xk xk-u #x0055 #x55) ;; XK_U + (define-xk xk-v #x0056 #x56) ;; XK_V + (define-xk xk-w #x0057 #x57) ;; XK_W + (define-xk xk-x #x0058 #x58) ;; XK_X + (define-xk xk-y #x0059 #x59) ;; XK_Y + (define-xk xk-z #x005A #x5A));; XK_Z + +(defun llk-init () + "Initialize low level key events. + +Fills the `llk-keysyms' list, and binds the `low-level-key' event +to the `llk-handle' function. Resets the `llk-bindings' list. +Besides calling this function, you need to set `enable-low-level-key-events' +to a non-nil value." + (interactive) + (llk-define-keysyms) + (define-key special-event-map [low-level-key] 'llk-handle) + (setq llk-bindings nil)) + +(defsubst event-is-key-press (event) + "Return the value of the IS-KEY-PRESS field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 1 event))) + +(defsubst event-keysym (event) + "Return the value of the KEY field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 2 event))) + +(defsubst event-modifier (event) + "Return the value of the MODIFIER field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 3 event))) + +(defsubst event-time (event) + "Return the value of the TIME field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 4 event))) + +;; For example: +;; Bind key tap to command +;; (llk-bind 'tap 'xk-shift-l 'delete-other-windows) +;; Bind modifier tap to command +;; (llk-bind 'tap 'shift 'delete-other-windows) +;; Bind tap to hyper modifier +;; (llk-bind 'tap 'xk-shift-r (lambda () +;; (message "H-...") +;; (setq unread-command-events +;; (append (event-apply-hyper-modifier nil) nil)))) +;; Can bind to a command or function +(defun llk-bind (action key function) + "Bind a command or function to a low level key event. + +The only action supported currently is `tap'. The key can be a keysym +symbol, or a modifier symbol (shift, control, alt, meta, hyper, super). +If there is no keysym symbol for a key, use the keysym number." + (push (list action key function) llk-bindings)) + +;; We store the last events (key/modifier is-press timestamp) here to +;; test for multitap. +(defvar llk-events nil + "Internal variable for detecting taps.") + +;; If positive, return key (xk-shift-l, etc) else return nil. +(defun llk-detect-n-tap (n timeout) + "Internal function to detect n-tap keys." + (let (key + (is-press (event-is-key-press last-input-event)) + ;; convert number to keysym symbol + (keysym (cdr (assoc (event-keysym last-input-event) llk-keysyms))) + (timestamp (event-time last-input-event)) + (modifier (event-modifier last-input-event))) + + ;; if ehte is no symbol for this key, use its keysym number + (unless keysym (setq keysym (event-keysym last-input-event))) + + ;; look in llk-tap-keys for the key, then the modifier + (if (member keysym llk-tap-keys) + (setq key keysym) + (if (member modifier llk-tap-keys) + (setq key modifier))) + + (if (not key) + ;; Key not in tap list, clear history + (setq llk-events nil) + ;; Clear it also if the first element is from a different key + (and llk-events + (not (equal (car (car llk-events)) key)) + (setq llk-events nil)) + (push (list key is-press timestamp) llk-events) + ;; Only care about last 2xN events + (ntake (* 2 n) llk-events) + ;; If we have: + ;; - Exactly 2 * n events. + ;; - down, up, down, up, ... + ;; - not two much time between first and last + (and (eq (* 2 n) (length llk-events)) + (cl-every 'eq + (ntake (* 2 n) + (list nil t nil t nil t nil t + nil t nil t nil t nil t)) + (mapcar 'cl-second llk-events)) + (< (- (cl-third (cl-first llk-events)) + (cl-third (car (last llk-events)))) + timeout) + (progn + (setq llk-events nil) + key))))) + +(defun describe-low-level-key () + "Wait for key press and describe the low level key event it generates." + (interactive) + (setq llk-describe-next-press t)) + +(defun llk-show-event-description () + "Show information about the last low level key event." + (setq llk-describe-next-press nil) + (with-help-window (help-buffer) + (insert "\n") + (let* ((xk (event-keysym last-input-event)) + (sym (assoc xk llk-keysyms))) + (insert (format "Keysym number: %d (#x%X),\n" xk xk)) + (if sym + (insert (format "which corresponds to named key %s.\n\n" (cdr sym))) + (insert "which does not correspond to any known named key.\n\n")) + (if (event-modifier last-input-event) + (insert (format "This key corresponds to the %s modifier.\n\n" + (event-modifier last-input-event))) + (insert "This key does not correspond to a modifier.\n\n")) + (insert "See the value of the `llk-keysyms' variable for a list of known keys.\n")))) + +(defun llk-handle () + "Internal function to handle low level key events." + (interactive) + (if (and (event-is-key-press last-input-event) + llk-describe-next-press) + (llk-show-event-description) + (let ((tap-key (llk-detect-n-tap + llk-tap-count + llk-tap-timeout))) + (when tap-key + (let ((func (cl-third + (seq-find + (lambda (b) + (and (eq (cl-first b) 'tap) + (eq (cl-second b) tap-key))) + llk-bindings)))) + (cond + ((commandp func) (call-interactively func)) + ((functionp func) (funcall func)))))))) diff --git a/src/gtkutil.c b/src/gtkutil.c index d57627f152f..86d4321cf35 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -98,6 +98,7 @@ G_DEFINE_TYPE (EmacsMenuBar, emacs_menu_bar, GTK_TYPE_MENU_BAR) static void xg_im_context_preedit_changed (GtkIMContext *, gpointer); static void xg_im_context_preedit_end (GtkIMContext *, gpointer); static bool xg_widget_key_press_event_cb (GtkWidget *, GdkEvent *, gpointer); +static bool xg_widget_key_release_event_cb (GtkWidget *, GdkEvent *, gpointer); #endif #if GTK_CHECK_VERSION (3, 10, 0) @@ -1749,6 +1750,12 @@ xg_create_frame_widgets (struct frame *f) g_signal_connect (G_OBJECT (wfixed), "key-press-event", G_CALLBACK (xg_widget_key_press_event_cb), NULL); + + g_signal_connect (G_OBJECT (wfixed), "key-release-event", + G_CALLBACK (xg_widget_key_release_event_cb), + NULL); + + #endif { @@ -6376,6 +6383,53 @@ xg_im_context_preedit_end (GtkIMContext *imc, gpointer user_data) kbd_buffer_store_event (&inev); } +#ifndef HAVE_XINPUT2 +static void +xg_maybe_send_low_level_key_event (struct frame *f, + GdkEvent *xev) +{ + GdkEventKey xkey = xev->key; + bool is_press; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (xev->type) + { + case GDK_KEY_PRESS: + is_press = true; + break; + case GDK_KEY_RELEASE: + is_press = false; + break; + default: + return; + } + + modifier = x_get_modifier_for_keycode (FRAME_OUTPUT_DATA (f)->display_info, + xev->key.hardware_keycode); + + int keysym = xkey.keyval; + + if (keysym >= GDK_KEY_a && keysym <= GDK_KEY_z) + keysym -= GDK_KEY_a - GDK_KEY_A; + + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = xkey.time; + inev.ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); +} +#endif + static bool xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) @@ -6404,6 +6458,10 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, if (!f) return true; +#ifndef HAVE_XINPUT2 + xg_maybe_send_low_level_key_event (f, event); +#endif + if (popup_activated ()) return true; @@ -6557,6 +6615,29 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, return true; } +static bool +xg_widget_key_release_event_cb (GtkWidget *widget, GdkEvent *event, + gpointer user_data) +{ +#ifndef HAVE_XINPUT2 + Lisp_Object tail, tem; + struct frame *f = NULL; + + FOR_EACH_FRAME (tail, tem) + { + if (FRAME_X_P (XFRAME (tem)) + && (FRAME_GTK_WIDGET (XFRAME (tem)) == widget)) + { + f = XFRAME (tem); + break; + } + } + if (f) + xg_maybe_send_low_level_key_event (f, event); +#endif + return true; +} + bool xg_filter_key (struct frame *frame, XEvent *xkey) { diff --git a/src/keyboard.c b/src/keyboard.c index 6d28dca9aeb..cf13e5e7e63 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4274,6 +4274,7 @@ kbd_buffer_get_event (KBOARD **kbp, case CONFIG_CHANGED_EVENT: case FOCUS_OUT_EVENT: case SELECT_WINDOW_EVENT: + case LOW_LEVEL_KEY_EVENT: { obj = make_lispy_event (&event->ie); kbd_fetch_ptr = next_kbd_event (event); @@ -7118,12 +7119,47 @@ make_lispy_event (struct input_event *event) case PREEDIT_TEXT_EVENT: return list2 (Qpreedit_text, event->arg); + case LOW_LEVEL_KEY_EVENT: + return listn (6, Qlow_level_key, + XCAR (event->arg), /* Press or release. */ + XCAR (XCDR (event->arg)), /* The key symbol. */ + XCAR (XCDR (XCDR (event->arg))), /* The modifier. */ + make_fixnum (event->timestamp), + event->frame_or_window); + /* The 'kind' field of the event is something we don't recognize. */ default: emacs_abort (); } } +bool +kbd_low_level_key_is_enabled (int keysym, Lisp_Object modifier) +{ + if (Venable_low_level_key_events == Qt) + return true; + + if (Venable_low_level_key_events == Qnil) + return false; + + if (FIXNUMP (Venable_low_level_key_events)) + return keysym == XFIXNUM (Venable_low_level_key_events); + + if (Venable_low_level_key_events == Qmodifiers) + return modifier != Qnil; + + for (Lisp_Object e = Venable_low_level_key_events; CONSP (e); e = XCDR (e)) + { + Lisp_Object c = XCAR (e); + if (FIXNUMP (c) && XFIXNUM (c) == keysym) + return true; + if (c == Qmodifiers && modifier != Qnil) + return true; + } + + return false; +} + static Lisp_Object make_lispy_movement (struct frame *frame, Lisp_Object bar_window, enum scroll_bar_part part, Lisp_Object x, Lisp_Object y, Time t) @@ -12931,6 +12967,33 @@ syms_of_keyboard (void) DEFSYM (Qfile_notify, "file-notify"); #endif /* USE_FILE_NOTIFY */ + + DEFSYM (Qmodifiers, "modifiers"); + + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, + doc: /* If non-nil, reception of low-level key events is enabled. + +The value configures the set of keys that are handled: + +If t, send events for all keys. + +If a number, send events for the corresponding keysym. When calling +'llk-init', a set of variables with the xk- prefix is initialized with +the numeric values for keysyms. Because this number are platform +dependent, only the variables should be used to refer to a key. For +example, the 'xk-backspace' variable refer to the backspace key, with +the numeric value 0xff08 on X, and the 0x08 on MS-Windows. You can see +all defined variables on the variable 'llk-keysyms'. + +If a symbol, a predefined set of keys is selected. The only currently +valid symbol is 'modifiers. + +If a list of numbers and/or symbols, the corresponding keysyms and sets +are selected. */); + Venable_low_level_key_events = Qnil; + + DEFSYM (Qlow_level_key, "low-level-key"); + DEFSYM (Qtouch_end, "touch-end"); /* Menu and tool bar item parts. */ @@ -14018,6 +14081,8 @@ keys_of_keyboard (void) "handle-focus-out"); initial_define_lispy_key (Vspecial_event_map, "move-frame", "handle-move-frame"); + initial_define_lispy_key (Vspecial_event_map, "low-level-key", + "ignore"); } /* Mark the pointers in the kboard objects. diff --git a/src/keyboard.h b/src/keyboard.h index 387501c9f88..83f9a0f141a 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -511,6 +511,7 @@ kbd_buffer_store_event_hold (struct input_event *event, extern Lisp_Object menu_item_eval_property (Lisp_Object); extern bool kbd_buffer_events_waiting (void); extern void add_user_signal (int, const char *); +extern bool kbd_low_level_key_is_enabled (int, Lisp_Object); extern int tty_read_avail_input (struct terminal *, struct input_event *); extern struct timespec timer_check (void); diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 079945126e0..fba81f5ec0e 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -5201,6 +5201,56 @@ pgtk_enqueue_preedit (struct frame *f, Lisp_Object preedit) evq_enqueue (&inev); } +static void +pgtk_maybe_send_low_level_key_event (struct frame *f, GdkEvent *event) +{ + GdkEventKey xkey = event->key; + bool is_press; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (event->type) + { + case GDK_KEY_PRESS: + is_press = true; + break; + case GDK_KEY_RELEASE: + is_press = false; + break; + default: + return; + } + + /* We don't support modifier identification on PGTK. We only can tell + if the key corresponds to a modifier or not, which is used for + filtering enabled keys with kbd_low_level_key_is_enabled. */ + modifier = event->key.is_modifier ? Qt : Qnil; + + int keysym = xkey.keyval; + + if (keysym >= GDK_KEY_a && keysym <= GDK_KEY_z) + keysym -= GDK_KEY_a - GDK_KEY_A; + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + if (!f) + f = pgtk_any_window_to_frame (event->key.window); + if (!f) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = event->key.time; + inev.ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + evq_enqueue (&inev); +} + static gboolean key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { @@ -5211,6 +5261,9 @@ key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) struct pgtk_display_info *dpyinfo; f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); + + pgtk_maybe_send_low_level_key_event (f, event); + EVENT_INIT (inev.ie); hlinfo = MOUSE_HL_INFO (f); nbytes = 0; @@ -5454,6 +5507,8 @@ key_release_event (GtkWidget *widget, GdkDisplay *display; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_low_level_key_event (NULL, event); + display = gtk_widget_get_display (widget); dpyinfo = pgtk_display_info_for_display (display); diff --git a/src/termhooks.h b/src/termhooks.h index d6a9300bac9..966a6492f69 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -347,6 +347,7 @@ #define EMACS_TERMHOOKS_H /* In a NOTIFICATION_EVENT, .arg is a lambda to evaluate. */ , NOTIFICATION_EVENT #endif /* HAVE_ANDROID */ + , LOW_LEVEL_KEY_EVENT }; /* Bit width of an enum event_kind tag at the start of structs and unions. */ diff --git a/src/w32fns.c b/src/w32fns.c index e2455b9271e..1d18bf408e1 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -4669,6 +4669,11 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_KEYUP: case WM_SYSKEYUP: record_keyup (wParam, lParam); + if (!NILP (Venable_low_level_key_events)) + { + signal_user_input (); + my_post_msg (&wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam); + } goto dflt; case WM_KEYDOWN: @@ -4695,6 +4700,12 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) if (w32_use_fallback_wm_chars_method) wParam = map_keypad_keys (wParam, (lParam & 0x1000000L) != 0); + if (!NILP (Venable_low_level_key_events)) + { + signal_user_input (); + my_post_msg (&wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam); + } + windows_translate = 0; switch (wParam) diff --git a/src/w32term.c b/src/w32term.c index e18f39dd2a8..a2e0a4b0fa0 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -5270,6 +5270,55 @@ w32_read_socket (struct terminal *terminal, } break; + case WM_EMACS_LOW_LEVEL_KEY: + WORD key_flags = HIWORD (msg.msg.lParam); + BOOL is_wm_keyup = key_flags & KF_UP; + + if (is_wm_keyup || (key_flags & KF_REPEAT) == 0) /* WM_KEYDOWN, not repeating. */ + { + WORD scan_code = LOBYTE (key_flags); + if (key_flags & KF_EXTENDED) + scan_code = MAKEWORD (scan_code, 0xE0); + + UINT translated = MapVirtualKey (scan_code, MAPVK_VSC_TO_VK_EX); + WORD vk = LOWORD (msg.msg.wParam); + if (translated) + vk = LOWORD (translated); + + Lisp_Object key = make_fixnum (vk); + Lisp_Object modifier = Qnil; + + switch (vk) + { + case VK_LSHIFT: + case VK_RSHIFT: + modifier = Qshift; + break; + case VK_LCONTROL: + case VK_RCONTROL: + modifier = Qctrl; + break; + case VK_LMENU: + case VK_RMENU: + modifier = Qmeta; + break; + } + + if (kbd_low_level_key_is_enabled (vk, modifier)) + { + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + inev.kind = LOW_LEVEL_KEY_EVENT; + XSETFRAME (inev.frame_or_window, f); + inev.timestamp = msg.msg.time; + inev.arg = list3 (is_wm_keyup ? Qnil : Qt, key, modifier); + kbd_buffer_store_event_hold (&inev, hold_quit); + } + + inev.kind = NO_EVENT; + + } + break; + case WM_UNICHAR: case WM_SYSCHAR: case WM_CHAR: diff --git a/src/w32term.h b/src/w32term.h index cad9fcf8cb1..88f7dfeef8b 100644 --- a/src/w32term.h +++ b/src/w32term.h @@ -713,7 +713,8 @@ #define WM_EMACS_FILENOTIFY (WM_EMACS_START + 25) #define WM_EMACS_IME_STATUS (WM_EMACS_START + 26) #define WM_EMACS_DRAGOVER (WM_EMACS_START + 27) #define WM_EMACS_DROP (WM_EMACS_START + 28) -#define WM_EMACS_END (WM_EMACS_START + 29) +#define WM_EMACS_LOW_LEVEL_KEY (WM_EMACS_START + 29) +#define WM_EMACS_END (WM_EMACS_START + 30) #define WND_FONTWIDTH_INDEX (0) #define WND_LINEHEIGHT_INDEX (4) diff --git a/src/xterm.c b/src/xterm.c index 0c20d38b0f7..72715e0ed73 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -17840,6 +17840,141 @@ #define STORE_KEYSYM_FOR_DEBUG(keysym) ((void)0) static struct x_display_info *next_noop_dpyinfo; +Lisp_Object +x_get_modifier_for_keycode (struct x_display_info *dpyinfo, + int keycode) +{ +#ifdef HAVE_XKB + if (dpyinfo->xkb_desc) + for (int mod = 0; mod < XkbNumModifiers; mod++) + { + int mask = (1 << mod); + if (dpyinfo->xkb_desc->map->modmap[keycode] & mask) + { + if (mask == ShiftMask) + return Qshift; + if (mask == ControlMask) + return Qctrl; + if (mask == dpyinfo->meta_mod_mask) + return Qmeta; + if (mask == dpyinfo->alt_mod_mask) + return Qalt; + if (mask == dpyinfo->super_mod_mask) + return Qsuper; + if (mask == dpyinfo->hyper_mod_mask) + return Qhyper; + } + } +#endif + XModifierKeymap *map = dpyinfo->modmap; + if (map) + for (int mod = 0; mod < 8; mod++) + { + int mask = (1 << mod); + for (int key = 0; key < map->max_keypermod; key++) + if (map->modifiermap[mod * map->max_keypermod + key] == keycode) + { + if (mask == ShiftMask) + return Qshift; + if (mask == ControlMask) + return Qctrl; + if (mask == dpyinfo->meta_mod_mask) + return Qmeta; + if (mask == dpyinfo->alt_mod_mask) + return Qalt; + if (mask == dpyinfo->super_mod_mask) + return Qsuper; + if (mask == dpyinfo->hyper_mod_mask) + return Qhyper; + } + } + return Qnil; +} + +static void +x_maybe_send_low_level_key_event (struct x_display_info *dpyinfo, + const XEvent *xev, struct frame *f) +{ + XKeyEvent xkey; + bool is_press; + KeySym keysym; + Lisp_Object key, modifier; + struct input_event ie; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (xev->type) + { + case KeyPress: + is_press = true; + xkey = xev->xkey; + break; + case KeyRelease: + is_press = false; + xkey = xev->xkey; + break; +#ifdef HAVE_XINPUT2 + case GenericEvent: + XIDeviceEvent *xiev = xev->xcookie.data; + switch (xev->xgeneric.evtype) + { + case XI_KeyPress: + is_press = true; + break; + case XI_KeyRelease: + is_press = false; + break; + default: + return; + } + + xkey.serial = xiev->serial; + xkey.send_event = xiev->send_event; + xkey.display = xiev->display; + xkey.window = xiev->event; + xkey.root = xiev->root; + xkey.subwindow = xiev->child; + xkey.time = xiev->time; + xkey.x = xiev->event_x; + xkey.y = xiev->event_y; + xkey.x_root = xiev->root_x; + xkey.y_root = xiev->root_y; + xkey.state = xiev->mods.effective; + xkey.keycode = xiev->detail; + xkey.same_screen = 1; + break; +#endif + default: + return; + } + + if (!f) + f = x_any_window_to_frame (dpyinfo, xkey.window); + if (!f) + return; + + XLookupString (&xkey, NULL, 0, &keysym, NULL); + + modifier = x_get_modifier_for_keycode (dpyinfo, xkey.keycode); + + /* Convert lowercase latin letter to uppercase. */ + if (keysym >= XK_a && keysym <= XK_z) + keysym -= XK_a - XK_A; + + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (ie); + XSETFRAME (ie.frame_or_window, f); + ie.kind = LOW_LEVEL_KEY_EVENT; + ie.timestamp = xkey.time; + ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + kbd_buffer_store_event (&ie); +} + /* Filter events for the current X input method. DPYINFO is the display this event is for. EVENT is the X event to filter. @@ -20206,6 +20341,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case KeyPress: + x_maybe_send_low_level_key_event (dpyinfo, event, any); x_display_set_last_user_time (dpyinfo, event->xkey.time, event->xkey.send_event, true); @@ -20715,6 +20851,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event, any); #ifdef HAVE_X_I18N /* Don't dispatch this event since XtDispatchEvent calls XFilterEvent, and two calls in a row may freeze the @@ -23970,6 +24107,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, struct xi_device_t *device, *source; XKeyPressedEvent xkey; + x_maybe_send_low_level_key_event (dpyinfo, event, any); + coding = Qlatin_1; /* The code under this label is quite desultory. There @@ -24586,6 +24725,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case XI_KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event, any); + #if defined HAVE_X_I18N || defined USE_GTK || defined USE_LUCID { XKeyPressedEvent xkey; @@ -32662,6 +32803,7 @@ syms_of_xterm (void) Vx_toolkit_scroll_bars = Qnil; #endif + DEFSYM (Qshift, "shift"); DEFSYM (Qmodifier_value, "modifier-value"); DEFSYM (Qctrl, "ctrl"); Fput (Qctrl, Qmodifier_value, make_fixnum (ctrl_modifier)); diff --git a/src/xterm.h b/src/xterm.h index 8d5c9917749..66e052e7acd 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -1906,6 +1906,8 @@ x_mutable_colormap (XVisualInfo *visual) extern void tear_down_x_back_buffer (struct frame *f); extern void initial_set_up_x_back_buffer (struct frame *f); +extern Lisp_Object x_get_modifier_for_keycode (struct x_display_info *, int); + /* Defined in xfns.c. */ extern void x_real_positions (struct frame *, int *, int *); extern void x_change_tab_bar_height (struct frame *, int); -- 2.35.1.windows.2 ^ permalink raw reply related [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-13 22:55 ` Cecilio Pardo @ 2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-14 9:26 ` Cecilio Pardo 2024-12-14 11:14 ` Eli Zaretskii 1 sibling, 1 reply; 93+ messages in thread From: Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-14 1:16 UTC (permalink / raw) To: Cecilio Pardo; +Cc: 74423, Eli Zaretskii, monnier Cecilio Pardo <cpardo@imayhem.com> writes: > +#ifndef HAVE_XINPUT2 > +static void > +xg_maybe_send_low_level_key_event (struct frame *f, > + GdkEvent *xev) > +{ > + GdkEventKey xkey = xev->key; > + bool is_press; > + Lisp_Object key, modifier; > + union buffered_input_event inev; > + > + if (NILP (Venable_low_level_key_events)) > + return; > + > + switch (xev->type) > + { > + case GDK_KEY_PRESS: > + is_press = true; > + break; > + case GDK_KEY_RELEASE: > + is_press = false; > + break; > + default: > + return; > + } > + > + modifier = x_get_modifier_for_keycode (FRAME_OUTPUT_DATA (f)->display_info, > + xev->key.hardware_keycode); > + > + int keysym = xkey.keyval; > + > + if (keysym >= GDK_KEY_a && keysym <= GDK_KEY_z) > + keysym -= GDK_KEY_a - GDK_KEY_A; > + > + if (!kbd_low_level_key_is_enabled (keysym, modifier)) > + return; > + > + key = make_fixnum (keysym); > + > + EVENT_INIT (inev.ie); > + XSETFRAME (inev.ie.frame_or_window, f); > + inev.ie.kind = LOW_LEVEL_KEY_EVENT; > + inev.ie.timestamp = xkey.time; > + inev.ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); > + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); > +} > +#endif Why is this !HAVE_XINPUT2? GTK input method support exists independently of XInput 2. ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-14 9:26 ` Cecilio Pardo 0 siblings, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-12-14 9:26 UTC (permalink / raw) To: Po Lu; +Cc: 74423, Eli Zaretskii, monnier On 14/12/2024 2:16, Po Lu wrote: > Cecilio Pardo <cpardo@imayhem.com> writes: > >> +#ifndef HAVE_XINPUT2 >> +static void >> +xg_maybe_send_low_level_key_event (struct frame *f, >> + GdkEvent *xev) >> +{ >> + GdkEventKey xkey = xev->key; > Why is this !HAVE_XINPUT2? GTK input method support exists > independently of XInput 2. When XInput 2 is active, we get the event through it (XI_KeyPress) first. That gives the information we need. The GTK code is used when there is no XInput and use_native_input is active. ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-13 22:55 ` Cecilio Pardo 2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-14 11:14 ` Eli Zaretskii 2024-12-18 10:59 ` Cecilio Pardo 1 sibling, 1 reply; 93+ messages in thread From: Eli Zaretskii @ 2024-12-14 11:14 UTC (permalink / raw) To: Cecilio Pardo; +Cc: luangruo, 74423, monnier > Date: Fri, 13 Dec 2024 23:55:18 +0100 > Cc: luangruo@yahoo.com, 74423@debbugs.gnu.org, monnier@iro.umontreal.ca > From: Cecilio Pardo <cpardo@imayhem.com> > > This new version includes a manual entry, and corrected doc strings and > comments. Thanks, a few minor comments. > +@cindex @code{low-level-key} event > +@item (low-level-key @var{is-key-press} @var{key} @var{modifier} @var{time} @var{frame}) > +This event is sent on the physical press or release of keys, only on GUI > +systems, currently X, MS-Windows and PGTK. Aren't these events sent only if enable-low-level-key-events is non-nil? If so, this should be documented, and the variable itself should be documented. > +@var{is-key-press} is @code{t} for a key press, @code{nil} for a key release. > +@var{time} is the event's time in milliseconds, @var{frame} is the > +frame receiving it. @var{modifier} is @code{nil} if the key is not a ^^ Two spaces between sentences. > + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, > + doc: /* If non-nil, reception of low-level key events is enabled. > + > +The value configures the set of keys that are handled: > + > +If t, send events for all keys. > + > +If a number, send events for the corresponding keysym. When calling > +'llk-init', a set of variables with the xk- prefix is initialized with ^^^^^^^^^^ In doc strings, we quote symbols `like this'. ^ permalink raw reply [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-14 11:14 ` Eli Zaretskii @ 2024-12-18 10:59 ` Cecilio Pardo 2024-12-22 4:31 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 0 siblings, 1 reply; 93+ messages in thread From: Cecilio Pardo @ 2024-12-18 10:59 UTC (permalink / raw) To: Eli Zaretskii; +Cc: luangruo, 74423, monnier [-- Attachment #1: Type: text/plain, Size: 240 bytes --] On 14/12/2024 12:14, Eli Zaretskii wrote: > Aren't these events sent only if enable-low-level-key-events is > non-nil? If so, this should be documented, and the variable itself > should be documented. Fixed this and the style mistakes. [-- Attachment #2: 0001-Send-events-for-key-presses-and-key-releases.patch --] [-- Type: text/plain, Size: 39324 bytes --] From 9e7d219543b2dd424727125910ec711f1aa6cb55 Mon Sep 17 00:00:00 2001 From: Cecilio Pardo <cpardo@imayhem.com> Date: Mon, 2 Dec 2024 17:30:42 +0100 Subject: [PATCH] Send events for key presses and key releases. Detect double/triple taps. * doc/lispref/commands.texi: Added information about new event. * src/gtkutil.c (xg_create_frame_widgets): Modified to handle key release events. (xg_maybe_send_low_level_key_event): New function that sends key events. (xg_widget_key_press_event_cb): Modified to send low level key events. (xg_widget_key_release_event_cb): New function to send low level key events on key release. * src/keyboard.c (kbd_buffer_get_event): Modified to handle low level keyboard events. (make_lispy_event): Modified to handle low level keyboard events. (kbd_low_level_key_is_enabled): New function to decide if a particular key should generate events, looking at configuration. (syms_of_keyboard): Added symbols and varialbles. (keys_of_keyboard): Modified to map low level key events to 'special-event-map'. * src/keyboard.h: Modified with function prototype. * src/pgtkterm.c (pgtk_maybe_send_low_level_key_event): New function that sends key events. (key_press_event): Modified to handle low level keyboard events. (key_release_event): Modified to handle low level keyboard events. * src/termhooks.h: Modified to define constant. * src/w32fns.c (w32_wnd_proc): Modified to generate low level key events. * src/w32term.c (w32_read_socket): Modified to generate low level key events. * src/w32term.h: Modified to define constants. * src/xterm.c (x_get_modifier_for_keycode): New function to decide which (if any) modifier corresponds to a given key. (x_maybe_send_low_level_key_event): New function that sends key events. (handle_one_xevent): Modified to handle low level key events. (syms_of_xterm): Modified to define symbol. * src/xterm.h: Modified with function prototype * lisp/low-level-key.el (New file). (llk-bindings): User bindings for low level key events. (llk-tap-count): User option. (llk-tap-timeout): User option. (llk-tap-keys): User option. (llk-keysyms): List of available keysyms. (define-xk): Macro for defining keysyms. (llk-define-keysyms): Build llk-keysyms. (llk-init): Function to initialize low level key handling. (event-is-key-press): Get field from event. (event-keysym): Get field from event. (event-modifier): Get field from event. (event-time): Get field from event. (llk-bind): Function to create a binding. (llk-events): Event history for tap detection. (llk-detect-n-tap): Function to detect taps. (describe-low-level-key): Command to get information about a key. (llk-show-event-description): Show help buffer with information about an event. (llk-handle): Handler for key events. --- doc/lispref/commands.texi | 32 ++++ lisp/low-level-key.el | 342 ++++++++++++++++++++++++++++++++++++++ src/gtkutil.c | 81 +++++++++ src/keyboard.c | 65 ++++++++ src/keyboard.h | 1 + src/pgtkterm.c | 55 ++++++ src/termhooks.h | 1 + src/w32fns.c | 11 ++ src/w32term.c | 49 ++++++ src/w32term.h | 3 +- src/xterm.c | 142 ++++++++++++++++ src/xterm.h | 2 + 12 files changed, 783 insertions(+), 1 deletion(-) create mode 100644 lisp/low-level-key.el diff --git a/doc/lispref/commands.texi b/doc/lispref/commands.texi index df8266a7157..88dd6d4baa0 100644 --- a/doc/lispref/commands.texi +++ b/doc/lispref/commands.texi @@ -2658,6 +2658,38 @@ Misc Events respectively. They are usually interpreted as being relative to the size of the object beneath the gesture: image, window, etc. +@cindex @code{low-level-key} event +@item (low-level-key @var{is-key-press} @var{key} @var{modifier} @var{time} @var{frame}) +This event is sent on the physical press or release of keys, only on GUI +systems, currently X, MS-Windows and PGTK, and only if the variable +@code{enable-low-level-key-events} has a non-@code{nil} value. See its +documentation for the values it can take, that allow to filter which +keys should generate this kind of event. + +@var{is-key-press} is @code{t} for a key press, @code{nil} for a key release. +@var{time} is the event's time in milliseconds, @var{frame} is the +frame receiving it. @var{modifier} is @code{nil} if the key is not a +modifier key, @code{t} if it is, but it is unknown which one, or one of +@code{shift}, @code{control}, @code{meta}, @code{alt}, @code{super}, +@code{hyper}. + +@var{key}, an integer, identifies the key pressed or released. This +number is platform dependent, but there are variables for most keys that +can be used in place of the numbers to identify them. For example, the +variable @code{xk-backspace} identifies the @key{backspace} key. + +The names are parallel to those for KeySyms on X, as defined in +@file{xkeysymdef.h}. For example, @code{XK_Shift_L} (the left shift +key), corresponds to @code{xk-shift-l}. The @file{xkeysymdef.h} file +defines different KeySyms for capital and small versions of letters. +For this event, only the capital version is used, with the variables +@code{xk-a}, @code{xk-b}, etc. + +These variables are initialized by calling the function @code{llk-init}. +This function also binds a handler to this event, which allows to detect +double taps on keys (normally modifier keys) and bind commands or +functions to be run. See the function @code{llk-bind}. + @cindex @code{preedit-text} event @item (preedit-text @var{arg}) This event is sent when a system input method tells Emacs to display diff --git a/lisp/low-level-key.el b/lisp/low-level-key.el new file mode 100644 index 00000000000..bc9311f82d0 --- /dev/null +++ b/lisp/low-level-key.el @@ -0,0 +1,342 @@ +;;; -*- lexical-binding: t -*- + +;; The physical-key event is like this: +;; (low-level-key IS-KEY-PRESS KEY MODIFIER TIME FRAME) +;; IS-KEY-PRESS is t if the key has been pressed, nil if it has been released. +;; KEY is the keysym number. +;; MODIFIER is the modifier associated with this key. It is nil if the key is +;; not a modifier. It can be one of the following symbols: shift, control, meta, +;; super, hyper, alt. It can also be t if the key is a modifier but it can't be +;; identified, as in the PGTK backend. +;; TIME is the timestamp in milliseconds of the event. +;; FRAME is the frame where the event happened. +;; +;; After calling 'llk-init' and setting a non-nil value for +;; 'enable-low-level-key-events', events begin to be handled by 'llk-handler', +;; which tries to detect n-taps and calls the corresponding function. +;; +;; To implement other functionalities, you can replace llk-handler with +;; your own function. + +(require 'cl-lib) + +;; User options + +(defvar llk-bindings nil + "List of bindings for low level key events (press/release/tap). + +Use the `llk-bind' function to add bindings. See its documentation for +a description of the binding information.") + +(defvar llk-tap-count 2 + "Number or key press/releases to consider a tap.") + +(defvar llk-tap-timeout 1000 + "Time (ms) between consecutive key presses/releases to consider a tap.") + +(defvar llk-tap-keys + '(xk-shift-l xk-shift-r xk-control-l xk-control-r meta) + "Keys that can generate taps.") + +(defvar llk-keysyms nil + "List of keysym numbers and their corresponding symbols. + +Each element has the form (KEYSYM . SYMBOL). The variable value for +each symbol is the keysym. This list is initialized by `llk-init'.") + +(defvar llk-describe-next-press nil + "Internal variable to mark that next key press should be described.") + +(defmacro define-xk (name x-keysym w32-keysym) + "Internal macro to define keysyms." + `(let ((ksym (pcase (window-system) + ('pgtk ,x-keysym) + ('x ,x-keysym) + ('w32 ,w32-keysym)))) + (defconst ,name ksym "Constant for a keysym value.") + (push (cons ksym ',name) llk-keysyms))) + +(defun llk-define-keysyms () + "Initialize the keysym list, `llk-keysyms'. + +Called from `llk-init'." + (setq llk-keysyms nil) + + ;; tty keys + (define-xk xk-backspace #xff08 #x08) ;; XK_BackSpace VK_BACK + (define-xk xk-tab #xff09 #x09) ;; XK_Tab VK_TAB + (define-xk xk-clear #xff0b #x0C) ;; XK_Clear VK_CLEAR + (define-xk xk-return #xff0d #x0D) ;; XK_Return VK_RETURN + (define-xk xk-pause #xff13 #x13) ;; XK_Pause VK_PAUSE + (define-xk xk-scroll-lock #xff14 #x91) ;; XK_Scroll_Lock VK_SCROLL + (define-xk xk-escape #xff1B #x1B) ;; XK_Escape VK_ESCAPE + (define-xk xk-delete #xffff #x2E) ;; XK_Delete VK_DELETE + + ;; Cursor control and motion + (define-xk xk-home #xff50 #x24) ;; XK_Home VK_HOME + (define-xk xk-left #xff51 #x25) ;; XK_Left VK_LEFT + (define-xk xk-up #xff52 #x26) ;; XK_Up VK_UP + (define-xk xk-right #xff53 #x27) ;; XK_Right VK_RIGHT + (define-xk xk-down #xff54 #x28) ;; XK_Down VK_DOWN + (define-xk xk-page-up #xff55 #x21) ;; XK_Page_Up VK_PRIOR + (define-xk xk-page-down #xff56 #x22) ;; XK_Page_Down VK_NEXT + (define-xk xk-end #xff57 #x23) ;; XK_End VK_END + (define-xk xk-begin #xff58 #x24) ;; XK_Begin VK_HOME + + ;; Special Windows keyboard keys + (define-xk xk-win-l #xFF5B #x5B) ;; XK_Win_L VK_LWIN + (define-xk xk-win-r #xFF5C #x5C) ;; XK_Win_R VK_RWIN + (define-xk xk-app #xFF5D #x5D) ;; XK_App VK_APPS + + ;; Misc functions + (define-xk xk-select #xff60 #x29) ;; XK_Select VK_SELECT + (define-xk xk-print #xff61 #x2A) ;; XK_Print VK_PRINT + (define-xk xk-insert #xff64 #x2D) ;; XK_Insert VK_INSERT + (define-xk xk-num-lock #xff7f #x90) ;; XK_Num_Lock VK_NUMLOCK + + ;; Keypad + ;; TODO: Check values for MS-Windows + (define-xk xk-kp-enter #xff8d nil) ;; XK_KP_Enter ??? + (define-xk xk-kp-multiply #xffaa nil) ;; XK_KP_Multiply ??? + (define-xk xk-kp-add #xffab nil) ;; XK_KP_Add ??? + (define-xk xk-kp-subtract #xffad nil) ;; XK_KP_Subtract ??? + (define-xk xk-kp-decimal #xffae nil) ;; XK_KP_Decimal ??? + (define-xk xk-kp-divide #xffaf nil) ;; XK_KP_Divide ??? + (define-xk xk-kp-0 #xffb0 #x60) ;; XK_KP_0 VK_NUMPAD0 + (define-xk xk-kp-1 #xffb1 #x61) ;; XK_KP_1 VK_NUMPAD1 + (define-xk xk-kp-2 #xffb2 #x62) ;; XK_KP_2 VK_NUMPAD2 + (define-xk xk-kp-3 #xffb3 #x63) ;; XK_KP_3 VK_NUMPAD3 + (define-xk xk-kp-4 #xffb4 #x64) ;; XK_KP_4 VK_NUMPAD4 + (define-xk xk-kp-5 #xffb5 #x65) ;; XK_KP_5 VK_NUMPAD5 + (define-xk xk-kp-6 #xffb6 #x66) ;; XK_KP_6 VK_NUMPAD6 + (define-xk xk-kp-7 #xffb7 #x67) ;; XK_KP_7 VK_NUMPAD7 + (define-xk xk-kp-8 #xffb8 #x68) ;; XK_KP_8 VK_NUMPAD8 + (define-xk xk-kp-9 #xffb9 #x69) ;; XK_KP_9 VK_NUMPAD9 + + ;; Function keys + (define-xk xk-f1 #xffbe #x70) ;; XK_F1 VK_F1 + (define-xk xk-f2 #xffbf #x71) ;; XK_F2 VK_F2 + (define-xk xk-f3 #xffc0 #x72) ;; XK_F3 VK_F3 + (define-xk xk-f4 #xffc1 #x73) ;; XK_F4 VK_F4 + (define-xk xk-f5 #xffc2 #x74) ;; XK_F5 VK_F5 + (define-xk xk-f6 #xffc3 #x75) ;; XK_F6 VK_F6 + (define-xk xk-f7 #xffc4 #x76) ;; XK_F7 VK_F7 + (define-xk xk-f8 #xffc5 #x77) ;; XK_F8 VK_F8 + (define-xk xk-f9 #xffc6 #x78) ;; XK_F9 VK_F9 + (define-xk xk-f10 #xffc7 #x79) ;; XK_F10 VK_F10 + (define-xk xk-f11 #xffc8 #x7A) ;; XK_F11 VK_F11 + (define-xk xk-f12 #xffc9 #x7B) ;; XK_F12 VK_F12 + (define-xk xk-f13 #xffca #x7C) ;; XK_F13 VK_F13 + (define-xk xk-f14 #xffcb #x7D) ;; XK_F14 VK_F14 + (define-xk xk-f15 #xffcc #x7E) ;; XK_F15 VK_F15 + (define-xk xk-f16 #xffcd #x7F) ;; XK_F16 VK_F16 + (define-xk xk-f17 #xffce #x80) ;; XK_F17 VK_F17 + (define-xk xk-f18 #xffcf #x81) ;; XK_F18 VK_F18 + (define-xk xk-f19 #xffd0 #x82) ;; XK_F19 VK_F19 + (define-xk xk-f20 #xffd1 #x83) ;; XK_F20 VK_F20 + (define-xk xk-f21 #xffd2 #x84) ;; XK_F21 VK_F21 + (define-xk xk-f22 #xffd3 #x85) ;; XK_F22 VK_F22 + (define-xk xk-f23 #xffd4 #x86) ;; XK_F23 VK_F23 + (define-xk xk-f24 #xffd5 #x87) ;; XK_F24 VK_F24 + + ;; Modifier keys + (define-xk xk-shift-l #xffe1 #xA0) ;; XK_Shift_L VK_LSHIFT + (define-xk xk-shift-r #xffe2 #xA1) ;; XK_Shift_R VK_RSHIFT + (define-xk xk-control-l #xffe3 #xA2) ;; XK_Control_L VK_LCONTROL + (define-xk xk-control-r #xffe4 #xA3) ;; XK_Control_R VK_RCONTROL + (define-xk xk-caps-lock #xffe5 #x14) ;; XK_Caps_Lock VK_CAPITAL + (define-xk xk-metal-l #xffe7 nil) ;; XK_Meta_L + (define-xk xk-metal-t #xffee nil) ;; XK_Meta_R + (define-xk xk-alt-l #xffe9 #xA4) ;; XK_Alt_L VK_LMENU + (define-xk xk-alt-r #xffea #xA5) ;; XK_Alt_R VK_RMENU + (define-xk xk-super-l #xffeb nil) ;; XK_Super_L + (define-xk xk-super-r #xffec nil) ;; XK_Super_R + (define-xk xk-hyper-l #xffed nil) ;; XK_Hyper_L + (define-xk xk-hyper-r #xffee nil) ;; XK_Hyper_R + + ;; Latin 1 + ;; For numbers and letters, MS-Windows does not define constant names. + ;; X11 defines distinct keysyms for lowercase and uppercase + ;; letters. We use only the uppercase ones. Events with lowercase + ;; letters are converted to uppercase. + (define-xk xk-space #x0020 #x20) ;; XK_space VK_SPACE + (define-xk xk-0 #x0030 #x30) ;; XK_0 + (define-xk xk-1 #x0031 #x31) ;; XK_1 + (define-xk xk-2 #x0032 #x32) ;; XK_2 + (define-xk xk-3 #x0033 #x33) ;; XK_3 + (define-xk xk-4 #x0034 #x34) ;; XK_4 + (define-xk xk-5 #x0035 #x35) ;; XK_5 + (define-xk xk-6 #x0036 #x36) ;; XK_6 + (define-xk xk-7 #x0037 #x37) ;; XK_7 + (define-xk xk-8 #x0038 #x38) ;; XK_8 + (define-xk xk-9 #x0039 #x39) ;; XK_9 + (define-xk xk-a #x0041 #x41) ;; XK_A + (define-xk xk-b #x0042 #x42) ;; XK_B + (define-xk xk-c #x0043 #x43) ;; XK_C + (define-xk xk-d #x0044 #x44) ;; XK_D + (define-xk xk-e #x0045 #x45) ;; XK_E + (define-xk xk-f #x0046 #x46) ;; XK_F + (define-xk xk-g #x0047 #x47) ;; XK_G + (define-xk xk-h #x0048 #x48) ;; XK_H + (define-xk xk-i #x0049 #x49) ;; XK_I + (define-xk xk-j #x004A #x4A) ;; XK_J + (define-xk xk-k #x004B #x4B) ;; XK_K + (define-xk xk-l #x004C #x4C) ;; XK_L + (define-xk xk-m #x004D #x4D) ;; XK_M + (define-xk xk-n #x004E #x4E) ;; XK_N + (define-xk xk-o #x004F #x4F) ;; XK_O + (define-xk xk-p #x0050 #x50) ;; XK_P + (define-xk xk-q #x0051 #x51) ;; XK_Q + (define-xk xk-r #x0052 #x52) ;; XK_R + (define-xk xk-s #x0053 #x53) ;; XK_S + (define-xk xk-t #x0054 #x54) ;; XK_T + (define-xk xk-u #x0055 #x55) ;; XK_U + (define-xk xk-v #x0056 #x56) ;; XK_V + (define-xk xk-w #x0057 #x57) ;; XK_W + (define-xk xk-x #x0058 #x58) ;; XK_X + (define-xk xk-y #x0059 #x59) ;; XK_Y + (define-xk xk-z #x005A #x5A));; XK_Z + +(defun llk-init () + "Initialize low level key events. + +Fills the `llk-keysyms' list, and binds the `low-level-key' event +to the `llk-handle' function. Resets the `llk-bindings' list. +Besides calling this function, you need to set `enable-low-level-key-events' +to a non-nil value." + (interactive) + (llk-define-keysyms) + (define-key special-event-map [low-level-key] 'llk-handle) + (setq llk-bindings nil)) + +(defsubst event-is-key-press (event) + "Return the value of the IS-KEY-PRESS field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 1 event))) + +(defsubst event-keysym (event) + "Return the value of the KEY field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 2 event))) + +(defsubst event-modifier (event) + "Return the value of the MODIFIER field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 3 event))) + +(defsubst event-time (event) + "Return the value of the TIME field of the EVENT, a low level key event." + (declare (side-effect-free t)) + (if (consp event) (nth 4 event))) + +;; For example: +;; Bind key tap to command +;; (llk-bind 'tap 'xk-shift-l 'delete-other-windows) +;; Bind modifier tap to command +;; (llk-bind 'tap 'shift 'delete-other-windows) +;; Bind tap to hyper modifier +;; (llk-bind 'tap 'xk-shift-r (lambda () +;; (message "H-...") +;; (setq unread-command-events +;; (append (event-apply-hyper-modifier nil) nil)))) +;; Can bind to a command or function +(defun llk-bind (action key function) + "Bind a command or function to a low level key event. + +The only action supported currently is `tap'. The key can be a keysym +symbol, or a modifier symbol (shift, control, alt, meta, hyper, super). +If there is no keysym symbol for a key, use the keysym number." + (push (list action key function) llk-bindings)) + +;; We store the last events (key/modifier is-press timestamp) here to +;; test for multitap. +(defvar llk-events nil + "Internal variable for detecting taps.") + +;; If positive, return key (xk-shift-l, etc) else return nil. +(defun llk-detect-n-tap (n timeout) + "Internal function to detect n-tap keys." + (let (key + (is-press (event-is-key-press last-input-event)) + ;; convert number to keysym symbol + (keysym (cdr (assoc (event-keysym last-input-event) llk-keysyms))) + (timestamp (event-time last-input-event)) + (modifier (event-modifier last-input-event))) + + ;; if ehte is no symbol for this key, use its keysym number + (unless keysym (setq keysym (event-keysym last-input-event))) + + ;; look in llk-tap-keys for the key, then the modifier + (if (member keysym llk-tap-keys) + (setq key keysym) + (if (member modifier llk-tap-keys) + (setq key modifier))) + + (if (not key) + ;; Key not in tap list, clear history + (setq llk-events nil) + ;; Clear it also if the first element is from a different key + (and llk-events + (not (equal (car (car llk-events)) key)) + (setq llk-events nil)) + (push (list key is-press timestamp) llk-events) + ;; Only care about last 2xN events + (ntake (* 2 n) llk-events) + ;; If we have: + ;; - Exactly 2 * n events. + ;; - down, up, down, up, ... + ;; - not two much time between first and last + (and (eq (* 2 n) (length llk-events)) + (cl-every 'eq + (ntake (* 2 n) + (list nil t nil t nil t nil t + nil t nil t nil t nil t)) + (mapcar 'cl-second llk-events)) + (< (- (cl-third (cl-first llk-events)) + (cl-third (car (last llk-events)))) + timeout) + (progn + (setq llk-events nil) + key))))) + +(defun describe-low-level-key () + "Wait for key press and describe the low level key event it generates." + (interactive) + (setq llk-describe-next-press t)) + +(defun llk-show-event-description () + "Show information about the last low level key event." + (setq llk-describe-next-press nil) + (with-help-window (help-buffer) + (insert "\n") + (let* ((xk (event-keysym last-input-event)) + (sym (assoc xk llk-keysyms))) + (insert (format "Keysym number: %d (#x%X),\n" xk xk)) + (if sym + (insert (format "which corresponds to named key %s.\n\n" (cdr sym))) + (insert "which does not correspond to any known named key.\n\n")) + (if (event-modifier last-input-event) + (insert (format "This key corresponds to the %s modifier.\n\n" + (event-modifier last-input-event))) + (insert "This key does not correspond to a modifier.\n\n")) + (insert "See the value of the `llk-keysyms' variable for a list of known keys.\n")))) + +(defun llk-handle () + "Internal function to handle low level key events." + (interactive) + (if (and (event-is-key-press last-input-event) + llk-describe-next-press) + (llk-show-event-description) + (let ((tap-key (llk-detect-n-tap + llk-tap-count + llk-tap-timeout))) + (when tap-key + (let ((func (cl-third + (seq-find + (lambda (b) + (and (eq (cl-first b) 'tap) + (eq (cl-second b) tap-key))) + llk-bindings)))) + (cond + ((commandp func) (call-interactively func)) + ((functionp func) (funcall func)))))))) diff --git a/src/gtkutil.c b/src/gtkutil.c index d57627f152f..86d4321cf35 100644 --- a/src/gtkutil.c +++ b/src/gtkutil.c @@ -98,6 +98,7 @@ G_DEFINE_TYPE (EmacsMenuBar, emacs_menu_bar, GTK_TYPE_MENU_BAR) static void xg_im_context_preedit_changed (GtkIMContext *, gpointer); static void xg_im_context_preedit_end (GtkIMContext *, gpointer); static bool xg_widget_key_press_event_cb (GtkWidget *, GdkEvent *, gpointer); +static bool xg_widget_key_release_event_cb (GtkWidget *, GdkEvent *, gpointer); #endif #if GTK_CHECK_VERSION (3, 10, 0) @@ -1749,6 +1750,12 @@ xg_create_frame_widgets (struct frame *f) g_signal_connect (G_OBJECT (wfixed), "key-press-event", G_CALLBACK (xg_widget_key_press_event_cb), NULL); + + g_signal_connect (G_OBJECT (wfixed), "key-release-event", + G_CALLBACK (xg_widget_key_release_event_cb), + NULL); + + #endif { @@ -6376,6 +6383,53 @@ xg_im_context_preedit_end (GtkIMContext *imc, gpointer user_data) kbd_buffer_store_event (&inev); } +#ifndef HAVE_XINPUT2 +static void +xg_maybe_send_low_level_key_event (struct frame *f, + GdkEvent *xev) +{ + GdkEventKey xkey = xev->key; + bool is_press; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (xev->type) + { + case GDK_KEY_PRESS: + is_press = true; + break; + case GDK_KEY_RELEASE: + is_press = false; + break; + default: + return; + } + + modifier = x_get_modifier_for_keycode (FRAME_OUTPUT_DATA (f)->display_info, + xev->key.hardware_keycode); + + int keysym = xkey.keyval; + + if (keysym >= GDK_KEY_a && keysym <= GDK_KEY_z) + keysym -= GDK_KEY_a - GDK_KEY_A; + + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = xkey.time; + inev.ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + kbd_buffer_store_buffered_event (&inev, &xg_pending_quit_event); +} +#endif + static bool xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) @@ -6404,6 +6458,10 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, if (!f) return true; +#ifndef HAVE_XINPUT2 + xg_maybe_send_low_level_key_event (f, event); +#endif + if (popup_activated ()) return true; @@ -6557,6 +6615,29 @@ xg_widget_key_press_event_cb (GtkWidget *widget, GdkEvent *event, return true; } +static bool +xg_widget_key_release_event_cb (GtkWidget *widget, GdkEvent *event, + gpointer user_data) +{ +#ifndef HAVE_XINPUT2 + Lisp_Object tail, tem; + struct frame *f = NULL; + + FOR_EACH_FRAME (tail, tem) + { + if (FRAME_X_P (XFRAME (tem)) + && (FRAME_GTK_WIDGET (XFRAME (tem)) == widget)) + { + f = XFRAME (tem); + break; + } + } + if (f) + xg_maybe_send_low_level_key_event (f, event); +#endif + return true; +} + bool xg_filter_key (struct frame *frame, XEvent *xkey) { diff --git a/src/keyboard.c b/src/keyboard.c index 6d28dca9aeb..eaa84f50327 100644 --- a/src/keyboard.c +++ b/src/keyboard.c @@ -4274,6 +4274,7 @@ kbd_buffer_get_event (KBOARD **kbp, case CONFIG_CHANGED_EVENT: case FOCUS_OUT_EVENT: case SELECT_WINDOW_EVENT: + case LOW_LEVEL_KEY_EVENT: { obj = make_lispy_event (&event->ie); kbd_fetch_ptr = next_kbd_event (event); @@ -7118,12 +7119,47 @@ make_lispy_event (struct input_event *event) case PREEDIT_TEXT_EVENT: return list2 (Qpreedit_text, event->arg); + case LOW_LEVEL_KEY_EVENT: + return listn (6, Qlow_level_key, + XCAR (event->arg), /* Press or release. */ + XCAR (XCDR (event->arg)), /* The key symbol. */ + XCAR (XCDR (XCDR (event->arg))), /* The modifier. */ + make_fixnum (event->timestamp), + event->frame_or_window); + /* The 'kind' field of the event is something we don't recognize. */ default: emacs_abort (); } } +bool +kbd_low_level_key_is_enabled (int keysym, Lisp_Object modifier) +{ + if (Venable_low_level_key_events == Qt) + return true; + + if (Venable_low_level_key_events == Qnil) + return false; + + if (FIXNUMP (Venable_low_level_key_events)) + return keysym == XFIXNUM (Venable_low_level_key_events); + + if (Venable_low_level_key_events == Qmodifiers) + return modifier != Qnil; + + for (Lisp_Object e = Venable_low_level_key_events; CONSP (e); e = XCDR (e)) + { + Lisp_Object c = XCAR (e); + if (FIXNUMP (c) && XFIXNUM (c) == keysym) + return true; + if (c == Qmodifiers && modifier != Qnil) + return true; + } + + return false; +} + static Lisp_Object make_lispy_movement (struct frame *frame, Lisp_Object bar_window, enum scroll_bar_part part, Lisp_Object x, Lisp_Object y, Time t) @@ -12931,6 +12967,33 @@ syms_of_keyboard (void) DEFSYM (Qfile_notify, "file-notify"); #endif /* USE_FILE_NOTIFY */ + + DEFSYM (Qmodifiers, "modifiers"); + + DEFVAR_LISP ("enable-low-level-key-events", Venable_low_level_key_events, + doc: /* If non-nil, reception of low-level key events is enabled. + +The value configures the set of keys that are handled: + +If t, send events for all keys. + +If a number, send events for the corresponding keysym. When calling +`llk-init', a set of variables with the xk- prefix is initialized with +the numeric values for keysyms. Because this number are platform +dependent, only the variables should be used to refer to a key. For +example, the 'xk-backspace' variable refer to the backspace key, with +the numeric value 0xff08 on X, and the 0x08 on MS-Windows. You can see +all defined variables on the variable `llk-keysyms'. + +If a symbol, a predefined set of keys is selected. The only currently +valid symbol is 'modifiers. + +If a list of numbers and/or symbols, the corresponding keysyms and sets +are selected. */); + Venable_low_level_key_events = Qnil; + + DEFSYM (Qlow_level_key, "low-level-key"); + DEFSYM (Qtouch_end, "touch-end"); /* Menu and tool bar item parts. */ @@ -14018,6 +14081,8 @@ keys_of_keyboard (void) "handle-focus-out"); initial_define_lispy_key (Vspecial_event_map, "move-frame", "handle-move-frame"); + initial_define_lispy_key (Vspecial_event_map, "low-level-key", + "ignore"); } /* Mark the pointers in the kboard objects. diff --git a/src/keyboard.h b/src/keyboard.h index 387501c9f88..83f9a0f141a 100644 --- a/src/keyboard.h +++ b/src/keyboard.h @@ -511,6 +511,7 @@ kbd_buffer_store_event_hold (struct input_event *event, extern Lisp_Object menu_item_eval_property (Lisp_Object); extern bool kbd_buffer_events_waiting (void); extern void add_user_signal (int, const char *); +extern bool kbd_low_level_key_is_enabled (int, Lisp_Object); extern int tty_read_avail_input (struct terminal *, struct input_event *); extern struct timespec timer_check (void); diff --git a/src/pgtkterm.c b/src/pgtkterm.c index 079945126e0..fba81f5ec0e 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -5201,6 +5201,56 @@ pgtk_enqueue_preedit (struct frame *f, Lisp_Object preedit) evq_enqueue (&inev); } +static void +pgtk_maybe_send_low_level_key_event (struct frame *f, GdkEvent *event) +{ + GdkEventKey xkey = event->key; + bool is_press; + Lisp_Object key, modifier; + union buffered_input_event inev; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (event->type) + { + case GDK_KEY_PRESS: + is_press = true; + break; + case GDK_KEY_RELEASE: + is_press = false; + break; + default: + return; + } + + /* We don't support modifier identification on PGTK. We only can tell + if the key corresponds to a modifier or not, which is used for + filtering enabled keys with kbd_low_level_key_is_enabled. */ + modifier = event->key.is_modifier ? Qt : Qnil; + + int keysym = xkey.keyval; + + if (keysym >= GDK_KEY_a && keysym <= GDK_KEY_z) + keysym -= GDK_KEY_a - GDK_KEY_A; + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + if (!f) + f = pgtk_any_window_to_frame (event->key.window); + if (!f) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (inev.ie); + XSETFRAME (inev.ie.frame_or_window, f); + inev.ie.kind = LOW_LEVEL_KEY_EVENT; + inev.ie.timestamp = event->key.time; + inev.ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + evq_enqueue (&inev); +} + static gboolean key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { @@ -5211,6 +5261,9 @@ key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) struct pgtk_display_info *dpyinfo; f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); + + pgtk_maybe_send_low_level_key_event (f, event); + EVENT_INIT (inev.ie); hlinfo = MOUSE_HL_INFO (f); nbytes = 0; @@ -5454,6 +5507,8 @@ key_release_event (GtkWidget *widget, GdkDisplay *display; struct pgtk_display_info *dpyinfo; + pgtk_maybe_send_low_level_key_event (NULL, event); + display = gtk_widget_get_display (widget); dpyinfo = pgtk_display_info_for_display (display); diff --git a/src/termhooks.h b/src/termhooks.h index d6a9300bac9..966a6492f69 100644 --- a/src/termhooks.h +++ b/src/termhooks.h @@ -347,6 +347,7 @@ #define EMACS_TERMHOOKS_H /* In a NOTIFICATION_EVENT, .arg is a lambda to evaluate. */ , NOTIFICATION_EVENT #endif /* HAVE_ANDROID */ + , LOW_LEVEL_KEY_EVENT }; /* Bit width of an enum event_kind tag at the start of structs and unions. */ diff --git a/src/w32fns.c b/src/w32fns.c index e2455b9271e..1d18bf408e1 100644 --- a/src/w32fns.c +++ b/src/w32fns.c @@ -4669,6 +4669,11 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) case WM_KEYUP: case WM_SYSKEYUP: record_keyup (wParam, lParam); + if (!NILP (Venable_low_level_key_events)) + { + signal_user_input (); + my_post_msg (&wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam); + } goto dflt; case WM_KEYDOWN: @@ -4695,6 +4700,12 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) if (w32_use_fallback_wm_chars_method) wParam = map_keypad_keys (wParam, (lParam & 0x1000000L) != 0); + if (!NILP (Venable_low_level_key_events)) + { + signal_user_input (); + my_post_msg (&wmsg, hwnd, WM_EMACS_LOW_LEVEL_KEY, wParam, lParam); + } + windows_translate = 0; switch (wParam) diff --git a/src/w32term.c b/src/w32term.c index e18f39dd2a8..a2e0a4b0fa0 100644 --- a/src/w32term.c +++ b/src/w32term.c @@ -5270,6 +5270,55 @@ w32_read_socket (struct terminal *terminal, } break; + case WM_EMACS_LOW_LEVEL_KEY: + WORD key_flags = HIWORD (msg.msg.lParam); + BOOL is_wm_keyup = key_flags & KF_UP; + + if (is_wm_keyup || (key_flags & KF_REPEAT) == 0) /* WM_KEYDOWN, not repeating. */ + { + WORD scan_code = LOBYTE (key_flags); + if (key_flags & KF_EXTENDED) + scan_code = MAKEWORD (scan_code, 0xE0); + + UINT translated = MapVirtualKey (scan_code, MAPVK_VSC_TO_VK_EX); + WORD vk = LOWORD (msg.msg.wParam); + if (translated) + vk = LOWORD (translated); + + Lisp_Object key = make_fixnum (vk); + Lisp_Object modifier = Qnil; + + switch (vk) + { + case VK_LSHIFT: + case VK_RSHIFT: + modifier = Qshift; + break; + case VK_LCONTROL: + case VK_RCONTROL: + modifier = Qctrl; + break; + case VK_LMENU: + case VK_RMENU: + modifier = Qmeta; + break; + } + + if (kbd_low_level_key_is_enabled (vk, modifier)) + { + f = w32_window_to_frame (dpyinfo, msg.msg.hwnd); + inev.kind = LOW_LEVEL_KEY_EVENT; + XSETFRAME (inev.frame_or_window, f); + inev.timestamp = msg.msg.time; + inev.arg = list3 (is_wm_keyup ? Qnil : Qt, key, modifier); + kbd_buffer_store_event_hold (&inev, hold_quit); + } + + inev.kind = NO_EVENT; + + } + break; + case WM_UNICHAR: case WM_SYSCHAR: case WM_CHAR: diff --git a/src/w32term.h b/src/w32term.h index cad9fcf8cb1..88f7dfeef8b 100644 --- a/src/w32term.h +++ b/src/w32term.h @@ -713,7 +713,8 @@ #define WM_EMACS_FILENOTIFY (WM_EMACS_START + 25) #define WM_EMACS_IME_STATUS (WM_EMACS_START + 26) #define WM_EMACS_DRAGOVER (WM_EMACS_START + 27) #define WM_EMACS_DROP (WM_EMACS_START + 28) -#define WM_EMACS_END (WM_EMACS_START + 29) +#define WM_EMACS_LOW_LEVEL_KEY (WM_EMACS_START + 29) +#define WM_EMACS_END (WM_EMACS_START + 30) #define WND_FONTWIDTH_INDEX (0) #define WND_LINEHEIGHT_INDEX (4) diff --git a/src/xterm.c b/src/xterm.c index 0c20d38b0f7..72715e0ed73 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -17840,6 +17840,141 @@ #define STORE_KEYSYM_FOR_DEBUG(keysym) ((void)0) static struct x_display_info *next_noop_dpyinfo; +Lisp_Object +x_get_modifier_for_keycode (struct x_display_info *dpyinfo, + int keycode) +{ +#ifdef HAVE_XKB + if (dpyinfo->xkb_desc) + for (int mod = 0; mod < XkbNumModifiers; mod++) + { + int mask = (1 << mod); + if (dpyinfo->xkb_desc->map->modmap[keycode] & mask) + { + if (mask == ShiftMask) + return Qshift; + if (mask == ControlMask) + return Qctrl; + if (mask == dpyinfo->meta_mod_mask) + return Qmeta; + if (mask == dpyinfo->alt_mod_mask) + return Qalt; + if (mask == dpyinfo->super_mod_mask) + return Qsuper; + if (mask == dpyinfo->hyper_mod_mask) + return Qhyper; + } + } +#endif + XModifierKeymap *map = dpyinfo->modmap; + if (map) + for (int mod = 0; mod < 8; mod++) + { + int mask = (1 << mod); + for (int key = 0; key < map->max_keypermod; key++) + if (map->modifiermap[mod * map->max_keypermod + key] == keycode) + { + if (mask == ShiftMask) + return Qshift; + if (mask == ControlMask) + return Qctrl; + if (mask == dpyinfo->meta_mod_mask) + return Qmeta; + if (mask == dpyinfo->alt_mod_mask) + return Qalt; + if (mask == dpyinfo->super_mod_mask) + return Qsuper; + if (mask == dpyinfo->hyper_mod_mask) + return Qhyper; + } + } + return Qnil; +} + +static void +x_maybe_send_low_level_key_event (struct x_display_info *dpyinfo, + const XEvent *xev, struct frame *f) +{ + XKeyEvent xkey; + bool is_press; + KeySym keysym; + Lisp_Object key, modifier; + struct input_event ie; + + if (NILP (Venable_low_level_key_events)) + return; + + switch (xev->type) + { + case KeyPress: + is_press = true; + xkey = xev->xkey; + break; + case KeyRelease: + is_press = false; + xkey = xev->xkey; + break; +#ifdef HAVE_XINPUT2 + case GenericEvent: + XIDeviceEvent *xiev = xev->xcookie.data; + switch (xev->xgeneric.evtype) + { + case XI_KeyPress: + is_press = true; + break; + case XI_KeyRelease: + is_press = false; + break; + default: + return; + } + + xkey.serial = xiev->serial; + xkey.send_event = xiev->send_event; + xkey.display = xiev->display; + xkey.window = xiev->event; + xkey.root = xiev->root; + xkey.subwindow = xiev->child; + xkey.time = xiev->time; + xkey.x = xiev->event_x; + xkey.y = xiev->event_y; + xkey.x_root = xiev->root_x; + xkey.y_root = xiev->root_y; + xkey.state = xiev->mods.effective; + xkey.keycode = xiev->detail; + xkey.same_screen = 1; + break; +#endif + default: + return; + } + + if (!f) + f = x_any_window_to_frame (dpyinfo, xkey.window); + if (!f) + return; + + XLookupString (&xkey, NULL, 0, &keysym, NULL); + + modifier = x_get_modifier_for_keycode (dpyinfo, xkey.keycode); + + /* Convert lowercase latin letter to uppercase. */ + if (keysym >= XK_a && keysym <= XK_z) + keysym -= XK_a - XK_A; + + if (!kbd_low_level_key_is_enabled (keysym, modifier)) + return; + + key = make_fixnum (keysym); + + EVENT_INIT (ie); + XSETFRAME (ie.frame_or_window, f); + ie.kind = LOW_LEVEL_KEY_EVENT; + ie.timestamp = xkey.time; + ie.arg = list3 (is_press ? Qt : Qnil, key, modifier); + kbd_buffer_store_event (&ie); +} + /* Filter events for the current X input method. DPYINFO is the display this event is for. EVENT is the X event to filter. @@ -20206,6 +20341,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, goto OTHER; case KeyPress: + x_maybe_send_low_level_key_event (dpyinfo, event, any); x_display_set_last_user_time (dpyinfo, event->xkey.time, event->xkey.send_event, true); @@ -20715,6 +20851,7 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event, any); #ifdef HAVE_X_I18N /* Don't dispatch this event since XtDispatchEvent calls XFilterEvent, and two calls in a row may freeze the @@ -23970,6 +24107,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, struct xi_device_t *device, *source; XKeyPressedEvent xkey; + x_maybe_send_low_level_key_event (dpyinfo, event, any); + coding = Qlatin_1; /* The code under this label is quite desultory. There @@ -24586,6 +24725,8 @@ handle_one_xevent (struct x_display_info *dpyinfo, #endif case XI_KeyRelease: + x_maybe_send_low_level_key_event (dpyinfo, event, any); + #if defined HAVE_X_I18N || defined USE_GTK || defined USE_LUCID { XKeyPressedEvent xkey; @@ -32662,6 +32803,7 @@ syms_of_xterm (void) Vx_toolkit_scroll_bars = Qnil; #endif + DEFSYM (Qshift, "shift"); DEFSYM (Qmodifier_value, "modifier-value"); DEFSYM (Qctrl, "ctrl"); Fput (Qctrl, Qmodifier_value, make_fixnum (ctrl_modifier)); diff --git a/src/xterm.h b/src/xterm.h index 8d5c9917749..66e052e7acd 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -1906,6 +1906,8 @@ x_mutable_colormap (XVisualInfo *visual) extern void tear_down_x_back_buffer (struct frame *f); extern void initial_set_up_x_back_buffer (struct frame *f); +extern Lisp_Object x_get_modifier_for_keycode (struct x_display_info *, int); + /* Defined in xfns.c. */ extern void x_real_positions (struct frame *, int *, int *); extern void x_change_tab_bar_height (struct frame *, int); -- 2.35.1.windows.2 ^ permalink raw reply related [flat|nested] 93+ messages in thread
* bug#74423: Low level key events 2024-12-18 10:59 ` Cecilio Pardo @ 2024-12-22 4:31 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 0 siblings, 0 replies; 93+ messages in thread From: Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors @ 2024-12-22 4:31 UTC (permalink / raw) To: Cecilio Pardo; +Cc: luangruo, 74423, Eli Zaretskii Hi, this is shaping up quite nicely, AFAICT. I'm not expert in the C level part of that code, so I provide feedback below only for the Lisp-facing part. Stefan > --- a/doc/lispref/commands.texi > +++ b/doc/lispref/commands.texi > @@ -2658,6 +2658,38 @@ Misc Events > respectively. They are usually interpreted as being relative to the > size of the object beneath the gesture: image, window, etc. > > +@cindex @code{low-level-key} event > +@item (low-level-key @var{is-key-press} @var{key} @var{modifier} @var{time} @var{frame}) > +This event is sent on the physical press or release of keys, only on GUI > +systems, currently X, MS-Windows and PGTK, and only if the variable > +@code{enable-low-level-key-events} has a non-@code{nil} value. See its > +documentation for the values it can take, that allow to filter which > +keys should generate this kind of event. Rather than saying it's sent "only on GUI systems", I'd say sent "only on systems where it is supported": I wouldn't be surprised to hear that some terminal emulators offer ways to support it. > +@var{is-key-press} is @code{t} for a key press, @code{nil} for a key release. > +@var{time} is the event's time in milliseconds, @var{frame} is the > +frame receiving it. Please clarify the origin of the event's time (or if possible make it a valid/normal Emacs time object). > +@var{key}, an integer, identifies the key pressed or released. This > +number is platform dependent, but there are variables for most keys that > +can be used in place of the numbers to identify them. For example, the > +variable @code{xk-backspace} identifies the @key{backspace} key. I get the impression that those variables/constants may be replaced/complemented in the future with alternatives, such as functions that convert the key number to a symbol, or functions that test whether the key number is within some set of keys described as symbols, ... So the documentation of the meaning of those key numbers via associated variables/constants should probably be placed elsewhere. > +These variables are initialized by calling the function @code{llk-init}. > +This function also binds a handler to this event, which allows to detect [ "Allow" is transitive, so you need to say "allows us" or "allows users" or something like (or use some other formulation altogether). ] > --- /dev/null > +++ b/lisp/low-level-key.el > @@ -0,0 +1,342 @@ > +;;; -*- lexical-binding: t -*- Please follow our convention for the first line of ELisp files, and then include our usual copyright+license blurb. > +;; The physical-key event is like this: > +;; (low-level-key IS-KEY-PRESS KEY MODIFIER TIME FRAME) > +;; IS-KEY-PRESS is t if the key has been pressed, nil if it has been released. > +;; KEY is the keysym number. > +;; MODIFIER is the modifier associated with this key. It is nil if the key is > +;; not a modifier. It can be one of the following symbols: shift, control, meta, > +;; super, hyper, alt. It can also be t if the key is a modifier but it can't be > +;; identified, as in the PGTK backend. > +;; TIME is the timestamp in milliseconds of the event. > +;; FRAME is the frame where the event happened. I suggest you replace that text with a `cl-defstruct` (where you can include the same information but in a way that's available via `cl-describe-type`). And then you can stop using `cl-third` and use proper accessors instead. > +(defvar llk-bindings nil > + "List of bindings for low level key events (press/release/tap). > + > +Use the `llk-bind' function to add bindings. See its documentation for > +a description of the binding information.") AFAICT this is a list of elements of the form `(tab KEY FUNCTION)`. Why not drop the `tap` and make it a "normal" keymap? > +(defvar llk-tap-count 2 > + "Number or key press/releases to consider a tap.") Shouldn't `llk-tap-count` be specified individually for each binding? > +(defvar llk-tap-timeout 1000 > + "Time (ms) between consecutive key presses/releases to consider a tap.") Default it to `mouse-double-click-time` (and maybe double it for triple taps)? > +(defvar llk-tap-keys > + '(xk-shift-l xk-shift-r xk-control-l xk-control-r meta) > + "Keys that can generate taps.") This seems redundant with `llk-bindings`. > +(defvar llk-describe-next-press nil > + "Internal variable to mark that next key press should be described.") Use a "--" in the var's name for internal variables. > +(defmacro define-xk (name x-keysym w32-keysym) > + "Internal macro to define keysyms." Same thing for macros. > + `(let ((ksym (pcase (window-system) > + ('pgtk ,x-keysym) > + ('x ,x-keysym) > + ('w32 ,w32-keysym)))) You can combine the first two branches into: ((or 'pgtk 'x) ,x-keysym) > + ;; tty keys > + (define-xk xk-backspace #xff08 #x08) ;; XK_BackSpace VK_BACK > + (define-xk xk-tab #xff09 #x09) ;; XK_Tab VK_TAB > + (define-xk xk-clear #xff0b #x0C) ;; XK_Clear VK_CLEAR > + (define-xk xk-return #xff0d #x0D) ;; XK_Return VK_RETURN > + (define-xk xk-pause #xff13 #x13) ;; XK_Pause VK_PAUSE > + (define-xk xk-scroll-lock #xff14 #x91) ;; XK_Scroll_Lock VK_SCROLL > + (define-xk xk-escape #xff1B #x1B) ;; XK_Escape VK_ESCAPE > + (define-xk xk-delete #xffff #x2E) ;; XK_Delete VK_DELETE > + > + ;; Cursor control and motion > + (define-xk xk-home #xff50 #x24) ;; XK_Home VK_HOME > + (define-xk xk-left #xff51 #x25) ;; XK_Left VK_LEFT > + (define-xk xk-up #xff52 #x26) ;; XK_Up VK_UP > + (define-xk xk-right #xff53 #x27) ;; XK_Right VK_RIGHT > + (define-xk xk-down #xff54 #x28) ;; XK_Down VK_DOWN > + (define-xk xk-page-up #xff55 #x21) ;; XK_Page_Up VK_PRIOR > + (define-xk xk-page-down #xff56 #x22) ;; XK_Page_Down VK_NEXT > + (define-xk xk-end #xff57 #x23) ;; XK_End VK_END > + (define-xk xk-begin #xff58 #x24) ;; XK_Begin VK_HOME > + > + ;; Special Windows keyboard keys > + (define-xk xk-win-l #xFF5B #x5B) ;; XK_Win_L VK_LWIN > + (define-xk xk-win-r #xFF5C #x5C) ;; XK_Win_R VK_RWIN > + (define-xk xk-app #xFF5D #x5D) ;; XK_App VK_APPS > + > + ;; Misc functions > + (define-xk xk-select #xff60 #x29) ;; XK_Select VK_SELECT > + (define-xk xk-print #xff61 #x2A) ;; XK_Print VK_PRINT > + (define-xk xk-insert #xff64 #x2D) ;; XK_Insert VK_INSERT > + (define-xk xk-num-lock #xff7f #x90) ;; XK_Num_Lock VK_NUMLOCK > + > + ;; Keypad > + ;; TODO: Check values for MS-Windows > + (define-xk xk-kp-enter #xff8d nil) ;; XK_KP_Enter ??? > + (define-xk xk-kp-multiply #xffaa nil) ;; XK_KP_Multiply ??? > + (define-xk xk-kp-add #xffab nil) ;; XK_KP_Add ??? > + (define-xk xk-kp-subtract #xffad nil) ;; XK_KP_Subtract ??? > + (define-xk xk-kp-decimal #xffae nil) ;; XK_KP_Decimal ??? > + (define-xk xk-kp-divide #xffaf nil) ;; XK_KP_Divide ??? > + (define-xk xk-kp-0 #xffb0 #x60) ;; XK_KP_0 VK_NUMPAD0 > + (define-xk xk-kp-1 #xffb1 #x61) ;; XK_KP_1 VK_NUMPAD1 > + (define-xk xk-kp-2 #xffb2 #x62) ;; XK_KP_2 VK_NUMPAD2 > + (define-xk xk-kp-3 #xffb3 #x63) ;; XK_KP_3 VK_NUMPAD3 > + (define-xk xk-kp-4 #xffb4 #x64) ;; XK_KP_4 VK_NUMPAD4 > + (define-xk xk-kp-5 #xffb5 #x65) ;; XK_KP_5 VK_NUMPAD5 > + (define-xk xk-kp-6 #xffb6 #x66) ;; XK_KP_6 VK_NUMPAD6 > + (define-xk xk-kp-7 #xffb7 #x67) ;; XK_KP_7 VK_NUMPAD7 > + (define-xk xk-kp-8 #xffb8 #x68) ;; XK_KP_8 VK_NUMPAD8 > + (define-xk xk-kp-9 #xffb9 #x69) ;; XK_KP_9 VK_NUMPAD9 > + > + ;; Function keys > + (define-xk xk-f1 #xffbe #x70) ;; XK_F1 VK_F1 > + (define-xk xk-f2 #xffbf #x71) ;; XK_F2 VK_F2 > + (define-xk xk-f3 #xffc0 #x72) ;; XK_F3 VK_F3 > + (define-xk xk-f4 #xffc1 #x73) ;; XK_F4 VK_F4 > + (define-xk xk-f5 #xffc2 #x74) ;; XK_F5 VK_F5 > + (define-xk xk-f6 #xffc3 #x75) ;; XK_F6 VK_F6 > + (define-xk xk-f7 #xffc4 #x76) ;; XK_F7 VK_F7 > + (define-xk xk-f8 #xffc5 #x77) ;; XK_F8 VK_F8 > + (define-xk xk-f9 #xffc6 #x78) ;; XK_F9 VK_F9 > + (define-xk xk-f10 #xffc7 #x79) ;; XK_F10 VK_F10 > + (define-xk xk-f11 #xffc8 #x7A) ;; XK_F11 VK_F11 > + (define-xk xk-f12 #xffc9 #x7B) ;; XK_F12 VK_F12 > + (define-xk xk-f13 #xffca #x7C) ;; XK_F13 VK_F13 > + (define-xk xk-f14 #xffcb #x7D) ;; XK_F14 VK_F14 > + (define-xk xk-f15 #xffcc #x7E) ;; XK_F15 VK_F15 > + (define-xk xk-f16 #xffcd #x7F) ;; XK_F16 VK_F16 > + (define-xk xk-f17 #xffce #x80) ;; XK_F17 VK_F17 > + (define-xk xk-f18 #xffcf #x81) ;; XK_F18 VK_F18 > + (define-xk xk-f19 #xffd0 #x82) ;; XK_F19 VK_F19 > + (define-xk xk-f20 #xffd1 #x83) ;; XK_F20 VK_F20 > + (define-xk xk-f21 #xffd2 #x84) ;; XK_F21 VK_F21 > + (define-xk xk-f22 #xffd3 #x85) ;; XK_F22 VK_F22 > + (define-xk xk-f23 #xffd4 #x86) ;; XK_F23 VK_F23 > + (define-xk xk-f24 #xffd5 #x87) ;; XK_F24 VK_F24 > + > + ;; Modifier keys > + (define-xk xk-shift-l #xffe1 #xA0) ;; XK_Shift_L VK_LSHIFT > + (define-xk xk-shift-r #xffe2 #xA1) ;; XK_Shift_R VK_RSHIFT > + (define-xk xk-control-l #xffe3 #xA2) ;; XK_Control_L VK_LCONTROL > + (define-xk xk-control-r #xffe4 #xA3) ;; XK_Control_R VK_RCONTROL > + (define-xk xk-caps-lock #xffe5 #x14) ;; XK_Caps_Lock VK_CAPITAL > + (define-xk xk-metal-l #xffe7 nil) ;; XK_Meta_L > + (define-xk xk-metal-t #xffee nil) ;; XK_Meta_R > + (define-xk xk-alt-l #xffe9 #xA4) ;; XK_Alt_L VK_LMENU > + (define-xk xk-alt-r #xffea #xA5) ;; XK_Alt_R VK_RMENU > + (define-xk xk-super-l #xffeb nil) ;; XK_Super_L > + (define-xk xk-super-r #xffec nil) ;; XK_Super_R > + (define-xk xk-hyper-l #xffed nil) ;; XK_Hyper_L > + (define-xk xk-hyper-r #xffee nil) ;; XK_Hyper_R > + > + ;; Latin 1 > + ;; For numbers and letters, MS-Windows does not define constant names. > + ;; X11 defines distinct keysyms for lowercase and uppercase > + ;; letters. We use only the uppercase ones. Events with lowercase > + ;; letters are converted to uppercase. > + (define-xk xk-space #x0020 #x20) ;; XK_space VK_SPACE > + (define-xk xk-0 #x0030 #x30) ;; XK_0 > + (define-xk xk-1 #x0031 #x31) ;; XK_1 > + (define-xk xk-2 #x0032 #x32) ;; XK_2 > + (define-xk xk-3 #x0033 #x33) ;; XK_3 > + (define-xk xk-4 #x0034 #x34) ;; XK_4 > + (define-xk xk-5 #x0035 #x35) ;; XK_5 > + (define-xk xk-6 #x0036 #x36) ;; XK_6 > + (define-xk xk-7 #x0037 #x37) ;; XK_7 > + (define-xk xk-8 #x0038 #x38) ;; XK_8 > + (define-xk xk-9 #x0039 #x39) ;; XK_9 > + (define-xk xk-a #x0041 #x41) ;; XK_A > + (define-xk xk-b #x0042 #x42) ;; XK_B > + (define-xk xk-c #x0043 #x43) ;; XK_C > + (define-xk xk-d #x0044 #x44) ;; XK_D > + (define-xk xk-e #x0045 #x45) ;; XK_E > + (define-xk xk-f #x0046 #x46) ;; XK_F > + (define-xk xk-g #x0047 #x47) ;; XK_G > + (define-xk xk-h #x0048 #x48) ;; XK_H > + (define-xk xk-i #x0049 #x49) ;; XK_I > + (define-xk xk-j #x004A #x4A) ;; XK_J > + (define-xk xk-k #x004B #x4B) ;; XK_K > + (define-xk xk-l #x004C #x4C) ;; XK_L > + (define-xk xk-m #x004D #x4D) ;; XK_M > + (define-xk xk-n #x004E #x4E) ;; XK_N > + (define-xk xk-o #x004F #x4F) ;; XK_O > + (define-xk xk-p #x0050 #x50) ;; XK_P > + (define-xk xk-q #x0051 #x51) ;; XK_Q > + (define-xk xk-r #x0052 #x52) ;; XK_R > + (define-xk xk-s #x0053 #x53) ;; XK_S > + (define-xk xk-t #x0054 #x54) ;; XK_T > + (define-xk xk-u #x0055 #x55) ;; XK_U > + (define-xk xk-v #x0056 #x56) ;; XK_V > + (define-xk xk-w #x0057 #x57) ;; XK_W > + (define-xk xk-x #x0058 #x58) ;; XK_X > + (define-xk xk-y #x0059 #x59) ;; XK_Y > + (define-xk xk-z #x005A #x5A));; XK_Z These tables should be somehow auto-generated, IMO. > +(defun llk-init () > + "Initialize low level key events. > + > +Fills the `llk-keysyms' list, and binds the `low-level-key' event > +to the `llk-handle' function. Resets the `llk-bindings' list. > +Besides calling this function, you need to set `enable-low-level-key-events' > +to a non-nil value." Does the user need to know about those things it does? Would there be any harm to call it automatically when the package is loaded, or the first time `llk-bind` is called? IOW, please try and make it unnecessary for the caller to know and call this function (and then make it internal with a "--" in its name). > + (interactive) Why interactive? > +(defsubst event-is-key-press (event) > + "Return the value of the IS-KEY-PRESS field of the EVENT, a low level key event." > + (declare (side-effect-free t)) > + (if (consp event) (nth 1 event))) > + > +(defsubst event-keysym (event) > + "Return the value of the KEY field of the EVENT, a low level key event." > + (declare (side-effect-free t)) > + (if (consp event) (nth 2 event))) > + > +(defsubst event-modifier (event) > + "Return the value of the MODIFIER field of the EVENT, a low level key event." > + (declare (side-effect-free t)) > + (if (consp event) (nth 3 event))) > + > +(defsubst event-time (event) > + "Return the value of the TIME field of the EVENT, a low level key event." > + (declare (side-effect-free t)) > + (if (consp event) (nth 4 event))) I think the `cl-defstruct` will define those functions for you (with a different name, but I think it's OK because these aren't applicable to other events anyway). > +;; We store the last events (key/modifier is-press timestamp) here to > +;; test for multitap. > +(defvar llk-events nil > + "Internal variable for detecting taps.") "--" > +;; If positive, return key (xk-shift-l, etc) else return nil. > +(defun llk-detect-n-tap (n timeout) > + "Internal function to detect n-tap keys." "--" I think it should not take `n` as an argument and instead it should automatically discover how many times the key has been tapped and generate events of the form `double-xk-shift-l`, `triple-xk-shift-l`, `tap-xk-shift-l`. > + (cl-every 'eq Please #' to quote function names. Here and elsewhere. > +(defun describe-low-level-key () > + "Wait for key press and describe the low level key event it generates." > + (interactive) > + (setq llk-describe-next-press t)) Can we get rid of `llk-describe-next-press` and instead temporarily (define-key special-event-map [low-level-key] 'llk--describe) or something like that? > +(defun llk-handle () > + "Internal function to handle low level key events." > + (interactive) > + (if (and (event-is-key-press last-input-event) > + llk-describe-next-press) > + (llk-show-event-description) > + (let ((tap-key (llk-detect-n-tap > + llk-tap-count > + llk-tap-timeout))) > + (when tap-key > + (let ((func (cl-third > + (seq-find > + (lambda (b) > + (and (eq (cl-first b) 'tap) > + (eq (cl-second b) tap-key))) > + llk-bindings)))) > + (cond > + ((commandp func) (call-interactively func)) Maybe instead of focusing on running a command, `llk-handle` and `llk-bind` should concentrate on generating "normal" events that are then looked up in the normal keymaps. So the user would use them only to specify which events should be recognized and which should be ignored. Then the user can use the standard keymaps to declare what to do in response to `double-xk-shift-l`. WDYT? > + ((functionp func) (funcall func)))))))) Why? Stefan ^ permalink raw reply [flat|nested] 93+ messages in thread
* Re: Physical keyboard events 2024-11-04 0:21 ` Po Lu ` (3 preceding siblings ...) 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo @ 2024-11-18 20:38 ` Cecilio Pardo 4 siblings, 0 replies; 93+ messages in thread From: Cecilio Pardo @ 2024-11-18 20:38 UTC (permalink / raw) To: Po Lu; +Cc: emacs-devel Hello, I just opened bug#74423 with a new version of this patch. ^ permalink raw reply [flat|nested] 93+ messages in thread
end of thread, other threads:[~2024-12-22 4:31 UTC | newest] Thread overview: 93+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2024-10-28 23:15 Physical keyboard events Cecilio Pardo 2024-10-29 13:40 ` Eli Zaretskii 2024-10-29 15:07 ` Cecilio Pardo 2024-10-29 15:38 ` Peter Feigl 2024-10-29 17:54 ` Cecilio Pardo 2024-10-29 23:41 ` James Thomas 2024-10-29 16:44 ` Eli Zaretskii 2024-10-29 16:55 ` Yuri Khan 2024-10-29 17:46 ` Eli Zaretskii 2024-10-30 2:56 ` Max Nikulin 2024-10-30 6:28 ` Yuri Khan 2024-10-30 6:39 ` Peter Feigl 2024-10-30 15:27 ` Eli Zaretskii 2024-10-30 17:13 ` Yuri Khan 2024-10-30 17:37 ` Eli Zaretskii 2024-10-30 19:26 ` Dov Grobgeld 2024-10-30 19:36 ` Juri Linkov 2024-10-30 19:55 ` Eli Zaretskii 2024-10-31 6:13 ` Yuri Khan 2024-10-30 15:21 ` Eli Zaretskii 2024-10-30 16:59 ` Max Nikulin 2024-10-29 17:56 ` Cecilio Pardo 2024-10-29 17:52 ` Cecilio Pardo 2024-10-29 17:13 ` Alan Mackenzie 2024-10-29 18:20 ` Cecilio Pardo 2024-10-29 19:31 ` Alan Mackenzie 2024-10-29 21:45 ` Cecilio Pardo 2024-10-30 6:02 ` Yuri Khan 2024-10-30 15:23 ` Eli Zaretskii 2024-10-30 16:51 ` Yuri Khan 2024-10-30 17:25 ` Eli Zaretskii 2024-10-30 3:27 ` Eli Zaretskii 2024-11-03 23:44 ` Cecilio Pardo 2024-11-04 0:21 ` Po Lu 2024-11-04 8:03 ` Cecilio Pardo 2024-11-04 9:35 ` Po Lu 2024-11-04 11:11 ` Cecilio Pardo 2024-11-04 11:49 ` Po Lu 2024-11-04 11:59 ` Cecilio Pardo 2024-11-04 13:29 ` Eli Zaretskii 2024-11-04 13:46 ` Cecilio Pardo 2024-11-04 13:54 ` Po Lu 2024-11-04 13:24 ` Eli Zaretskii 2024-11-04 14:09 ` Po Lu 2024-11-04 16:46 ` Eli Zaretskii 2024-11-05 1:31 ` Po Lu 2024-11-05 7:15 ` Cecilio Pardo 2024-11-05 9:03 ` Po Lu 2024-11-05 9:20 ` Cecilio Pardo 2024-11-05 12:21 ` Po Lu 2024-11-05 13:30 ` Eli Zaretskii 2024-11-05 14:27 ` Cecilio Pardo 2024-11-06 0:10 ` Po Lu 2024-11-06 12:49 ` Po Lu 2024-11-06 13:31 ` Eli Zaretskii 2024-11-07 0:25 ` Po Lu 2024-11-07 6:41 ` Eli Zaretskii 2024-11-07 14:36 ` Po Lu 2024-11-07 15:47 ` Eli Zaretskii 2024-11-07 16:58 ` Cecilio Pardo 2024-11-08 0:36 ` Po Lu 2024-11-05 13:13 ` Eli Zaretskii 2024-11-04 13:18 ` Eli Zaretskii 2024-11-04 14:37 ` Po Lu 2024-11-04 16:49 ` Eli Zaretskii 2024-11-05 1:03 ` Po Lu 2024-11-05 7:09 ` Cecilio Pardo 2024-11-05 13:06 ` Eli Zaretskii 2024-11-04 12:27 ` Eli Zaretskii 2024-11-04 13:09 ` Po Lu 2024-11-04 13:33 ` Eli Zaretskii 2024-11-16 8:42 ` Cecilio Pardo 2024-11-17 0:05 ` Po Lu 2024-11-18 20:35 ` bug#74423: Low level key events Cecilio Pardo 2024-11-18 23:49 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-23 12:08 ` Cecilio Pardo 2024-11-19 15:29 ` Eli Zaretskii 2024-11-19 16:43 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-19 20:05 ` Cecilio Pardo 2024-11-20 4:21 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-02 16:54 ` Cecilio Pardo 2024-12-04 20:01 ` Eli Zaretskii 2024-12-04 21:25 ` Cecilio Pardo 2024-12-05 5:41 ` Eli Zaretskii 2024-12-06 1:01 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-07 21:52 ` Cecilio Pardo 2024-12-13 22:55 ` Cecilio Pardo 2024-12-14 1:16 ` Po Lu via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-12-14 9:26 ` Cecilio Pardo 2024-12-14 11:14 ` Eli Zaretskii 2024-12-18 10:59 ` Cecilio Pardo 2024-12-22 4:31 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors 2024-11-18 20:38 ` Physical keyboard events Cecilio Pardo
Code repositories for project(s) associated with this external index https://git.savannah.gnu.org/cgit/emacs.git https://git.savannah.gnu.org/cgit/emacs/org-mode.git This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.