Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix latches #611

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions changes/api/+fix-latch-unexpected-break.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Make the key events that break *latches* also the only events that
prevent the latches to trigger. Previously any down event would prevent
it.

The major effect of this change is that depressing and releasing
two latching keys simultaneously will now activate both latches, as
expected.
169 changes: 98 additions & 71 deletions src/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ xkb_filter_group_lock_func(struct xkb_state *state,
static bool
xkb_action_breaks_latch(const union xkb_action *action)
{
/*
* XKB specification, §2.1 “Locking and Latching Modifiers and Groups”:
*
* Latched modifiers or groups apply only to the next key event that
* does not change keyboard state.
*/
switch (action->type) {
case ACTION_TYPE_NONE:
case ACTION_TYPE_PTR_BUTTON:
Expand Down Expand Up @@ -373,42 +379,57 @@ xkb_filter_group_latch_func(struct xkb_state *state,
union group_latch_priv priv = {.priv = filter->priv};
enum xkb_key_latch_state latch = priv.latch;

if (direction == XKB_KEY_DOWN && latch == LATCH_PENDING) {
/* If this is a new keypress and we're awaiting our single latched
* keypress, then either break the latch if any random key is pressed,
* or promote it to a lock if it's the same group delta & flags and
* latchToLock option is enabled. */
if (direction == XKB_KEY_DOWN) {
const union xkb_action *actions = NULL;
unsigned int count = xkb_key_get_actions(state, key, &actions);
for (unsigned int k = 0; k < count; k++) {
if (actions[k].type == ACTION_TYPE_GROUP_LATCH &&
actions[k].group.group == filter->action.group.group &&
actions[k].group.flags == filter->action.group.flags) {
filter->action = actions[k];
if (filter->action.group.flags & ACTION_LATCH_TO_LOCK &&
filter->action.group.group != 0) {
/* Promote to lock */
filter->action.type = ACTION_TYPE_GROUP_LOCK;
filter->func = xkb_filter_group_lock_func;
xkb_filter_group_lock_new(state, filter);
if (latch == LATCH_PENDING) {
/* If this is a new keypress and we're awaiting our single latched
* keypress, then either break the latch if any random key is pressed,
* or promote it to a lock if it's the same group delta & flags and
* latchToLock option is enabled. */
for (unsigned int k = 0; k < count; k++) {
if (actions[k].type == ACTION_TYPE_GROUP_LATCH &&
actions[k].group.group == filter->action.group.group &&
actions[k].group.flags == filter->action.group.flags) {
filter->action = actions[k];
if (filter->action.group.flags & ACTION_LATCH_TO_LOCK &&
filter->action.group.group != 0) {
/* Promote to lock */
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;
}
/* Do nothing if latchToLock option is not activated; if the
* latch is not broken by the following actions and the key is
* not consummed, then another latch filter will be created.
*/
continue;
}
else if (xkb_action_breaks_latch(&(actions[k]))) {
/* Breaks the latch */
state->components.latched_group -= priv.group_delta;
filter->key = key;
/* XXX beep beep! */
return XKB_FILTER_CONSUME;
filter->func = NULL;
return XKB_FILTER_CONTINUE;
}
/* Do nothing if latchToLock option is not activated; if the
* latch is not broken by the following actions and the key is
* not consummed, then another latch filter will be created.
*/
continue;
}
else if (xkb_action_breaks_latch(&(actions[k]))) {
/* Breaks the latch */
state->components.latched_group -= priv.group_delta;
filter->func = NULL;
return XKB_FILTER_CONTINUE;
}
else if (latch == LATCH_KEY_DOWN) {
/* Another key was pressed while we’ve still got the latching key
* held down. If some of the released key’s actions breaks latches,
* prevent the latch to trigger and keep the base group unchanged;
* it will be reset as soon as our modifier key gets released. */
for (unsigned int k = 0; k < count; k++) {
if (xkb_action_breaks_latch(&(actions[k]))) {
latch = NO_LATCH;
break;
}
}
}
/* Ignore press in NO_LATCH state */
}
else if (direction == XKB_KEY_UP && key == filter->key) {
/* Our key got released. If we've set it to clear locks, and we
Expand Down Expand Up @@ -437,13 +458,7 @@ xkb_filter_group_latch_func(struct xkb_state *state,
/* XXX beep beep! */
}
}
else if (direction == XKB_KEY_DOWN && latch == LATCH_KEY_DOWN) {
/* Another key was pressed while we've still got the latching
* key held down, so keep the base group active (from
* xkb_filter_group_latch_new), but don't trip the latch, just clear
* it as soon as the group key gets released. */
latch = NO_LATCH;
}
/* Ignore release of other keys */

priv.latch = latch;
filter->priv = priv.priv;
Expand Down Expand Up @@ -533,41 +548,59 @@ xkb_filter_mod_latch_func(struct xkb_state *state,
{
enum xkb_key_latch_state latch = filter->priv;

if (direction == XKB_KEY_DOWN && latch == LATCH_PENDING) {
/* If this is a new keypress and we're awaiting our single latched
* keypress, then either break the latch if any random key is pressed,
* or promote it to a lock or plain base set if it's the same
* modifier. */
if (direction == XKB_KEY_DOWN) {
const union xkb_action *actions = NULL;
unsigned int count = xkb_key_get_actions(state, key, &actions);
for (unsigned int k = 0; k < count; k++) {
if (actions[k].type == ACTION_TYPE_MOD_LATCH &&
actions[k].mods.flags == filter->action.mods.flags &&
actions[k].mods.mods.mask == filter->action.mods.mods.mask) {
filter->action = actions[k];
if (filter->action.mods.flags & ACTION_LATCH_TO_LOCK) {
filter->action.type = ACTION_TYPE_MOD_LOCK;
filter->func = xkb_filter_mod_lock_func;
state->components.locked_mods |= filter->action.mods.mods.mask;
if (latch == LATCH_PENDING) {
/* If this is a new keypress and we're awaiting our single latched
* keypress, then either break the latch if any random key is pressed,
* or promote it to a lock or plain base set if it's the same
* modifier. */
for (unsigned int k = 0; k < count; k++) {
if (actions[k].type == ACTION_TYPE_MOD_LATCH &&
actions[k].mods.flags == filter->action.mods.flags &&
actions[k].mods.mods.mask == filter->action.mods.mods.mask) {
filter->action = actions[k];
if (filter->action.mods.flags & ACTION_LATCH_TO_LOCK) {
filter->action.type = ACTION_TYPE_MOD_LOCK;
filter->func = xkb_filter_mod_lock_func;
state->components.locked_mods |=
filter->action.mods.mods.mask;
state->components.latched_mods &=
~filter->action.mods.mods.mask;
filter->key = key;
/* XXX beep beep! */
return XKB_FILTER_CONSUME;
}
/* Do nothing if latchToLock option is not activated; if the
* latch is not broken by the following actions and the key is
* is not consummed, then another latch filter will be created.
*/
continue;
}
else {
filter->action.type = ACTION_TYPE_MOD_SET;
filter->func = xkb_filter_mod_set_func;
state->set_mods |= filter->action.mods.mods.mask;
else if (xkb_action_breaks_latch(&(actions[k]))) {
/* XXX: This may be totally broken, we might need to break
* the latch in the next run after this press? */
state->components.latched_mods &=
~filter->action.mods.mods.mask;
filter->func = NULL;
return XKB_FILTER_CONTINUE;
}
filter->key = key;
state->components.latched_mods &= ~filter->action.mods.mods.mask;
/* XXX beep beep! */
return XKB_FILTER_CONSUME;
}
else if (xkb_action_breaks_latch(&(actions[k]))) {
/* XXX: This may be totally broken, we might need to break the
* latch in the next run after this press? */
state->components.latched_mods &= ~filter->action.mods.mods.mask;
filter->func = NULL;
return XKB_FILTER_CONTINUE;
}
else if (latch == LATCH_KEY_DOWN) {
/* Another key was pressed while we’ve still got the latching key
* held down. If some of the released key’s actions breaks latches,
* prevent the latch to trigger and keep the base modifier active;
* it will be cleared as soon as our modifier key gets released. */
for (unsigned int k = 0; k < count; k++) {
if (xkb_action_breaks_latch(&(actions[k]))) {
latch = NO_LATCH;
break;
}
}
}
/* Ignore press in NO_LATCH state */
}
else if (direction == XKB_KEY_UP && key == filter->key) {
/* Our key got released. If we've set it to clear locks, and we
Expand Down Expand Up @@ -596,13 +629,7 @@ xkb_filter_mod_latch_func(struct xkb_state *state,
/* XXX beep beep! */
}
}
else if (direction == XKB_KEY_DOWN && latch == LATCH_KEY_DOWN) {
/* Someone's pressed another key while we've still got the latching
* key held down, so keep the base modifier state active (from
* xkb_filter_mod_latch_new), but don't trip the latch, just clear
* it as soon as the modifier gets released. */
latch = NO_LATCH;
}
/* Ignore release of other keys */

filter->priv = latch;

Expand Down
48 changes: 48 additions & 0 deletions test/data/symbols/latch
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
default partial alphanumeric_keys
xkb_symbols "modifiers" {
name[Group1] = "Test latching behavior";

virtual_modifiers LevelThree;

key <AE01> { [ 1, exclam, onesuperior, exclamdown, plus ], type[Group1]="CTRL+ALT"};

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

key <LFSH> {
symbols[Group1] = [ Shift_L ],
actions[Group1] = [ LatchMods(modifiers=Shift,latchToLock=true,clearLocks=true) ]
};

key <RTSH> {
symbols[Group1] = [ Shift_R ],
actions[Group1] = [ LatchMods(modifiers=Shift,latchToLock=false,clearLocks=false) ]
};

key <LCTL> {
symbols[Group1] = [ Control_L ],
actions[Group1] = [ LatchMods(modifiers=Control) ]
};

key <LALT> {
type[Group1] = "ONE_LEVEL",
symbols[Group1] = [ Alt_L ],
actions[Group1] = [ LatchMods(modifiers=Alt) ]
};

key <RALT> {
type[Group1] = "ONE_LEVEL",
symbols[Group1] = [ ISO_Level3_Latch ],
actions[Group1] = [ LatchMods(modifiers=LevelThree,latchToLock=true,clearLocks=true) ]
};

key <MENU> {
type[Group1] = "ONE_LEVEL",
symbols[Group1] = [ ISO_Level3_Latch ],
actions[Group1] = [ LatchMods(modifiers=LevelThree,latchToLock=false,clearLocks=false) ]
};

key <FK01> { [XF86_Switch_VT_1], type[Group1] = "ONE_LEVEL" };
key <FK02> { [ISO_Group_Shift], type[Group1] = "ONE_LEVEL" };

key <LSGT> { [ ISO_Level3_Lock ], type[Group1] = "ONE_LEVEL" };
};
Loading
Loading