When handling “generic” keys (i.e. keys not in the Kitty keymap), we
use the pressed key’s Unicode codepoint as “key” in the kitty CSI.
If we failed to convert the XKB symbol to a Unicode codepoint, we used
to (before this patch), fallback to using the XKB symbol as is.
This can never be correct... and it caused us to emit a meaningless
CSI for XKB_KEY_ISO_Next_Group, which confused e.g. Kakoune.
With this patch, key- and mouse-bindings structs (the non-layout
specific ones) are unified into a single struct.
The logic that parses, and manages, the key- and mouse binding lists
are almost identical. The *only* difference between a key- and a mouse
binding is that key bindings have an XKB symbol, and mouse bindings a
button and click-count.
The new, unified, struct uses a union around these, and all functions
that need to know which members to use/operate on now takes a ‘type’
parameter.
In this mode, the “shifted” and “base layout” keys are added to the
CSIs, as sub-parameters to the “key” parameter.
Note that this PR only implements the “shifted” key, not the “base
layout key”.
This is done by converting the original XKB symbol to it’s
corresponding UTF-32 codepoint. If this codepoint is different from
the one we use as “key” in the CSI, we add it as a sub-parameter.
Related to #319
The generic input handler now converts the composed character to it’s
UTF-32 equivalent. This means we now provide a valid UTF-32 codepoint
for both composed characters, and non-composed (plain-text)
characters.
Use this in the kitty protocol to simplify the logic around composed
characters, by simply treating them as plain text.
In this mode, key events that generate text now add a third CSI
parameter, indicating the actual codepoint.
Remember that we always use the *unshifted* key in the CSI
escapes. With this mode, those CSI escapes now also included the text
codepoint. I.e. what would have been emitted, had we not generated a
CSI escape.
As far as I can tell, this mode has no effect unless “report all keys
as escape sequences” is enabled (reason being, without that, there
aren’t any text events that generate CSIs - they’re always emitted
as-is).
Note that Kitty itself seems to be somewhat buggy in this mode. At
least on Wayland, with my Swedish layout. For example ‘a’ and ‘A’ does
generate the expected CSIs, but ‘å’ and ‘Å’ appears to be treated as
non-text input.
Furthermore, Kitty optimizes away the modifier parameter, if no
modifiers are pressed (e.g. CSI 97;;97u), while we always emit the
modifier (CSI 97;1;97u).
Related to #319
All plain-text and composed characters are now printed as-is, in a
single place.
Also fix handling of “generic” keys when emitted as escapes; don’t use
the raw XKB symbol as key in the escape, convert it to a unicode code
point first. For many symbols, these are the same. But not
all.
For now, we fallback to using the symbol as is if XKB fails to convert
it to a codepoint. Not sure if we should simply drop the key press
instead.
Composed characters also need special treatment; we can’t use the
symbol as is, since it typically refers to the last key
pressed (i.e. not the composed character). And, that key is
also (usually) a special “dead” key, which cannot be converted to a
unicode codepoint.
So, what we do is convert the generated utf8 string, and (try to)
convert it to a wchar. If it succeeds, use that. If not, fallback to
using the XKB symbol (as above).
Before this, key release events stopped the repeat timer, and then
returned.
Now, we run through the entire function. Most things are still only
done on key press events. But, the goal here is to get to the keyboard
protocol functions (and the kitty protocol in particular), and call
them on release events too.
This is in preparation for the kitty protocol mode 0b10, report event
types.
Now that term_xcursor_update_for_seat() takes the current surface into
account (i.e. doesn’t assume the cursor is over the main grid),
there’s no longer any need to call render_xcursor_set() directly.
Thus, we can simply call term_xcursor_update_for_seat() on **all**
pointer enter and motion events. As long as we take care to update the
internal state to reflect the, possibly new, current surface before
doing so.
Also make sure to **always** reset the seat’s “current” xcursor
pointer on pointer leave events. This is done without actually sending
anything to the compositor, but is necessary to ensure that we *do*
send a request to update the xcursor on the next pointer enter event.
This fixes a regression, where the view (and selection) was only reset
if the keyboard input resulted in plain text. That is, key presses
like enter, arrows etc did not.
Otherwise if you don't receive motion event before e.g. button pressed,
the coordinates will be incorrect. This happens when e.g. you get
alt-tabbed so that the mouse cursor ends up on top of the terminal
window, but the mouse never actually moved.
When term_xcursor_update_for_seat() was called on e.g. keyboard focus
loss, it'd update the curret xcursor to 'text' even if it was e.g. on
top of the window title, or resize areas. This makes the function a bit
more focus aware, and will not be so eager to set the text xcursor.
Not sure why these keys have CSIs in the kitty spec; they don’t emit
anything.
Could it be that they are used if the keys are *not* modifiers in the
current layout?
Not sure if this is the best/correct way to do it. But kitty seems to
ignore at least Num-Lock for printables, while it _does_ affect other
keys. For example, Return, which usually emits ‘\r’, are affected by
Num-Lock and emit ‘CSI 13;129u’.
Note that as soon as some other modifier is in effect, the Num-Lock
modifier *is* encoded in the CSI, also for printables.
Our internal binding handling cares about a different set of
modifiers, compared to the kitty keyboard protocol.
To handle this, get_current_modifiers() has been modified, to no
longer strip the “unsignificant” modifiers. This is now up to the
caller to do.
To help, we keep two masks (for significant modifiers) in the seat
struct; one for our internal binding handling (and the legacy keyboard
protocol), and one for the kitty keyboard protocol. These two masks
are updated when the seat’s keymap is updated/changed.
When emitting an escape sequence for a printable character, with
modifiers (e.g. ctrl+a), use the key’s base symbol instead of
“lowering” it.
This means we now handle e.g. ctrl+2 and ctrl+shift+2, with Swedish
layout.
There’s a twist however. We *only* use the base symbol if the
modifiers that is used to “generate” the symbol are “significant”.
Significant modifiers are, in this context, modifiers we can encode in
the kitty escape sequences.
In the Swedish layout, pressing AltGr+2 results in ‘@’. AltGr cannot
be encoded in the kitty protocol. If we were to use the base symbol,
AltGr+Alt+2 would result in exactly the same escape sequence as Alt+2.
Most things appear to work as in kitty. There’s one known difference:
tri-state keys don’t generate the same unshifted symbol while holding
Shift (but e.g. Ctrl+a and Ctrl+Shift+a *does* generate the same base
symbol).
For example, the Swedish keyboard layout has double quote, ‘2’ and ‘@’
on the same key. ‘2’ is the base (unshifted) symbol. Double quote is
Shift+2. ‘@’ is AltGr+2.
Kitty generates the same base symbol for ‘2’ and double quote (the
base symbol is, as expected, ‘2’).
But, for ‘@’ kitty generates the base symbol ‘@’.
Currently, foot generates the base symbol by calling
xkb_keysym_to_lower().
I _think_ what we need to do is “consume” the shift modifier, and then
re-retrieve the symbol (given the current xkb state and key pressed).
This breaks out all handling of key escapes to-be-sent to the client,
to a separate function, legacy_kbd_protocol().
That is, the key press/release handler first handles key generic
handling, such as starting and stopping the repeat timer.
Then it checks for foot keyboard bindings. If not bindings match, we
need to pass the keyboard event to the client. This code has now been
separated out into a new function.
If a mouse selection was ongoing, and the user switched
workspace (probably using the keyboard...), and then back, the
selection was still treated as ongoing, while all other mouse state
has been reset.
This meant the user had to tap at least once to stop the selection.