Skip to content

Commit

Permalink
actions: Add support for multiple actions per level
Browse files Browse the repository at this point in the history
This makes 1 keysym == 1 action holds also for multiple keysyms per level.

The motivation of this new feature are:

- Make multiple keysyms per level more intuitive.
- Explore how to fix the issue with shortcuts in multi-layout settings
  (see the xkeyboard-config issue[^1]). The idea is to use e.g.:

  ```c
  key <LCTL> {
      symbols[1] = [ {Control_L,                  ISO_First_Group    } ],
      actions[1] = [ {SetMods(modifiers=Control), SetGroup(group=-4) } ]
  };
  ```

  in order to switch temporarily to a reference layout in order to get
  the same shortcuts on every layout.

When no action is specified, `interpret` statements are used to find
an action corresponding for *each* keysym, as expected.

For an interpretation matching Any keysym, we may get the same
interpretation for multiple keysyms. This may result in unwanted
duplicate actions. So set this interpretation only if no previous
keysym was matched with this interpret at this level, else set the
default interpretation.

For now, at most one action of each following categories is allowed
per level:
- modifier actions: `SetMods`, `LatchMods`, `LockMods`;
- group actions: `SetGroup`, `LatchGroup`, `LockGroup`.

Some examples:
- `SetMods` + `SetGroup`: ok
- `SetMods` + `SetMods`: error
- `SetMods` + `LockMods`: error
- `SetMods` + `LockGroup`: ok

[^1]: https://gitlab.freedesktop.org/xkeyboard-config/xkeyboard-config/-/issues/416
  • Loading branch information
wismill committed Oct 11, 2024
1 parent 772ac0c commit fdf2c52
Show file tree
Hide file tree
Showing 13 changed files with 766 additions and 306 deletions.
88 changes: 88 additions & 0 deletions src/keymap-priv.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,91 @@ XkbLevelsSameSyms(const struct xkb_level *a, const struct xkb_level *b)
return a->s.sym == b->s.sym;
return memcmp(a->s.syms, b->s.syms, sizeof(*a->s.syms) * a->num_syms) == 0;
}

bool
XkbLevelHasNoAction(const struct xkb_level *level)
{
if (level->num_syms == 0)
return true;
if (level->num_syms == 1)
return level->a.action.type == ACTION_TYPE_NONE;
for (unsigned int k = 0; k < level->num_syms; k++) {
if (level->a.actions[k].type != ACTION_TYPE_NONE)
return false;
}
return true;
}

xkb_layout_index_t
XkbWrapGroupIntoRange(int32_t group,
xkb_layout_index_t num_groups,
enum xkb_range_exceed_type out_of_range_group_action,
xkb_layout_index_t out_of_range_group_number)
{
if (num_groups == 0)
return XKB_LAYOUT_INVALID;

if (group >= 0 && (xkb_layout_index_t) group < num_groups)
return group;

switch (out_of_range_group_action) {
case RANGE_REDIRECT:
if (out_of_range_group_number >= num_groups)
return 0;
return out_of_range_group_number;

case RANGE_SATURATE:
if (group < 0)
return 0;
else
return num_groups - 1;

case RANGE_WRAP:
default:
/*
* C99 says a negative dividend in a modulo operation always
* gives a negative result.
*/
if (group < 0)
return ((int) num_groups + (group % (int) num_groups));
else
return group % num_groups;
}
}

unsigned int
xkb_keymap_key_get_actions_by_level(struct xkb_keymap *keymap,
xkb_keycode_t kc,
xkb_layout_index_t layout,
xkb_level_index_t level,
const union xkb_action **actions)
{
const struct xkb_key *key = XkbKey(keymap, kc);
if (!key)
goto err;

layout = XkbWrapGroupIntoRange(layout, key->num_groups,
key->out_of_range_group_action,
key->out_of_range_group_number);
if (layout == XKB_LAYOUT_INVALID)
goto err;

if (level >= XkbKeyNumLevels(key, layout))
goto err;

const unsigned int count = key->groups[layout].levels[level].num_syms;
switch (count) {
case 0:
goto err;
case 1:
*actions = &key->groups[layout].levels[level].a.action;
break;
default:
*actions = key->groups[layout].levels[level].a.actions;
}
return count;

err:
*actions = NULL;
return 0;
}
4 changes: 3 additions & 1 deletion src/keymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ xkb_keymap_unref(struct xkb_keymap *keymap)
for (unsigned i = 0; i < key->num_groups; i++) {
if (key->groups[i].levels) {
for (unsigned j = 0; j < XkbKeyNumLevels(key, i); j++)
if (key->groups[i].levels[j].num_syms > 1)
if (key->groups[i].levels[j].num_syms > 1) {
free(key->groups[i].levels[j].s.syms);
free(key->groups[i].levels[j].a.actions);
}
free(key->groups[i].levels);
}
}
Expand Down
21 changes: 18 additions & 3 deletions src/keymap.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,18 @@ enum xkb_explicit_components {
};

struct xkb_level {
union xkb_action action;
/* Count of keysyms/actions */
unsigned int num_syms;
/* Keysyms */
union {
xkb_keysym_t sym; /* num_syms == 1 */
xkb_keysym_t *syms; /* num_syms > 1 */
xkb_keysym_t sym; /* num_syms == 1 */
xkb_keysym_t *syms; /* num_syms > 1 */
} s;
/* Actions */
union {
union xkb_action action; /* num_syms == 1 */
union xkb_action *actions; /* num_syms > 1 */
} a;
};

/**
Expand Down Expand Up @@ -493,6 +498,9 @@ XkbModNameToIndex(const struct xkb_mod_set *mods, xkb_atom_t name,
bool
XkbLevelsSameSyms(const struct xkb_level *a, const struct xkb_level *b);

bool
XkbLevelHasNoAction(const struct xkb_level *level);

xkb_layout_index_t
XkbWrapGroupIntoRange(int32_t group,
xkb_layout_index_t num_groups,
Expand All @@ -502,6 +510,13 @@ XkbWrapGroupIntoRange(int32_t group,
xkb_mod_mask_t
mod_mask_get_effective(struct xkb_keymap *keymap, xkb_mod_mask_t mods);

unsigned int
xkb_keymap_key_get_actions_by_level(struct xkb_keymap *keymap,
xkb_keycode_t kc,
xkb_layout_index_t layout,
xkb_level_index_t level,
const union xkb_action **actions);

struct xkb_keymap_format_ops {
bool (*keymap_new_from_names)(struct xkb_keymap *keymap,
const struct xkb_rule_names *names);
Expand Down
Loading

0 comments on commit fdf2c52

Please sign in to comment.