libxkbcommon icon indicating copy to clipboard operation
libxkbcommon copied to clipboard

Various fixes and improvements to latching behavior

Open Jules-Bertholet opened this issue 1 year ago • 6 comments

Any new lock breaks all existing latches

For example, if Caps_Lock is on the second level of some key, and Shift is latched, pressing the key will enable Caps while also breaking the Shift latch, ensuring that the next character is properly uppercase. (Prior to this commit, both Shift and Caps would be active, so the next character would be lowercase.) (Setting a lock via latchToLock or clearing it via clearLocks also breaks all latches.)

New latches break existing latches for non-preserved modifiers in the modifier list of the type of the key that triggered the new latch

For example, if a new latch is triggered by pressing a key of type ALPHABETIC, existing Shift and Lock latches will now be broken, but other latches will be preserved as before.

This ensures the correct behavior when combining sticky keys with ISO_Level5_Latch or latched-group additional symbols layers.

The rules for what does and does not break a latch are also applied before the latch is formed

Notably, pressing two latching keys simultaneously now applies both latches. For example, if I set up sticky keys, and then press Shift and AltGr together, both modifiers now end up latched.


An alternative to #51 that resolves its motivating issue without incurring the problems discussed in the comments there.

Closes #51

Jules-Bertholet avatar Dec 21 '24 03:12 Jules-Bertholet

Sorry for the noise. Every time I think this is ready, I sleep on it and then notice a new edge case…

I have been using this locally for several weeks now, and it makes the latching experience much more pleasant.

The intuition is that each sequence of latches builds up to some lasting effect intended by the user. That effect can either be a change in the application state, or a long-term change in the keyboard state. Once the effect has been achieved, the latches used to accomplish it have served their purpose are no longer desired.

Setting or releasing a lock is one such lasting effect, therefore changes in the lock state should always break all latches.

A user may also latch a modifier only to enable the latching of a different one. In such cases, the first latch has served its purpose as soon as the second one is formed. The “non-preserved modifiers” rule disambiguates between this case and the case where both latches are meant to be active simultaneously. Users can control the behavior precisely by means of preserve.

One change this PR does not make is that new latches continue to not break existing group latches. Because there is no equivalent of preserve for groups, users would have no way to configure this behavior, so I judged it best to preserve compatibility.

Jules-Bertholet avatar Jan 07 '25 18:01 Jules-Bertholet

Rebased. Also squashed a few commits, hopefully making it easier to review.

Jules-Bertholet avatar Jan 14 '25 23:01 Jules-Bertholet

I extracted the commit “Actions that would not break a latch no longer prevent it” into #611. Good catch!

wismill avatar Jan 24 '25 15:01 wismill

As for the rest of the MR, I will pause new features development until 1.10. But things that break the XKB specification will require more time to asset. I am not against it if proved worth it, but it would be best to wait to have a new API to specify new specification references.

@mahkoh how do you think we could handle divergences with the XKB spec that are not a strict extension (e.g. >256 keycodes, 16→32 mods, 4→32 groups)? See #485 for my take.

wismill avatar Jan 24 '25 15:01 wismill

Rebased.

Jules-Bertholet avatar Feb 05 '25 17:02 Jules-Bertholet

Current status:

Any new lock breaks all existing latches
New latches break existing latches for non-preserved modifiers in the modifier list of the type of the key that triggered the new latch

Alternative solution for both features implemented in #812, using multiple actions per level and VoidAction().

The rules for what does and does not break a latch are also applied before the latch is formed

Implemented in #611.

So at the moment I am not planning to merge the current MR; see #812 for the rationale. The gist of it is: it seems too adventurous to generalize a behavior that can be achieved locally with VoidAction(). See these examples where the generalization may be problematic.

@Jules-Bertholet This is not a definitive no, so I leave the MR open.

wismill avatar Jun 25 '25 10:06 wismill