Skip to content

Commit 341a490

Browse files
wismillbluetech
andcommitted
state: add server API for updating latched and locked mods & layout
Up to now, the “server state” `xkb_state` API only offered one entry point to update the server state – `xkb_state_update_key`, which reflects the direct keyboard keys state. But some updates come out-of-band from keyboard input events stream, for example, a GUI layout switcher. The X11 XKB protocol has a request which allows for such updates, `XkbLatchLockState`[^1], but xkbcommon does not have similar functionality. So server applications ended up using `xkb_state_update_state` for this, but that’s a function intended for client applications, not servers. Add support for updating the latched & locked state of the mods and layout. Note that the depressed states cannot be updated in this way -- XKB does not expect them to be updated out of band. [^1]: https://www.x.org/releases/X11R7.7/doc/kbproto/xkbproto.html#Querying_and_Changing_Keyboard_State Fixes: #310 Signed-off-by: Ran Benita <[email protected]> Co-authored-by: Ran Benita <[email protected]> Co-authored-by: Pierre Le Marre <[email protected]>
1 parent a455add commit 341a490

File tree

10 files changed

+742
-10
lines changed

10 files changed

+742
-10
lines changed

include/xkbcommon/xkbcommon.h

+57
Original file line numberDiff line numberDiff line change
@@ -1520,6 +1520,63 @@ XKB_EXPORT enum xkb_state_component
15201520
xkb_state_update_key(struct xkb_state *state, xkb_keycode_t key,
15211521
enum xkb_key_direction direction);
15221522

1523+
/**
1524+
* Update the keyboard state to change the latched and locked state of
1525+
* the modifiers and layout.
1526+
*
1527+
* This entry point is intended for *server* applications and should not be used
1528+
* by *client* applications; see @ref server-client-state for details.
1529+
*
1530+
* Use this function to update the latched and locked state according to
1531+
* “out of band” (non-device) inputs, such as UI layout switchers.
1532+
*
1533+
* @par Layout out of range
1534+
* @parblock
1535+
*
1536+
* If the effective layout, after taking into account the depressed, latched and
1537+
* locked layout, is out of range (negative or greater than the maximum layout),
1538+
* it is brought into range. Currently, the layout is wrapped using integer
1539+
* modulus (with negative values wrapping from the end). The wrapping behavior
1540+
* may be made configurable in the future.
1541+
*
1542+
* @endparblock
1543+
*
1544+
* @param state The keyboard state object.
1545+
* @param affect_latched_mods
1546+
* @param latched_mods
1547+
* Modifiers to set as latched or unlatched. Only modifiers in
1548+
* `affect_latched_mods` are considered.
1549+
* @param affect_latched_layout
1550+
* @param latched_layout
1551+
* Layout to latch. Only considered if `affect_latched_layout` is true.
1552+
* Maybe be out of range (including negative) -- see note above.
1553+
* @param affect_locked_mods
1554+
* @param locked_mods
1555+
* Modifiers to set as locked or unlocked. Only modifiers in
1556+
* `affect_locked_mods` are considered.
1557+
* @param affect_locked_layout
1558+
* @param locked_layout
1559+
* Layout to lock. Only considered if `affect_locked_layout` is true.
1560+
* Maybe be out of range (including negative) -- see note above.
1561+
*
1562+
* @returns A mask of state components that have changed as a result of
1563+
* the update. If nothing in the state has changed, returns 0.
1564+
*
1565+
* @memberof xkb_state
1566+
*
1567+
* @sa xkb_state_update_mask()
1568+
*/
1569+
XKB_EXPORT enum xkb_state_component
1570+
xkb_state_update_latched_locked(struct xkb_state *state,
1571+
xkb_mod_mask_t affect_latched_mods,
1572+
xkb_mod_mask_t latched_mods,
1573+
bool affect_latched_layout,
1574+
int32_t latched_layout,
1575+
xkb_mod_mask_t affect_locked_mods,
1576+
xkb_mod_mask_t locked_mods,
1577+
bool affect_locked_layout,
1578+
int32_t locked_layout);
1579+
15231580
/**
15241581
* Update a keyboard state from a set of explicit masks.
15251582
*

src/keymap-priv.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,8 @@ action_equal(const union xkb_action *a, const union xkb_action *b)
147147
return false;
148148

149149
/* Ensure we support all action types */
150-
static_assert(ACTION_TYPE_PRIVATE == 15 &&
151-
ACTION_TYPE_PRIVATE + 1 == _ACTION_TYPE_NUM_ENTRIES,
150+
static_assert(ACTION_TYPE_INTERNAL == 16 &&
151+
ACTION_TYPE_INTERNAL + 1 == _ACTION_TYPE_NUM_ENTRIES,
152152
"Missing action type");
153153

154154
switch (a->type) {
@@ -188,6 +188,9 @@ action_equal(const union xkb_action *a, const union xkb_action *b)
188188
a->ctrls.ctrls == b->ctrls.ctrls);
189189
case ACTION_TYPE_PRIVATE:
190190
return (a->priv.data == b->priv.data);
191+
case ACTION_TYPE_INTERNAL:
192+
return (a->internal.flags == b->internal.flags) &&
193+
(a->internal.clear_latched_mods == b->internal.clear_latched_mods);
191194
default:
192195
assert(!"Unsupported action");
193196
return false;

src/keymap.h

+17
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ enum xkb_action_type {
9292
ACTION_TYPE_CTRL_SET,
9393
ACTION_TYPE_CTRL_LOCK,
9494
ACTION_TYPE_PRIVATE,
95+
ACTION_TYPE_INTERNAL, /* Action specific and internal to xkbcommon */
9596
_ACTION_TYPE_NUM_ENTRIES
9697
};
9798

@@ -189,6 +190,21 @@ struct xkb_private_action {
189190
uint8_t data[7];
190191
};
191192

193+
enum xkb_internal_action_flags {
194+
INTERNAL_BREAKS_GROUP_LATCH = (1 << 0),
195+
INTERNAL_BREAKS_MOD_LATCH = (1 << 1),
196+
};
197+
198+
/* Action specific and internal to xkbcommon */
199+
struct xkb_internal_action {
200+
enum xkb_action_type type;
201+
enum xkb_internal_action_flags flags;
202+
union {
203+
/* flag == INTERNAL_BREAKS_MOD_LATCH */
204+
xkb_mod_mask_t clear_latched_mods;
205+
};
206+
};
207+
192208
union xkb_action {
193209
enum xkb_action_type type;
194210
struct xkb_mod_action mods;
@@ -199,6 +215,7 @@ union xkb_action {
199215
struct xkb_pointer_action ptr;
200216
struct xkb_pointer_button_action btn;
201217
struct xkb_private_action priv;
218+
struct xkb_internal_action internal;
202219
};
203220

204221
struct xkb_key_type_entry {

src/state.c

+160-4
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,9 @@ xkb_filter_group_lock_func(struct xkb_state *state,
295295
}
296296

297297
static bool
298-
xkb_action_breaks_latch(const union xkb_action *action)
298+
xkb_action_breaks_latch(const union xkb_action *action,
299+
enum xkb_internal_action_flags flag,
300+
xkb_mod_mask_t mask)
299301
{
300302
switch (action->type) {
301303
case ACTION_TYPE_NONE:
@@ -306,6 +308,9 @@ xkb_action_breaks_latch(const union xkb_action *action)
306308
case ACTION_TYPE_SWITCH_VT:
307309
case ACTION_TYPE_TERMINATE:
308310
return true;
311+
case ACTION_TYPE_INTERNAL:
312+
return (action->internal.flags & flag) &&
313+
((action->internal.clear_latched_mods & mask) == mask);
309314
default:
310315
return false;
311316
}
@@ -389,7 +394,8 @@ xkb_filter_group_latch_func(struct xkb_state *state,
389394
*/
390395
continue;
391396
}
392-
else if (xkb_action_breaks_latch(&(actions[k]))) {
397+
else if (xkb_action_breaks_latch(&(actions[k]),
398+
INTERNAL_BREAKS_GROUP_LATCH, 0)) {
393399
/* Breaks the latch */
394400
state->components.latched_group -= priv.group_delta;
395401
filter->func = NULL;
@@ -547,9 +553,11 @@ xkb_filter_mod_latch_func(struct xkb_state *state,
547553
/* XXX beep beep! */
548554
return XKB_FILTER_CONSUME;
549555
}
550-
else if (xkb_action_breaks_latch(&(actions[k]))) {
556+
else if (xkb_action_breaks_latch(&(actions[k]),
557+
INTERNAL_BREAKS_MOD_LATCH,
558+
filter->action.mods.mods.mask)) {
551559
/* XXX: This may be totally broken, we might need to break the
552-
* latch in the next run after this press? */
560+
* latch in the next run after this press? */
553561
state->components.latched_mods &= ~filter->action.mods.mods.mask;
554562
filter->func = NULL;
555563
return XKB_FILTER_CONTINUE;
@@ -892,6 +900,114 @@ xkb_state_update_key(struct xkb_state *state, xkb_keycode_t kc,
892900
return get_state_component_changes(&prev_components, &state->components);
893901
}
894902

903+
/* We need fake keys for `update_latch_modifiers` and `update_latch_group`.
904+
* These keys must have at least one level in order to break latches. We need 2
905+
* keys with specific actions in order to update group/mod latches without
906+
* affecting each other. */
907+
static struct xkb_key_type_entry synthetic_key_level_entry = { 0 };
908+
static struct xkb_key_type synthetic_key_type = {
909+
.num_entries = 1,
910+
.num_levels = 1,
911+
.entries = &synthetic_key_level_entry
912+
};
913+
static const struct xkb_key synthetic_key = { 0 };
914+
915+
/* Transcription from xserver: XkbLatchModifiers */
916+
static void
917+
update_latch_modifiers(struct xkb_state *state,
918+
xkb_mod_mask_t mask, xkb_mod_mask_t latches)
919+
{
920+
/* Clear affected latched modifiers */
921+
const xkb_mod_mask_t clear = mask & ~latches;
922+
state->components.latched_mods &= ~clear;
923+
924+
/* Clear any pending latch to locks using ad hoc action:
925+
* only affect corresponding modifier latches and no group latch. */
926+
struct xkb_level synthetic_key_level_break_mod_latch = {
927+
.num_syms = 0,
928+
.num_actions = 1,
929+
.upper = XKB_KEY_NoSymbol,
930+
.s.sym = XKB_KEY_NoSymbol,
931+
.a.action.internal = {
932+
.type = ACTION_TYPE_INTERNAL,
933+
.flags = INTERNAL_BREAKS_MOD_LATCH,
934+
.clear_latched_mods = clear
935+
}
936+
};
937+
struct xkb_group synthetic_key_group_break_mod_latch = {
938+
.type = &synthetic_key_type,
939+
.levels = &synthetic_key_level_break_mod_latch
940+
};
941+
const struct xkb_key synthetic_key_break_mod_latch = {
942+
.num_groups = 1,
943+
.groups = &synthetic_key_group_break_mod_latch
944+
};
945+
xkb_filter_apply_all(state, &synthetic_key_break_mod_latch, XKB_KEY_DOWN);
946+
947+
/* Finally set the latched mods by simulate tapping a key with the
948+
* corresponding action */
949+
const struct xkb_key* const key = &synthetic_key;
950+
const union xkb_action latch_mods = {
951+
.mods = {
952+
.type = ACTION_TYPE_MOD_LATCH,
953+
.mods = { .mask = mask & latches },
954+
.flags = 0,
955+
},
956+
};
957+
struct xkb_filter* const filter = xkb_filter_new(state);
958+
filter->key = key;
959+
filter->func = xkb_filter_mod_latch_func;
960+
filter->action = latch_mods;
961+
xkb_filter_mod_latch_new(state, filter);
962+
/* We added the filter manually, so only fire “up” event */
963+
xkb_filter_mod_latch_func(state, filter, key, XKB_KEY_UP);
964+
}
965+
966+
/* Transcription from xserver: XkbLatchGroup */
967+
static void
968+
update_latch_group(struct xkb_state *state, int32_t group)
969+
{
970+
/* Clear any pending latch to locks. */
971+
static struct xkb_level synthetic_key_level_break_group_latch = {
972+
.num_syms = 0,
973+
.num_actions = 1,
974+
.upper = XKB_KEY_NoSymbol,
975+
.s.sym = XKB_KEY_NoSymbol,
976+
.a.action.internal = {
977+
.type = ACTION_TYPE_INTERNAL,
978+
.flags = INTERNAL_BREAKS_GROUP_LATCH,
979+
.clear_latched_mods = 0
980+
}
981+
};
982+
static struct xkb_group synthetic_key_group_break_group_latch = {
983+
.type = &synthetic_key_type,
984+
.levels = &synthetic_key_level_break_group_latch
985+
};
986+
static const struct xkb_key synthetic_key_break_group_latch = {
987+
.num_groups = 1,
988+
.groups = &synthetic_key_group_break_group_latch
989+
};
990+
xkb_filter_apply_all(state, &synthetic_key_break_group_latch, XKB_KEY_DOWN);
991+
992+
/* Simulate tapping a key with a group latch action, but in isolation: i.e.
993+
* without affecting the other filters. */
994+
const struct xkb_key* const key = &synthetic_key;
995+
const union xkb_action latch_group = {
996+
.group = {
997+
.type = ACTION_TYPE_GROUP_LATCH,
998+
.flags = ACTION_ABSOLUTE_SWITCH,
999+
.group = group,
1000+
},
1001+
};
1002+
struct xkb_filter* const filter = xkb_filter_new(state);
1003+
filter->key = key;
1004+
filter->func = xkb_filter_group_latch_func;
1005+
filter->action = latch_group;
1006+
xkb_filter_group_latch_new(state, filter);
1007+
/* We added the filter manually, so only fire “up” event */
1008+
xkb_filter_group_latch_func(state, filter, key, XKB_KEY_UP);
1009+
}
1010+
8951011
/**
8961012
* Compute the resolved effective mask of an arbitrary input.
8971013
*
@@ -913,6 +1029,46 @@ resolve_to_canonical_mods(struct xkb_keymap *keymap, xkb_mod_mask_t mods)
9131029
mods & ~keymap->canonical_state_mask);
9141030
}
9151031

1032+
enum xkb_state_component
1033+
xkb_state_update_latched_locked(struct xkb_state *state,
1034+
xkb_mod_mask_t affect_latched_mods,
1035+
xkb_mod_mask_t latched_mods,
1036+
bool affect_latched_layout,
1037+
int32_t latched_layout,
1038+
xkb_mod_mask_t affect_locked_mods,
1039+
xkb_mod_mask_t locked_mods,
1040+
bool affect_locked_layout,
1041+
int32_t locked_layout)
1042+
{
1043+
const struct state_components prev_components = state->components;
1044+
1045+
/* Update locks */
1046+
affect_locked_mods =
1047+
resolve_to_canonical_mods(state->keymap, affect_locked_mods);
1048+
if (affect_locked_mods) {
1049+
locked_mods = resolve_to_canonical_mods(state->keymap, locked_mods);
1050+
state->components.locked_mods &= ~affect_locked_mods;
1051+
state->components.locked_mods |= locked_mods & affect_locked_mods;
1052+
}
1053+
if (affect_locked_layout) {
1054+
state->components.locked_group = locked_layout;
1055+
}
1056+
1057+
/* Update latches */
1058+
affect_latched_mods =
1059+
resolve_to_canonical_mods(state->keymap, affect_latched_mods);
1060+
if (affect_latched_mods) {
1061+
latched_mods = resolve_to_canonical_mods(state->keymap, latched_mods);
1062+
update_latch_modifiers(state, affect_latched_mods, latched_mods);
1063+
}
1064+
if (affect_latched_layout) {
1065+
update_latch_group(state, latched_layout);
1066+
}
1067+
1068+
xkb_state_update_derived(state);
1069+
return get_state_component_changes(&prev_components, &state->components);
1070+
}
1071+
9161072
/**
9171073
* Updates the state from a set of explicit masks as gained from
9181074
* xkb_state_serialize_mods and xkb_state_serialize_groups. As noted in the

test/data/rules/evdev

+3
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,9 @@
993993
grp:alt_space_toggle = +group(alt_space_toggle)
994994
grp:menu_toggle = +group(menu_toggle)
995995
grp:lwin_toggle = +group(lwin_toggle)
996+
grp:lwin_latch = +group(lwin_latch)
996997
grp:rwin_toggle = +group(rwin_toggle)
998+
grp:rwin_latch_lock_clear = +group(rwin_latch_lock_clear)
997999
grp:lshift_toggle = +group(lshift_toggle)
9981000
grp:rshift_toggle = +group(rshift_toggle)
9991001
grp:rctrl_switch = +group(rctrl_switch)
@@ -1146,6 +1148,7 @@
11461148
lv3:caps_switch_latch = +level3(caps_switch_latch)
11471149
lv3:bksl_switch_latch = +level3(bksl_switch_latch)
11481150
lv3:lsgt_switch_latch = +level3(lsgt_switch_latch)
1151+
lv3:lsgt_latch = +level3(lsgt_latch)
11491152
lv5:lsgt_switch = +level5(lsgt_switch)
11501153
lv5:ralt_switch = +level5(ralt_switch)
11511154
lv5:lsgt_switch_lock = +level5(lsgt_switch_lock)

test/data/symbols/group

+16
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ xkb_symbols "lwin_switch" {
3434
};
3535
};
3636

37+
partial modifier_keys
38+
xkb_symbols "lwin_latch" {
39+
key <LWIN> {
40+
symbols[1] = [ ISO_Group_Latch ],
41+
actions[1] = [ LatchGroup(group=+1) ]
42+
};
43+
};
44+
3745
// The right Win key (while pressed) chooses the second keyboard group.
3846
// (Using this map, you should declare your keyboard as pc101 or pc102
3947
// instead of pc104 or pc105.)
@@ -45,6 +53,14 @@ xkb_symbols "rwin_switch" {
4553
};
4654
};
4755

56+
partial modifier_keys
57+
xkb_symbols "rwin_latch_lock_clear" {
58+
key <RWIN> {
59+
symbols[1] = [ ISO_Group_Latch ],
60+
actions[1] = [ LatchGroup(group=+1, latchToLock, clearLocks) ]
61+
};
62+
};
63+
4864
// The right Menu key (while pressed) chooses the second keyboard group.
4965
// while Shift+Menu acts as Menu.
5066
partial modifier_keys

test/data/symbols/level3

+8
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,14 @@ xkb_symbols "lsgt_switch_latch" {
210210
include "level3(modifier_mapping)"
211211
};
212212

213+
partial modifier_keys
214+
xkb_symbols "lsgt_latch" {
215+
key <LSGT> {
216+
type[Group1]="ONE_LEVEL",
217+
symbols[Group1] = [ ISO_Level3_Latch ]
218+
};
219+
};
220+
213221
// Number key 4 chooses third shift level when pressed in isolation.
214222
partial modifier_keys
215223
xkb_symbols "4_switch_isolated" {

0 commit comments

Comments
 (0)