Skip to content

Commit

Permalink
Break latches upon changing a lock with latchToLock/clearLocks
Browse files Browse the repository at this point in the history
Signed-off-by: Jules Bertholet <[email protected]>
  • Loading branch information
Jules-Bertholet committed Feb 5, 2025
1 parent 975f2ab commit 1a98fc2
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 48 deletions.
1 change: 1 addition & 0 deletions changes/api/+locks-break-latches.breaking.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Changed modifier and group latches so that setting or releasing a lock now breaks them.
(This includes setting a lock via `latchToLock`, or clearing it via `clearLocks`.)
This enables implementing a key that, for example, latches `Shift`
when pressed once, but locks `Caps` when pressed twice in succession.
109 changes: 73 additions & 36 deletions src/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,57 @@ enum xkb_filter_result {
else \
(state_)->components.component_ += (filter_)->action.group.group

enum xkb_key_latch_state {
NO_LATCH = 0,
LATCH_KEY_DOWN,
LATCH_PENDING,
_KEY_LATCH_STATE_NUM_ENTRIES
};

#define MAX_XKB_KEY_LATCH_STATE_LOG2 2
#if (_KEY_LATCH_STATE_NUM_ENTRIES > (1 << MAX_XKB_KEY_LATCH_STATE_LOG2)) || \
(-XKB_MAX_GROUPS) < (INT32_MIN >> MAX_XKB_KEY_LATCH_STATE_LOG2) || \
XKB_MAX_GROUPS > (INT32_MAX >> MAX_XKB_KEY_LATCH_STATE_LOG2)
#error "Cannot represent priv field of the group latch filter"
#endif

/* Hold the latch state *and* the group delta */
union group_latch_priv {
uint32_t priv;
struct {
/* The type is really: enum xkb_key_latch_state, but it is problematic
* on Windows, because it is interpreted as signed and leads to wrong
* negative values. */
unsigned int latch:MAX_XKB_KEY_LATCH_STATE_LOG2;
int32_t group_delta:(32 - MAX_XKB_KEY_LATCH_STATE_LOG2);
};
};

static void
xkb_break_all_latches(struct xkb_state *state) {
struct xkb_filter *filter;

darray_foreach(filter, state->filters) {
if (filter->action.type == ACTION_TYPE_MOD_LATCH) {
if (filter->priv == LATCH_PENDING) {
filter->func = NULL;
} else {
filter->priv = NO_LATCH;
}
} else if (filter->action.type == ACTION_TYPE_GROUP_LATCH) {
union group_latch_priv priv = {.priv = filter->priv};
if (priv.latch == LATCH_PENDING) {
filter->func = NULL;
} else {
priv.latch = NO_LATCH;
filter->priv = priv.priv;
}
}
}
state->components.latched_mods = 0;
state->components.latched_group = 0;
}

static void
xkb_filter_group_set_new(struct xkb_state *state, struct xkb_filter *filter)
{
Expand Down Expand Up @@ -288,8 +339,10 @@ xkb_filter_group_set_func(struct xkb_state *state,

state->components.base_group = filter->priv;

if (filter->action.group.flags & ACTION_LOCK_CLEAR)
if (filter->action.group.flags & ACTION_LOCK_CLEAR && state->components.locked_group) {
state->components.locked_group = 0;
xkb_break_all_latches(state);
}

filter->func = NULL;
return XKB_FILTER_CONTINUE;
Expand Down Expand Up @@ -343,32 +396,6 @@ xkb_action_breaks_latch(const union xkb_action *action)
}
}

enum xkb_key_latch_state {
NO_LATCH = 0,
LATCH_KEY_DOWN,
LATCH_PENDING,
_KEY_LATCH_STATE_NUM_ENTRIES
};

#define MAX_XKB_KEY_LATCH_STATE_LOG2 2
#if (_KEY_LATCH_STATE_NUM_ENTRIES > (1 << MAX_XKB_KEY_LATCH_STATE_LOG2)) || \
(-XKB_MAX_GROUPS) < (INT32_MIN >> MAX_XKB_KEY_LATCH_STATE_LOG2) || \
XKB_MAX_GROUPS > (INT32_MAX >> MAX_XKB_KEY_LATCH_STATE_LOG2)
#error "Cannot represent priv field of the group latch filter"
#endif

/* Hold the latch state *and* the group delta */
union group_latch_priv {
uint32_t priv;
struct {
/* The type is really: enum xkb_key_latch_state, but it is problematic
* on Windows, because it is interpreted as signed and leads to wrong
* negative values. */
unsigned int latch:MAX_XKB_KEY_LATCH_STATE_LOG2;
int32_t group_delta:(32 - MAX_XKB_KEY_LATCH_STATE_LOG2);
};
};

static void
xkb_filter_group_latch_new(struct xkb_state *state, struct xkb_filter *filter)
{
Expand Down Expand Up @@ -408,10 +435,10 @@ xkb_filter_group_latch_func(struct xkb_state *state,
if (filter->action.group.flags & ACTION_LATCH_TO_LOCK &&
filter->action.group.group != 0) {
/* Promote to lock */
xkb_break_all_latches(state); // Must be run *before* setting filter->func
filter->action.type = ACTION_TYPE_GROUP_LOCK;
filter->func = xkb_filter_group_lock_func;
xkb_filter_group_lock_new(state, filter);
state->components.latched_group -= priv.group_delta;
filter->key = key;
/* XXX beep beep! */
return XKB_FILTER_CONSUME;
Expand Down Expand Up @@ -446,8 +473,8 @@ xkb_filter_group_latch_func(struct xkb_state *state,
}
else if (direction == XKB_KEY_UP && key == filter->key) {
/* Our key got released. If we've set it to clear locks, and we
* currently have a group locked, then release it and
* don't actually latch. Else we've actually hit the latching
* currently have a group locked, then release it, break all latches,
* and don't actually latch. Else we've actually hit the latching
* stage, so set PENDING and move our group from base to
* latched. */
if (latch == NO_LATCH ||
Expand All @@ -457,8 +484,10 @@ xkb_filter_group_latch_func(struct xkb_state *state,
state->components.latched_group -= priv.group_delta;
else
state->components.base_group -= priv.group_delta;
if (filter->action.group.flags & ACTION_LOCK_CLEAR)
if (filter->action.group.flags & ACTION_LOCK_CLEAR && state->components.locked_group) {
state->components.locked_group = 0;
xkb_break_all_latches(state);
}
filter->func = NULL;
}
/* We may already have reached the latch state if pressing the
Expand Down Expand Up @@ -504,8 +533,10 @@ xkb_filter_mod_set_func(struct xkb_state *state,
}

state->clear_mods |= filter->action.mods.mods.mask;
if (filter->action.mods.flags & ACTION_LOCK_CLEAR)
if (filter->action.mods.flags & ACTION_LOCK_CLEAR && state->components.locked_mods & filter->action.mods.mods.mask) {
state->components.locked_mods &= ~filter->action.mods.mods.mask;
xkb_break_all_latches(state);
}

filter->func = NULL;
return XKB_FILTER_CONTINUE;
Expand Down Expand Up @@ -577,6 +608,7 @@ xkb_filter_mod_latch_func(struct xkb_state *state,
filter->action.type = ACTION_TYPE_MOD_LOCK;
filter->func = xkb_filter_mod_lock_func;
state->components.locked_mods |= filter->action.mods.mods.mask;
xkb_break_all_latches(state);
}
else {
filter->action.type = ACTION_TYPE_MOD_SET;
Expand Down Expand Up @@ -649,9 +681,10 @@ xkb_filter_mod_latch_func(struct xkb_state *state,
}
else if (direction == XKB_KEY_UP && key == filter->key) {
/* Our key got released. If we've set it to clear locks, and we
* currently have the same modifiers locked, then release them and
* don't actually latch. Else we've actually hit the latching
* stage, so set PENDING and move our modifier from base to
* currently have the same modifiers locked, then release them,
* break all latches, and don't actually latch.
* Else we've actually hit the latching stage,
* so set PENDING and move our modifier from base to
* latched. */
if (latch == NO_LATCH ||
((filter->action.mods.flags & ACTION_LOCK_CLEAR) &&
Expand All @@ -664,7 +697,11 @@ xkb_filter_mod_latch_func(struct xkb_state *state,
~filter->action.mods.mods.mask;
else
state->clear_mods |= filter->action.mods.mods.mask;
state->components.locked_mods &= ~filter->action.mods.mods.mask;
if (filter->action.mods.flags & ACTION_LOCK_CLEAR &&
state->components.locked_mods & filter->action.mods.mods.mask) {
state->components.locked_mods &= ~filter->action.mods.mods.mask;
xkb_break_all_latches(state);
}
filter->func = NULL;
}
else {
Expand Down
2 changes: 1 addition & 1 deletion test/data/symbols/latch
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ xkb_symbols "modifiers" {

key <AD01> { [ q, Q ], type[Group1] = "ALPHABETIC" };

key <AC01> { [ a, A, ISO_Level5_Latch, NoSymbol ], type[Group1] = "FOUR_LEVEL_SEMIALPHABETIC" };
key <AC01> { [ a, A, ISO_Level5_Latch, minus ], type[Group1] = "FOUR_LEVEL_SEMIALPHABETIC" };

key <LFSH> {
symbols[Group1] = [ Shift_L ],
Expand Down
68 changes: 57 additions & 11 deletions test/keyseq.c
Original file line number Diff line number Diff line change
Expand Up @@ -768,8 +768,8 @@ test_mod_latch(struct xkb_context *context)

KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Latch Shift */
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, /* Latch Level3 */
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Lock Shift */
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT, /* Unlatch Level3 */
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Lock Shift, unlatch Level3 */
KEY_1 , BOTH, XKB_KEY_exclam , NEXT,
KEY_1 , BOTH, XKB_KEY_exclam , NEXT,
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Unlock Shift */
KEY_1 , BOTH, XKB_KEY_1 , NEXT,
Expand All @@ -788,12 +788,12 @@ test_mod_latch(struct xkb_context *context)

KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Latch Shift */
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, /* Latch Level3 */
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Lock Shift */
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, /* Lock Level3 */
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT,
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT,
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, /* Unlock Level3 */
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Lock Shift, unlatch Level3 */
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, /* Latch Level3 */
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT, /* Unatch Level3 */
KEY_1 , BOTH, XKB_KEY_exclam , NEXT,
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, /* Latch Level3 */
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT,
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Unlock Shift */
KEY_1 , BOTH, XKB_KEY_1 , FINISH
));
Expand Down Expand Up @@ -878,17 +878,17 @@ test_mod_latch(struct xkb_context *context)
KEY_RIGHTALT , DOWN, XKB_KEY_ISO_Level3_Latch, NEXT, /* Set Level3 */
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Latch Shift */
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Lock Shift */
KEY_RIGHTALT , UP , XKB_KEY_ISO_Level3_Latch, NEXT, /* Latch Level3 */
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT, /* Unlatch Level3 */
KEY_RIGHTALT , UP , XKB_KEY_ISO_Level3_Latch, NEXT,
KEY_1 , BOTH, XKB_KEY_exclam , NEXT,
KEY_1 , BOTH, XKB_KEY_exclam , NEXT,
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Unlock Shift */
KEY_1 , BOTH, XKB_KEY_1 , NEXT,

KEY_RIGHTALT , DOWN, XKB_KEY_ISO_Level3_Latch, NEXT, /* Set Level3 */
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Latch Shift */
KEY_RIGHTALT , UP , XKB_KEY_ISO_Level3_Latch, NEXT, /* Latch Level3 */
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Lock Shift */
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT, /* Unlatch level 3*/
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Lock Shift, unlatch Level3 */
KEY_1 , BOTH, XKB_KEY_exclam , NEXT,
KEY_1 , BOTH, XKB_KEY_exclam , NEXT,
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, /* Unlock Shift */
KEY_1 , BOTH, XKB_KEY_1 , FINISH
Expand Down Expand Up @@ -1142,6 +1142,52 @@ test_latch_mod_cancel(struct xkb_context *context)
KEY_Q , BOTH, XKB_KEY_q , FINISH
));

// `latchToLock` locks and `clearLocks` unlocks break existing latches...
assert(test_key_seq(keymap,
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, // Latch Shift
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Latch LevelThree
KEY_A , BOTH, XKB_KEY_minus , NEXT, // Unlatch Shift, unlatch LevelThree

KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, // Latch Shift
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Latch LevelThree
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Lock LevelThree, unlatch Shift
KEY_A , BOTH, XKB_KEY_ISO_Level5_Latch, NEXT, // Latch LevelFive
KEY_Q , BOTH, XKB_KEY_q , NEXT, // Unlatch LevelFive
KEY_A , BOTH, XKB_KEY_ISO_Level5_Latch, NEXT, // Latch LevelFive
KEY_Q , BOTH, XKB_KEY_q , NEXT, // Unlatch LevelFive
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, // Latch Shift
KEY_Q , BOTH, XKB_KEY_Q , NEXT, // Unlatch Shift
KEY_Q , BOTH, XKB_KEY_q , NEXT,
KEY_LEFTSHIFT , BOTH, XKB_KEY_Shift_L , NEXT, // Latch Shift
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Unlock LevelThree, unlatch Shift
KEY_A , BOTH, XKB_KEY_a , FINISH
));

// ... but a latch key still sets while being held down
assert(test_key_seq(keymap,
KEY_F3 , DOWN, XKB_KEY_Shift_L , NEXT, // Set Shift
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Latch LevelThree
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Lock LevelThree
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT,
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT,
KEY_F3 , UP , XKB_KEY_Caps_Lock , NEXT, // Unset Shift
KEY_1 , BOTH, XKB_KEY_onesuperior , NEXT,
KEY_1 , BOTH, XKB_KEY_onesuperior , NEXT,
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Unlock LevelThree
KEY_1 , BOTH, XKB_KEY_1 , NEXT,

KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Latch LevelThree
KEY_F3 , DOWN, XKB_KEY_Shift_L , NEXT, // Set Shift
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Lock LevelThree
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT,
KEY_1 , BOTH, XKB_KEY_exclamdown , NEXT,
KEY_F3 , UP , XKB_KEY_Caps_Lock , NEXT, // Unset Shift
KEY_1 , BOTH, XKB_KEY_onesuperior , NEXT,
KEY_1 , BOTH, XKB_KEY_onesuperior , NEXT,
KEY_RIGHTALT , BOTH, XKB_KEY_ISO_Level3_Latch, NEXT, // Unlock LevelThree
KEY_1 , BOTH, XKB_KEY_1 , FINISH
));

xkb_keymap_unref(keymap);
}

Expand Down

0 comments on commit 1a98fc2

Please sign in to comment.