Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changes/api/redirect-key.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added support for [`RedirectKey()`](@ref redirect-key-action) action.
Please note that full support requires using the `xkb_state_machine` API.
21 changes: 15 additions & 6 deletions doc/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,20 @@ Unused in [xkeyboard-config] layouts.
</details>
</td>
</tr>
<!-- Keyboard emulation actions -->
<tr>
<th>Keyboard emulation</th>
<th>`RedirectKey()`</th>
<td>✅ Full support</td>
<td colspan="2">
<details>
<summary>✅ Full support (since 1.14)</summary>
- libxkbcommon \< 1.14: Parsing only.
- libxkbcommon ≥ 1.14: Full support. Note that the API support requires using
the `xkb_state_machine` API.
</details>
</td>
</tr>
<!-- Legacy actions -->
<tr>
<th rowspan="7">Legacy action</th>
Expand Down Expand Up @@ -560,12 +574,7 @@ Unused in [xkeyboard-config] layouts.
</tr>
<!-- Unsupported actions -->
<tr>
<th rowspan="6">Unsupported legacy action</th>
<th>`RedirectKey()`</th>
<td>✅ Full support</td>
<td colspan="2">❌️ Parsing only</td>
</tr>
<tr>
<th rowspan="5">Unsupported legacy action</th>
<th>`ISOLock()`</th>
<td>✅ Full support</td>
<td colspan="2">❌️ Parsing only</td>
Expand Down
149 changes: 76 additions & 73 deletions doc/keymap-text-format-v1-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -3127,15 +3127,15 @@ The following table provide an overview of the available actions:
| ^ | [`LockGroup`][LockGroup] | | Modifies the _locked_ group |
| [Keyboard controls action] | [`SetControls`][SetControls] | | Set the standard XKB controls |
| ^ | [`LockControls`][LockControls] | | Lock the standard XKB controls |
| [Keyboard emulation action] | [`RedirectKey`][redirectkey] | `Redirect` | Emulate pressing a key with a different key code |
| [Legacy action] | `MovePointer`| `MovePtr` | Move the mouse pointer |
| ^ | `PointerButton` | `PtrBtn` | Simulate a mouse button press |
| ^ | `LockPointerButton` | `LockPtrBtn` | Simulate a mouse button press, locked until the action’s key is pressed again. |
| ^ | `SetPointerDefault` | `SetPtrDflt` | Set the default select button (???)|
| ^ | [`TerminateServer`][TerminateServer] | `Terminate` | Shut down the X server |
| ^ | `SwitchScreen` | | Switch virtual X screen |
| ^ | [`Private`][Private]| | Raw encoding of an action |
| [Unsupported legacy action]| [`RedirectKey`][redirectkey] | `Redirect` | Emulate pressing a key with a different key code |
| ^ | `ISOLock` | | Convert ordinary modifier key actions into lock actions while this action is active |
| [Unsupported legacy action] | `ISOLock` | | Convert ordinary modifier key actions into lock actions while this action is active |
| ^ | `DeviceButton` | `DevBtn` | Emulate an event from an arbitrary input device such as a joystick |
| ^ | `LockDeviceButton` | `LockDevBtn` | Emulate an event from an arbitrary input device such as a joystick |
| ^ | `DeviceValuator` | `DevVal` | <span class="todo">TODO</span> |
Expand Down Expand Up @@ -3795,6 +3795,80 @@ press.
</tbody>
</table>

### Keyboard emulation actions {#kbd-emulation-actions}

#### RedirectKey {#redirect-key-action}

[Keyboard emulation action]: @ref kbd-emulation-actions
[redirectkey]: @ref redirect-key-action

`RedirectKey` emulates pressing a key with a different key code.

`RedirectKey` normally redirects to another key on the *same device* as the key
or button which caused the event, else on the *core* keyboard device.

<table>
<caption>Parameters</caption>
<thead>
<tr>
<th>Name</th>
<th>Aliases</th>
<th>Data type</th>
<th>Default value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<th>`key`</th>
<td>`keycode`, `kc`</td>
<td>keycode</td>
<td>0</td>
<td>Target keycode to emulate</td>
<tr>
<th>`clearmodifiers`</th>
<td>`clearmods`</td>
<td>modifier mask</td>
<td>`none` (0)</td>
<td>Modifiers to clear</td>
</tr>
<tr>
<th>`modifiers`</th>
<td>`mods`</td>
<td>modifier mask</td>
<td>`none` (0)</td>
<td>Modifiers to add</td>
</tr>
</tbody>
</table>

<table>
<caption>Effects</caption>
<thead>
<tr>
<th>On key *press*</th>
<th>On key *release*</th>
</tr>
</thead>
<tbody>
<tr>
<td>

Key press causes a key press event for the key specified by the `key` parameter
instead of for the actual key. The state reported in this event reports of the
current *effective* modifiers changed as follow:
- Modifiers in the `clearmodifiers` parameter are cleared.
- Modifiers in the `modifiers` parameter are set.
</td>
<td>

Key release causes a key release event for the key specified by the `key`
parameter; the state field for this event consists of the *effective*
modifiers at the time of the release, changed as described on the key press.
</td>
</tbody>
</table>


### Legacy X11 actions {#legacy-x11-actions}

Expand Down Expand Up @@ -3921,77 +3995,6 @@ Examples:
@attention The following legacy actions are **unsupported**: they are parsed
and but *not validated* and are then completely *ignored*.

#### Redirect key {#redirect-key-action}

[redirectkey]: @ref redirect-key-action

`RedirectKey` emulates pressing a key with a different key code.

`RedirectKey` normally redirects to another key on the *same device* as the key
or button which caused the event, else on the *core* keyboard device.

<table>
<caption>Parameters</caption>
<thead>
<tr>
<th>Name</th>
<th>Aliases</th>
<th>Data type</th>
<th>Default value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<th>`key`</th>
<td>`keycode`, `kc`</td>
<td>keycode</td>
<td>0</td>
<td>Target keycode to emulate</td>
<tr>
<th>`clearmodifiers`</th>
<td>`clearmods`</td>
<td>modifier mask</td>
<td>`none` (0)</td>
<td>Modifiers to clear</td>
</tr>
<tr>
<th>`modifiers`</th>
<td>`mods`</td>
<td>modifier mask</td>
<td>`none` (0)</td>
<td>Modifiers to add</td>
</tr>
</tbody>
</table>

<table>
<caption>Effects</caption>
<thead>
<tr>
<th>On key *press*</th>
<th>On key *release*</th>
</tr>
</thead>
<tbody>
<tr>
<td>

Key press causes a key press event for the key specified by the `key` parameter
instead of for the actual key. The state reported in this event reports of the
current *effective* modifiers changed as follow:
- Modifiers in the `clearmodifiers` parameter are cleared.
- Modifiers in the `modifiers` parameter are set.
</td>
<td>

Key release causes a key release event for the key specified by the `key`
parameter; the state field for this event consists of the *effective*
modifiers at the time of the release, changed as described on the key press.
</td>
</tbody>
</table>

#### ISO lock

@todo `ISOLock`
Expand Down
11 changes: 5 additions & 6 deletions src/keymap-priv.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,6 @@ action_equal(const union xkb_action *a, const union xkb_action *b)
if (a->type != b->type)
return false;

/* Ensure we support all action types */
static_assert(ACTION_TYPE_INTERNAL == 18 &&
ACTION_TYPE_INTERNAL + 1 == _ACTION_TYPE_NUM_ENTRIES,
"Missing action type");

switch (a->type) {
case ACTION_TYPE_NONE:
case ACTION_TYPE_VOID:
Expand Down Expand Up @@ -160,6 +155,10 @@ action_equal(const union xkb_action *a, const union xkb_action *b)
case ACTION_TYPE_CTRL_LOCK:
return (a->ctrls.flags == b->ctrls.flags &&
a->ctrls.ctrls == b->ctrls.ctrls);
case ACTION_TYPE_REDIRECT_KEY:
return (a->redirect.keycode == b->redirect.keycode &&
a->redirect.affect == b->redirect.affect &&
a->redirect.mods == b->redirect.mods);
case ACTION_TYPE_UNSUPPORTED_LEGACY:
return true;
/* ACTION_TYPE_PRIVATE processed in the default case */
Expand All @@ -169,7 +168,7 @@ action_equal(const union xkb_action *a, const union xkb_action *b)
default:
{} /* Label followed by declaration requires C23 */
/* Ensure to not miss `xkb_action_type` updates */
static_assert(ACTION_TYPE_INTERNAL == 18 &&
static_assert(ACTION_TYPE_INTERNAL == 19 &&
ACTION_TYPE_INTERNAL + 1 == _ACTION_TYPE_NUM_ENTRIES,
"Missing action type");
/* Private/custom action */
Expand Down
41 changes: 41 additions & 0 deletions src/keymap.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ enum xkb_action_type {
ACTION_TYPE_SWITCH_VT,
ACTION_TYPE_CTRL_SET,
ACTION_TYPE_CTRL_LOCK,
ACTION_TYPE_REDIRECT_KEY,
ACTION_TYPE_UNSUPPORTED_LEGACY,
ACTION_TYPE_PRIVATE,
ACTION_TYPE_INTERNAL, /* Action specific and internal to xkbcommon */
Expand Down Expand Up @@ -218,6 +219,25 @@ struct xkb_pointer_button_action {
uint8_t button;
};

struct xkb_redirect_key_action {
enum xkb_action_type type;
xkb_keycode_t keycode;
/*
* Next 2 fields are only used for parsing and serializing.
*
* We do not use `struct xkb_mods` here, because *both* fields denote
* *virtual* modifier indices, contrary to `xkb_mods`, where `mods` and
* `mask` denote respectively virtual and real mods.
*
* Ideally we would use 2 `xkb_mods` structs, but this would increase the
* `xkb_action` union type.
*/
/** Affected virtual modifiers */
xkb_mod_mask_t affect;
/** State of the affected virtual modifiers */
xkb_mod_mask_t mods;
};

struct xkb_private_action {
enum xkb_action_type type;
uint8_t data[7];
Expand Down Expand Up @@ -247,6 +267,7 @@ union xkb_action {
struct xkb_switch_screen_action screen;
struct xkb_pointer_action ptr;
struct xkb_pointer_button_action btn;
struct xkb_redirect_key_action redirect;
struct xkb_private_action priv;
struct xkb_internal_action internal;
};
Expand Down Expand Up @@ -657,6 +678,26 @@ enum {
void
clear_level(struct xkb_level *leveli);

/* ⚠️ Only valid before copying symbols to keymap */
static inline struct xkb_key *
XkbKeyByName(const struct xkb_keymap *keymap, xkb_atom_t name, bool use_aliases)
{
if (name < keymap->num_key_names) {
const KeycodeMatch match = keymap->key_names[name];
if (match.found) {
if (!match.is_alias) {
assert(name == keymap->keys[match.key.index].name);
return &keymap->keys[match.key.index];
} else if (use_aliases) {
assert(match.alias.real ==
keymap->keys[keymap->key_names[match.alias.real].key.index].name);
return &keymap->keys[keymap->key_names[match.alias.real].key.index];
}
}
}
return NULL;
}

static inline const struct xkb_key *
XkbKey(struct xkb_keymap *keymap, xkb_keycode_t kc)
{
Expand Down
Loading
Loading