Skip to content

Commit 924eca2

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 fdc08bd commit 924eca2

File tree

9 files changed

+735
-7
lines changed

9 files changed

+735
-7
lines changed

include/xkbcommon/xkbcommon.h

+58
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
#define _XKBCOMMON_H_
8383

8484
#include <stdint.h>
85+
#include <stdbool.h>
8586
#include <stdio.h>
8687
#include <stdarg.h>
8788

@@ -1480,6 +1481,63 @@ enum xkb_state_component
14801481
xkb_state_update_key(struct xkb_state *state, xkb_keycode_t key,
14811482
enum xkb_key_direction direction);
14821483

1484+
/**
1485+
* Update the keyboard state to change the latched and locked state of
1486+
* the modifiers and layout.
1487+
*
1488+
* This entry point is intended for *server* applications and should not be used
1489+
* by *client* applications; see @ref server-client-state for details.
1490+
*
1491+
* Use this function to update the latched and locked state according to
1492+
* “out of band” (non-device) inputs, such as UI layout switchers.
1493+
*
1494+
* @par Layout out of range
1495+
* @parblock
1496+
*
1497+
* If the effective layout, after taking into account the depressed, latched and
1498+
* locked layout, is out of range (negative or greater than the maximum layout),
1499+
* it is brought into range. Currently, the layout is wrapped using integer
1500+
* modulus (with negative values wrapping from the end). The wrapping behavior
1501+
* may be made configurable in the future.
1502+
*
1503+
* @endparblock
1504+
*
1505+
* @param state The keyboard state object.
1506+
* @param affect_latched_mods
1507+
* @param latched_mods
1508+
* Modifiers to set as latched or unlatched. Only modifiers in
1509+
* `affect_latched_mods` are considered.
1510+
* @param affect_latched_layout
1511+
* @param latched_layout
1512+
* Layout to latch. Only considered if `affect_latched_layout` is true.
1513+
* Maybe be out of range (including negative) -- see note above.
1514+
* @param affect_locked_mods
1515+
* @param locked_mods
1516+
* Modifiers to set as locked or unlocked. Only modifiers in
1517+
* `affect_locked_mods` are considered.
1518+
* @param affect_locked_layout
1519+
* @param locked_layout
1520+
* Layout to lock. Only considered if `affect_locked_layout` is true.
1521+
* Maybe be out of range (including negative) -- see note above.
1522+
*
1523+
* @returns A mask of state components that have changed as a result of
1524+
* the update. If nothing in the state has changed, returns 0.
1525+
*
1526+
* @memberof xkb_state
1527+
*
1528+
* @sa xkb_state_update_mask()
1529+
*/
1530+
enum xkb_state_component
1531+
xkb_state_update_latched_locked(struct xkb_state *state,
1532+
xkb_mod_mask_t affect_latched_mods,
1533+
xkb_mod_mask_t latched_mods,
1534+
bool affect_latched_layout,
1535+
int32_t latched_layout,
1536+
xkb_mod_mask_t affect_locked_mods,
1537+
xkb_mod_mask_t locked_mods,
1538+
bool affect_locked_layout,
1539+
int32_t locked_layout);
1540+
14831541
/**
14841542
* Update a keyboard state from a set of explicit masks.
14851543
*

src/keymap.h

+16
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ enum xkb_action_type {
137137
ACTION_TYPE_CTRL_SET,
138138
ACTION_TYPE_CTRL_LOCK,
139139
ACTION_TYPE_PRIVATE,
140+
ACTION_TYPE_INTERNAL, /* Action specific and internal to xkbcommon */
140141
_ACTION_TYPE_NUM_ENTRIES
141142
};
142143

@@ -234,6 +235,20 @@ struct xkb_private_action {
234235
uint8_t data[7];
235236
};
236237

238+
enum xkb_internal_action_flags {
239+
INTERNAL_BREAKS_GROUP_LATCH = (1 << 0),
240+
INTERNAL_BREAKS_MOD_LATCH = (1 << 1),
241+
};
242+
243+
/* Action specific and internal to xkbcommon */
244+
struct xkb_internal_action {
245+
enum xkb_action_type type;
246+
enum xkb_internal_action_flags flags;
247+
union {
248+
xkb_mod_mask_t clear_latched_mods;
249+
};
250+
};
251+
237252
union xkb_action {
238253
enum xkb_action_type type;
239254
struct xkb_mod_action mods;
@@ -244,6 +259,7 @@ union xkb_action {
244259
struct xkb_pointer_action ptr;
245260
struct xkb_pointer_button_action btn;
246261
struct xkb_private_action priv;
262+
struct xkb_internal_action internal;
247263
};
248264

249265
struct xkb_key_type_entry {

src/state.c

+160-5
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,9 @@ xkb_filter_group_lock_func(struct xkb_state *state,
317317
}
318318

319319
static bool
320-
xkb_action_breaks_latch(const union xkb_action *action)
320+
xkb_action_breaks_latch(const union xkb_action *action,
321+
enum xkb_internal_action_flags flag,
322+
xkb_mod_mask_t mask)
321323
{
322324
switch (action->type) {
323325
case ACTION_TYPE_NONE:
@@ -328,6 +330,9 @@ xkb_action_breaks_latch(const union xkb_action *action)
328330
case ACTION_TYPE_SWITCH_VT:
329331
case ACTION_TYPE_TERMINATE:
330332
return true;
333+
case ACTION_TYPE_INTERNAL:
334+
return (action->internal.flags & flag) &&
335+
((action->internal.clear_latched_mods & mask) == mask);
331336
default:
332337
return false;
333338
}
@@ -411,7 +416,8 @@ xkb_filter_group_latch_func(struct xkb_state *state,
411416
*/
412417
continue;
413418
}
414-
else if (xkb_action_breaks_latch(&(actions[k]))) {
419+
else if (xkb_action_breaks_latch(&(actions[k]),
420+
INTERNAL_BREAKS_GROUP_LATCH, 0)) {
415421
/* Breaks the latch */
416422
state->components.latched_group -= priv.group_delta;
417423
filter->func = NULL;
@@ -569,9 +575,11 @@ xkb_filter_mod_latch_func(struct xkb_state *state,
569575
/* XXX beep beep! */
570576
return XKB_FILTER_CONSUME;
571577
}
572-
else if (xkb_action_breaks_latch(&(actions[k]))) {
578+
else if (xkb_action_breaks_latch(&(actions[k]),
579+
INTERNAL_BREAKS_MOD_LATCH,
580+
filter->action.mods.mods.mask)) {
573581
/* XXX: This may be totally broken, we might need to break the
574-
* latch in the next run after this press? */
582+
* latch in the next run after this press? */
575583
state->components.latched_mods &= ~filter->action.mods.mods.mask;
576584
filter->func = NULL;
577585
return XKB_FILTER_CONTINUE;
@@ -920,6 +928,153 @@ xkb_state_update_key(struct xkb_state *state, xkb_keycode_t kc,
920928
return get_state_component_changes(&prev_components, &state->components);
921929
}
922930

931+
/* We need fake keys for update_latch_modifiers and update_latch_group.
932+
* These keys must have at least one level in order to break latches. We need 2
933+
* keys with specific actions in order to update group/mod latches without
934+
* affecting each other. */
935+
static struct xkb_key_type_entry synthetic_key_level_entry = { 0 };
936+
static struct xkb_key_type synthetic_key_type = {
937+
.num_entries = 1,
938+
.num_levels = 1,
939+
.entries = &synthetic_key_level_entry
940+
};
941+
static struct xkb_level synthetic_key_level_break_group_latch = {
942+
.num_syms = 1,
943+
.s = { XKB_KEY_NoSymbol },
944+
.a = { { .internal = {
945+
.type = ACTION_TYPE_INTERNAL,
946+
.flags = INTERNAL_BREAKS_GROUP_LATCH
947+
} } }
948+
};
949+
static struct xkb_group synthetic_key_group_break_group_latch = {
950+
.type = &synthetic_key_type,
951+
.levels = &synthetic_key_level_break_group_latch
952+
};
953+
static const struct xkb_key synthetic_key_break_group_latch = {
954+
.num_groups = 1,
955+
.groups = &synthetic_key_group_break_group_latch
956+
};
957+
static const struct xkb_key synthetic_key = { 0 };
958+
959+
/* Transcription from xserver: XkbLatchModifiers */
960+
static void
961+
update_latch_modifiers(struct xkb_state *state,
962+
xkb_mod_mask_t mask, xkb_mod_mask_t latches)
963+
{
964+
/* Clear affected latched modifiers */
965+
const xkb_mod_mask_t clear =
966+
mod_mask_get_effective(state->keymap, mask & ~latches);
967+
state->components.latched_mods &= ~clear;
968+
969+
/* Clear any pending latch to locks using ad hoc action:
970+
* only affect corresponding modifier latches and no group latch. */
971+
struct xkb_level synthetic_key_level_break_mod_latch = {
972+
.num_syms = 1,
973+
.s = { XKB_KEY_NoSymbol },
974+
.a = { { .internal = {
975+
.type = ACTION_TYPE_INTERNAL,
976+
.flags = INTERNAL_BREAKS_MOD_LATCH,
977+
.clear_latched_mods = clear
978+
} } }
979+
};
980+
struct xkb_group synthetic_key_group_break_mod_latch = {
981+
.type = &synthetic_key_type,
982+
.levels = &synthetic_key_level_break_mod_latch
983+
};
984+
const struct xkb_key synthetic_key_break_mod_latch = {
985+
.num_groups = 1,
986+
.groups = &synthetic_key_group_break_mod_latch
987+
};
988+
xkb_filter_apply_all(state, &synthetic_key_break_mod_latch, XKB_KEY_DOWN);
989+
990+
/* Finally set the latched mods by simulate tapping a key with the
991+
* corresponding action */
992+
const struct xkb_key *key = &synthetic_key;
993+
const union xkb_action latch_mods = {
994+
.mods = {
995+
.type = ACTION_TYPE_MOD_LATCH,
996+
.mods = {
997+
.mask = mod_mask_get_effective(state->keymap, mask & latches)
998+
},
999+
.flags = 0,
1000+
},
1001+
};
1002+
struct xkb_filter *filter = xkb_filter_new(state);
1003+
filter->key = key;
1004+
filter->func = xkb_filter_mod_latch_func;
1005+
filter->action = latch_mods;
1006+
xkb_filter_mod_latch_new(state, filter);
1007+
/* We added the filter manually, so only fire “up” event */
1008+
xkb_filter_mod_latch_func(state, filter, key, XKB_KEY_UP);
1009+
}
1010+
1011+
/* Transcription from xserver: XkbLatchGroup */
1012+
static void
1013+
update_latch_group(struct xkb_state *state, int32_t group)
1014+
{
1015+
/* Clear any pending latch to locks. */
1016+
xkb_filter_apply_all(state, &synthetic_key_break_group_latch, XKB_KEY_DOWN);
1017+
1018+
/* Simulate tapping a key with a group latch action, but in isolation: i.e.
1019+
* without affecting the other filters. */
1020+
const struct xkb_key *key = &synthetic_key;
1021+
const union xkb_action latch_group = {
1022+
.group = {
1023+
.type = ACTION_TYPE_GROUP_LATCH,
1024+
.flags = ACTION_ABSOLUTE_SWITCH,
1025+
.group = group,
1026+
},
1027+
};
1028+
struct xkb_filter *filter = xkb_filter_new(state);
1029+
filter->key = key;
1030+
filter->func = xkb_filter_group_latch_func;
1031+
filter->action = latch_group;
1032+
xkb_filter_group_latch_new(state, filter);
1033+
/* We added the filter manually, so only fire “up” event */
1034+
xkb_filter_group_latch_func(state, filter, key, XKB_KEY_UP);
1035+
}
1036+
1037+
XKB_EXPORT enum xkb_state_component
1038+
xkb_state_update_latched_locked(struct xkb_state *state,
1039+
xkb_mod_mask_t affect_latched_mods,
1040+
xkb_mod_mask_t latched_mods,
1041+
bool affect_latched_layout,
1042+
int32_t latched_layout,
1043+
xkb_mod_mask_t affect_locked_mods,
1044+
xkb_mod_mask_t locked_mods,
1045+
bool affect_locked_layout,
1046+
int32_t locked_layout)
1047+
{
1048+
const struct state_components prev_components = state->components;
1049+
/* Only include modifiers which exist in the keymap. */
1050+
const xkb_mod_mask_t mask =
1051+
(xkb_mod_mask_t) ((1ull << xkb_keymap_num_mods(state->keymap)) - 1u);
1052+
1053+
/* Update locks */
1054+
affect_locked_mods &= mask;
1055+
if (affect_locked_mods) {
1056+
state->components.locked_mods &= ~affect_locked_mods;
1057+
state->components.locked_mods |= locked_mods & affect_locked_mods;
1058+
state->components.locked_mods =
1059+
mod_mask_get_effective(state->keymap, state->components.locked_mods);
1060+
}
1061+
if (affect_locked_layout) {
1062+
state->components.locked_group = locked_layout;
1063+
}
1064+
1065+
/* Update latches */
1066+
affect_latched_mods &= mask;
1067+
if (affect_latched_mods) {
1068+
update_latch_modifiers(state, affect_latched_mods, latched_mods);
1069+
}
1070+
if (affect_latched_layout) {
1071+
update_latch_group(state, latched_layout);
1072+
}
1073+
1074+
xkb_state_update_derived(state);
1075+
return get_state_component_changes(&prev_components, &state->components);
1076+
}
1077+
9231078
/**
9241079
* Updates the state from a set of explicit masks as gained from
9251080
* xkb_state_serialize_mods and xkb_state_serialize_groups. As noted in the
@@ -953,7 +1108,7 @@ xkb_state_update_mask(struct xkb_state *state,
9531108
*
9541109
* It might seem more reasonable to do this only for components.mods
9551110
* in xkb_state_update_derived(), rather than for each component
956-
* seperately. That would allow to distinguish between "really"
1111+
* separately. That would allow to distinguish between "really"
9571112
* depressed mods (would be in MODS_DEPRESSED) and indirectly
9581113
* depressed to to a mapping (would only be in MODS_EFFECTIVE).
9591114
* However, the traditional behavior of xkb_state_update_key() is that

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)