Skip to content

Commit

Permalink
keymap: Add option "unlockOnPress" for LockMods action
Browse files Browse the repository at this point in the history
This is an extensions to XKB. It intends to allow to deactivate CapsLock on press
rather than on release, as in other platforms such as Windows.
  • Loading branch information
wismill committed Jul 5, 2024
1 parent 68debbc commit 18cf4d7
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/keymap.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ enum xkb_action_flags {
ACTION_ACCEL = (1 << 8),
ACTION_SAME_SCREEN = (1 << 9),
ACTION_LOCK_ON_RELEASE = (1 << 10),
ACTION_UNLOCK_ON_PRESS = (1 << 11),
};

enum xkb_action_controls {
Expand Down
14 changes: 11 additions & 3 deletions src/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,17 @@ xkb_filter_mod_lock_new(struct xkb_state *state, struct xkb_filter *filter)
{
filter->priv = (state->components.locked_mods &
filter->action.mods.mods.mask);
state->set_mods |= filter->action.mods.mods.mask;
if (!(filter->action.mods.flags & ACTION_LOCK_NO_LOCK))
state->components.locked_mods |= filter->action.mods.mods.mask;
if (filter->priv && (filter->action.mods.flags & ACTION_UNLOCK_ON_PRESS)) {
/* XKB extension: Unlock on second press */
state->clear_mods |= filter->action.mods.mods.mask;
if (!(filter->action.mods.flags & ACTION_LOCK_NO_UNLOCK))
state->components.locked_mods &= ~filter->priv;
filter->priv = 0;
} else {
state->set_mods |= filter->action.mods.mods.mask;
if (!(filter->action.mods.flags & ACTION_LOCK_NO_LOCK))
state->components.locked_mods |= filter->action.mods.mods.mask;
}
}

static bool
Expand Down
31 changes: 27 additions & 4 deletions src/xkbcomp/action.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ enum action_field {
ACTION_FIELD_KEYCODE,
ACTION_FIELD_MODS_TO_CLEAR,
ACTION_FIELD_LOCK_ON_RELEASE,
ACTION_FIELD_UNLOCK_ON_PRESS,
};

ActionsInfo *
Expand All @@ -124,6 +125,10 @@ NewActionsInfo(enum xkb_keymap_format format)
info->actions[ACTION_TYPE_PTR_MOVE].ptr.flags = ACTION_ACCEL;
info->actions[ACTION_TYPE_SWITCH_VT].screen.flags = ACTION_SAME_SCREEN;

if (isModUnlockOnPressSupported(format)) {
info->actions[ACTION_TYPE_MOD_LOCK].group.flags =
ACTION_UNLOCK_ON_PRESS;
}
if (isGroupLockOnReleaseSupported(format)) {
info->actions[ACTION_TYPE_GROUP_LOCK].group.flags =
ACTION_LOCK_ON_RELEASE;
Expand Down Expand Up @@ -173,6 +178,7 @@ static const LookupEntry fieldStrings[] = {
{ "clearmods", ACTION_FIELD_MODS_TO_CLEAR },
{ "clearmodifiers", ACTION_FIELD_MODS_TO_CLEAR },
{ "lockOnRelease", ACTION_FIELD_LOCK_ON_RELEASE },
{ "unlockOnPress", ACTION_FIELD_UNLOCK_ON_PRESS },
{ NULL, 0 }
};

Expand Down Expand Up @@ -330,6 +336,12 @@ CheckAffectField(struct xkb_context *ctx, enum xkb_action_type action,
return true;
}

inline bool
isModUnlockOnPressSupported(enum xkb_keymap_format format) {
/* Lax bound */
return format >= XKB_KEYMAP_FORMAT_TEXT_V1_1;
}

static bool
HandleSetLatchLockMods(struct xkb_context *ctx, enum xkb_keymap_format format,
const struct xkb_mod_set *mods,
Expand All @@ -352,10 +364,21 @@ HandleSetLatchLockMods(struct xkb_context *ctx, enum xkb_keymap_format format,
return CheckBooleanFlag(ctx, action->type, field,
ACTION_LATCH_TO_LOCK, array_ndx, value,
&act->flags);
if (type == ACTION_TYPE_MOD_LOCK &&
field == ACTION_FIELD_AFFECT)
return CheckAffectField(ctx, action->type, array_ndx, value,
&act->flags);
if (type == ACTION_TYPE_MOD_LOCK) {
if (field == ACTION_FIELD_AFFECT)
return CheckAffectField(ctx, action->type, array_ndx, value,
&act->flags);
if (field == ACTION_FIELD_UNLOCK_ON_PRESS) {
if (isModUnlockOnPressSupported(format)) {
return CheckBooleanFlag(ctx, action->type, field,
ACTION_UNLOCK_ON_PRESS, array_ndx, value,
&act->flags);
} else {
return ReportFormatVersionMismatch(ctx, action->type, field,
">= 1.1");
}
}
}

return ReportIllegal(ctx, action->type, field);
}
Expand Down
3 changes: 3 additions & 0 deletions src/xkbcomp/action.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ SetActionField(struct xkb_context *ctx, enum xkb_keymap_format format,
ActionsInfo *info, struct xkb_mod_set *mods, const char *elem,
const char *field, ExprDef *array_ndx, ExprDef *value);

bool
isModUnlockOnPressSupported(enum xkb_keymap_format format);

bool
isGroupLockOnReleaseSupported(enum xkb_keymap_format format);

Expand Down
11 changes: 10 additions & 1 deletion src/xkbcomp/keymap-dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,17 +317,25 @@ write_action(struct xkb_keymap *keymap, enum xkb_keymap_format format,
else
args = ModMaskText(keymap->ctx, &keymap->mods,
action->mods.mods.mods);
write_buf(buf, "%s%s(modifiers=%s%s%s%s)%s", prefix, type, args,
if (action->type == ACTION_TYPE_MOD_LOCK && (action->mods.flags & ACTION_UNLOCK_ON_PRESS) && !isModUnlockOnPressSupported(format)) {
/* Default is true in format ≥ 1.1 */
log_err(keymap->ctx, XKB_ERROR_INCOMPATIBLE_KEYMAP_EXPORT_FORMAT,
"Cannot use \"unlockOnPress=true\" in keymap format %d\n", format);
}
write_buf(buf, "%s%s(modifiers=%s%s%s%s%s)%s", prefix, type, args,
(action->type != ACTION_TYPE_MOD_LOCK && (action->mods.flags & ACTION_LOCK_CLEAR)) ? ",clearLocks" : "",
(action->type != ACTION_TYPE_MOD_LOCK && (action->mods.flags & ACTION_LATCH_TO_LOCK)) ? ",latchToLock" : "",
(action->type == ACTION_TYPE_MOD_LOCK) ? affect_lock_text(action->mods.flags, false) : "",
/* Default is true in format ≥ 1.1 */
(action->type == ACTION_TYPE_MOD_LOCK && (!(action->mods.flags & ACTION_UNLOCK_ON_PRESS)) && isModUnlockOnPressSupported(format)) ? ",unlockOnPress=false" : "",
suffix);
break;

case ACTION_TYPE_GROUP_SET:
case ACTION_TYPE_GROUP_LATCH:
case ACTION_TYPE_GROUP_LOCK:
if (action->type == ACTION_TYPE_GROUP_LOCK && (action->group.flags & ACTION_LOCK_ON_RELEASE) && !isGroupLockOnReleaseSupported(format)) {
/* Default is true in format ≥ 1.1 */
log_err(keymap->ctx, XKB_ERROR_INCOMPATIBLE_KEYMAP_EXPORT_FORMAT,
"Cannot use \"lockOnRelease=true\" in keymap format %d\n", format);
}
Expand All @@ -336,6 +344,7 @@ write_action(struct xkb_keymap *keymap, enum xkb_keymap_format format,
(action->group.flags & ACTION_ABSOLUTE_SWITCH) ? action->group.group + 1 : action->group.group,
(action->type != ACTION_TYPE_GROUP_LOCK && (action->group.flags & ACTION_LOCK_CLEAR)) ? ",clearLocks" : "",
(action->type != ACTION_TYPE_GROUP_LOCK && (action->group.flags & ACTION_LATCH_TO_LOCK)) ? ",latchToLock" : "",
/* Default is true in format ≥ 1.1 */
(action->type == ACTION_TYPE_GROUP_LOCK && (!(action->group.flags & ACTION_LOCK_ON_RELEASE)) && isGroupLockOnReleaseSupported(format)) ? ",lockOnRelease=false" : "",
suffix);
break;
Expand Down
12 changes: 12 additions & 0 deletions test/data/compat/caps
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,15 @@ partial xkb_compatibility "caps_lock" {
action = LockMods(modifiers = Lock);
};
};

partial xkb_compatibility "unlock_on_press" {
interpret Caps_Lock {
action = LockMods(modifiers = Lock, unlockOnPress);
};
};

partial xkb_compatibility "unlock_on_release" {
interpret Caps_Lock {
action = LockMods(modifiers = Lock, unlockOnPress=false);
};
};
2 changes: 2 additions & 0 deletions test/data/rules/evdev
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,8 @@
mod_led:compose = +ledcompose(compose)
japan:kana_lock = +japan(kana_lock)
caps:shiftlock = +ledcaps(shift_lock)
caps:unlock_on_press = +caps(unlock_on_press)
caps:unlock_on_release = +caps(unlock_on_release)
grab:break_actions = +xfree86(grab_break)
grp:lockOnRelease = +iso9995-v1.1(lockOnRelease)
grp:lockOnPress = +iso9995-v1.1(lockOnPress)
Expand Down
53 changes: 53 additions & 0 deletions test/keyseq.c
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,59 @@ main(void)
test_group_lock_on_press(keymap);
xkb_keymap_unref(keymap);

/* Caps unlocks on release for format V1 (implicit, XKB spec) */
keymap = test_compile_rules(ctx, XKB_KEYMAP_FORMAT_TEXT_V1,
"evdev", "pc105", "us", "", "");
assert(keymap);

#define test_caps_unlocks_on_release(keymap_) \
assert(test_key_seq(keymap_, \
KEY_Y, BOTH, XKB_KEY_y, NEXT, \
KEY_CAPSLOCK, BOTH, XKB_KEY_Caps_Lock, NEXT, \
KEY_Y, BOTH, XKB_KEY_Y, NEXT, \
KEY_CAPSLOCK, DOWN, XKB_KEY_Caps_Lock, NEXT, \
/* No unlock on press */ \
KEY_Y, BOTH, XKB_KEY_Y, NEXT, \
KEY_CAPSLOCK, UP, XKB_KEY_Caps_Lock, NEXT, \
/* Unlock on release */ \
KEY_Y, BOTH, XKB_KEY_y, FINISH))
test_caps_unlocks_on_release(keymap);

xkb_keymap_unref(keymap);

/* Caps unlocks on press for format V1.1 (implicit, default) */
keymap = test_compile_rules(ctx, XKB_KEYMAP_FORMAT_TEXT_V1_1,
"evdev", "pc105", "us", "", "");
assert(keymap);

#define test_caps_unlocks_on_press(keymap_) \
assert(test_key_seq(keymap_, \
KEY_Y, BOTH, XKB_KEY_y, NEXT, \
KEY_CAPSLOCK, BOTH, XKB_KEY_Caps_Lock, NEXT, \
KEY_Y, BOTH, XKB_KEY_Y, NEXT, \
KEY_CAPSLOCK, DOWN, XKB_KEY_Caps_Lock, NEXT, \
/* Unlock on press */ \
KEY_Y, BOTH, XKB_KEY_y, NEXT, \
KEY_CAPSLOCK, UP, XKB_KEY_Caps_Lock, NEXT, \
KEY_Y, BOTH, XKB_KEY_y, FINISH))
test_caps_unlocks_on_press(keymap);

xkb_keymap_unref(keymap);

/* Caps unlocks on press for format V1.1 (explicit unlockOnPress=true) */
keymap = test_compile_rules(ctx, XKB_KEYMAP_FORMAT_TEXT_V1_1,
"evdev", "pc105", "us", "", "caps:unlock_on_press");
assert(keymap);
test_caps_unlocks_on_press(keymap);
xkb_keymap_unref(keymap);

/* Caps unlocks on press for format V1.1 (explicit unlockOnPress=false) */
keymap = test_compile_rules(ctx, XKB_KEYMAP_FORMAT_TEXT_V1_1,
"evdev", "pc105", "us", "", "caps:unlock_on_release");
assert(keymap);
test_caps_unlocks_on_release(keymap);
xkb_keymap_unref(keymap);

xkb_context_unref(ctx);
return 0;
}

0 comments on commit 18cf4d7

Please sign in to comment.