Skip to content

Commit

Permalink
WIP: state: add API for updating latched and locked mods & layout in…
Browse files Browse the repository at this point in the history
… server state

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]>
  • Loading branch information
wismill and bluetech committed Oct 2, 2024
1 parent bf1ea08 commit b1855fa
Show file tree
Hide file tree
Showing 7 changed files with 504 additions and 2 deletions.
57 changes: 57 additions & 0 deletions include/xkbcommon/xkbcommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
#define _XKBCOMMON_H_

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdarg.h>

Expand Down Expand Up @@ -1471,6 +1472,62 @@ enum xkb_state_component
xkb_state_update_key(struct xkb_state *state, xkb_keycode_t key,
enum xkb_key_direction direction);

/**
* Update the keyboard state to change the latched and locked state of
* the modifiers and layout.
*
* This entry point is intended for *server* applications and should not be used
* by *client* applications; see @ref server-client-state for details.
*
* Use this function to update the latched and locked state according to
* “out of band” (non-device) inputs, such as UI layout switchers.
*
* @par Layout out of range
* @parblock
*
* If the effective layout, after taking into account the depressed, latched and
* locked layout, is out of range (negative or greater than the maximum layout),
* it is brought into range. Currently, the layout is wrapped using integer
* modulus (with negative values wrapping from the end). The wrapping behavior
* may be made configurable in the future.
*
* @endparblock
*
* @param affect_latched_mods
* @param latched_mods
* Modifiers to set as latched or unlatched. Only modifiers in
* `affect_latched_mods` are considered.
* @param affect_latched_layout
* @param latched_layout
* Layout to latch. Only considered if `affect_latched_layout` is true.
* Maybe be out of range (including negative) -- see note above.
* @param affect_locked_mods
* @param locked_mods
* Modifiers to set as locked or unlocked. Only modifiers in
* `affect_locked_mods` are considered.
* @param affect_locked_layout
* @param locked_layout
* Layout to lock. Only considered if `affect_locked_layout` is true.
* Maybe be out of range (including negative) -- see note above.
*
* @returns A mask of state components that have changed as a result of
* the update. If nothing in the state has changed, returns 0.
*
* @memberof xkb_state
*
* @sa xkb_state_update_mask()
*/
enum xkb_state_component
xkb_state_update_latched_locked(struct xkb_state *state,
xkb_mod_mask_t affect_latched_mods,
xkb_mod_mask_t latched_mods,
bool affect_latched_layout,
int32_t latched_layout,
xkb_mod_mask_t affect_locked_mods,
xkb_mod_mask_t locked_mods,
bool affect_locked_layout,
int32_t locked_layout);

/**
* Update a keyboard state from a set of explicit masks.
*
Expand Down
2 changes: 1 addition & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ test(
)
test(
'state',
executable('test-state', 'test/state.c', dependencies: test_dep),
executable('test-state', 'test/state.c', 'src/state.h', dependencies: test_dep),
env: test_env,
)
test(
Expand Down
119 changes: 119 additions & 0 deletions src/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
#include "keymap.h"
#include "keysym.h"
#include "utf8.h"
#ifdef ENABLE_PRIVATE_APIS
#include "state.h"
#endif

struct xkb_filter {
union xkb_action action;
Expand Down Expand Up @@ -864,6 +867,33 @@ get_state_component_changes(const struct state_components *a,
return mask;
}

#ifdef ENABLE_PRIVATE_APIS
void
xkb_state_set_components(
struct xkb_state *state,
int32_t base_group,
int32_t latched_group,
int32_t locked_group,
xkb_layout_index_t group,
xkb_mod_mask_t base_mods,
xkb_mod_mask_t latched_mods,
xkb_mod_mask_t locked_mods,
xkb_mod_mask_t mods,
xkb_led_mask_t leds)
{
state->components.base_group = base_group;
state->components.base_group = base_group;
state->components.latched_group = latched_group;
state->components.locked_group = locked_group;
state->components.group = group;
state->components.base_mods = base_mods;
state->components.latched_mods = latched_mods;
state->components.locked_mods = locked_mods;
state->components.mods = mods;
state->components.leds = leds;
}
#endif

/**
* Given a particular key event, updates the state structure to reflect the
* new modifiers.
Expand Down Expand Up @@ -911,6 +941,95 @@ xkb_state_update_key(struct xkb_state *state, xkb_keycode_t kc,
return get_state_component_changes(&prev_components, &state->components);
}

/* We need a fake key for XkbLatchModifiers and XkbLatchGroup */
static const struct xkb_key synthetic_key = { 0 };

// XXX transcription from xserver
static void
XkbLatchModifiers(struct xkb_state *state, xkb_mod_mask_t mask, xkb_mod_mask_t latches)
{
const struct xkb_key *key = &synthetic_key;

xkb_mod_mask_t clear = mask & (~latches);
state->components.latched_mods &= ~clear;
/* Clear any pending latch to locks. */
xkb_filter_apply_all(state, key, XKB_KEY_DOWN);

union xkb_action latch_mods = {
.mods = {
.type = ACTION_TYPE_MOD_LATCH,
.mods = {
.mask = mask & latches,
},
.flags = 0,
},
};
struct xkb_filter *filter = xkb_filter_new(state);
filter->key = key;
filter->func = filter_action_funcs[latch_mods.type].func;
filter->action = latch_mods;
xkb_filter_mod_latch_new(state, filter);
/* We added the filter manually, so only fire up event */
xkb_filter_mod_latch_func(state, filter, key, XKB_KEY_UP);
}

// XXX transcription from xserver
static void
XkbLatchGroup(struct xkb_state *state, int32_t group)
{
const struct xkb_key *key = &synthetic_key;

union xkb_action latch_group = {
.group = {
.type = ACTION_TYPE_GROUP_LATCH,
.flags = 0,
.group = group,
},
};
struct xkb_filter *filter = xkb_filter_new(state);
filter->key = key;
filter->func = filter_action_funcs[latch_group.type].func;
filter->action = latch_group;
xkb_filter_group_latch_new(state, filter);
/* We added the filter manually, so only fire up event */
xkb_filter_group_latch_func(state, filter, key, XKB_KEY_UP);
}

XKB_EXPORT enum xkb_state_component
xkb_state_update_latched_locked(struct xkb_state *state,
xkb_mod_mask_t affect_latched_mods,
xkb_mod_mask_t latched_mods,
bool affect_latched_layout,
int32_t latched_layout,
xkb_mod_mask_t affect_locked_mods,
xkb_mod_mask_t locked_mods,
bool affect_locked_layout,
int32_t locked_layout)
{
struct state_components prev_components = state->components;

if (affect_locked_mods) {
state->components.locked_mods &= ~affect_locked_mods;
state->components.locked_mods |= locked_mods & affect_locked_mods;
}

if (affect_locked_layout) {
state->components.locked_group = locked_layout;
}

if (affect_latched_mods) {
XkbLatchModifiers(state, affect_latched_mods, latched_mods);
}

if (affect_latched_layout) {
XkbLatchGroup(state, latched_layout);
}

xkb_state_update_derived(state);

return get_state_component_changes(&prev_components, &state->components);
}

/**
* Updates the state from a set of explicit masks as gained from
* xkb_state_serialize_mods and xkb_state_serialize_groups. As noted in the
Expand Down
20 changes: 20 additions & 0 deletions src/state.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#ifndef STATE_H
#define STATE_H

#include "xkbcommon/xkbcommon.h"
#include "keymap.h"

void
xkb_state_set_components(
struct xkb_state *state,
int32_t base_group,
int32_t latched_group,
int32_t locked_group,
xkb_layout_index_t group,
xkb_mod_mask_t base_mods,
xkb_mod_mask_t latched_mods,
xkb_mod_mask_t locked_mods,
xkb_mod_mask_t mods,
xkb_led_mask_t leds);

#endif
Loading

0 comments on commit b1855fa

Please sign in to comment.