Skip to content

Sequences (not combinations): how to? #1057

@nlambert

Description

@nlambert

Full Compose Key Sequences with Keyd: Working but Possibly Too Complex?

Four Unicode characters brought me here: the em-dash, the left and right apostrophes, and the left and right quotation marks. I’d been copy-pasting them for too long and finally decided enough was enough.

Keyd looked like the right tool, and I’m glad I found it — but it took a bit of a journey to get everything working. I wanted to replicate the Compose key sequences found in the standard X11 Compose file, particularly:

# /usr/share/X11/locale/en_US.UTF-8/Compose:

<Multi_key> <minus> <minus> <minus>	: ""	U2014 # EM DASH
<Multi_key> <less> <apostrophe>		: ""	U2018 # LEFT SINGLE QUOTATION MARK
<Multi_key> <greater> <apostrophe>	: ""	U2019 # RIGHT SINGLE QUOTATION MARK
<Multi_key> <less> <quotedbl>		: ""	U201c # LEFT DOUBLE QUOTATION MARK
<Multi_key> <greater> <quotedbl>	: ""	U201d # RIGHT DOUBLE QUOTATION MARK

...

(And the reversed variants)

Context

I’m on Wayland with KDE, which has its own Compose key setting under:

Settings > Keyboard > Advanced > Key Bindings > Compose key position

I enabled that, setting Caps Lock as my Compose key. But under Wayland, support is partial and inconsistent:

  • Chromium respected the setting.
  • Kate, Konsole, Plasma apps didn’t.
  • VSCode worked.
  • Most terminals did not.

In contrast, Keyd gave me reliable results — but recreating the sequence logic was a challenge. It wasn’t immediately obvious how to implement a three- or four-key chain like Compose-Minus-Minus-Minus, especially when Shift had to stay held across transitions.


The Solution

After trial and error, I learned about composite layers (e.g., [layer1+layer2+layer3]). That was the breakthrough.

Here’s my working Keyd config to replicate the sequences above:

# /etc/keyd/default.conf

[ids]
*

[main]
capslock = oneshot(compose)

[compose]
minus = oneshot(compose_minus)
capslock = escape
backspace = capslock

apostrophe = oneshot(compose_apostrophe)
leftshift = layer(compose_shift)

[compose_apostrophe]
capslock = layer(compose)
leftshift = layer(compose_apostrophe_shift)

[compose_apostrophe_shift]
capslock = layer(compose)
comma = ‘
dot = ’

[compose_shift]
capslock = layer(compose)
apostrophe = oneshot(compose_shift_apostrophe)

comma = oneshot(compose_shift_comma)
dot = oneshot(compose_shift_dot)

[compose_shift_apostrophe]
capslock = layer(compose)
comma = “
dot = ”

[compose_shift_comma]
apostrophe = ‘

[compose+compose_shift+compose_shift_comma]
apostrophe = “

[compose_shift_dot]
apostrophe = ’

[compose+compose_shift+compose_shift_dot]
apostrophe = ”

[compose_minus]
capslock = layer(compose)
minus = oneshot(compose_minus_minus)

[compose_minus_minus]
capslock = layer(compose)
minus = —

N.B. I think I should have used capslock = clear() instead of capslock = layer(compose)

Summary of Supported Sequences

# 
# Press 1  Press 2           Press 3          Press 4    Output
# -----------------------------------------------------------------
# Compose  Minus             Minus            Minus      —
# Compose  Apostrophe        Shift+Comma                 ‘
# Compose  Apostrophe        Shift+Dot                   ’
# Compose  Shift+Apostrophe  Shift+Comma                 “
# Compose  Shift+Apostrophe  Shift+Dot                   ”
# Compose  Shift+Comma       Apostrophe                  ‘
# Compose  Shift+Dot         Apostrophe                  ‘
# Compose  Shift+Comma       Shift+Apostrophe            ”
# Compose  Shift+Dot         Shift+Apostrophe            “

The Concern

This config works. But it feels verbose. I also saw this warning:

WARNING: max layers (32) exceeded

While the current config doesn’t hit the limit, it’s easy to see how a similar approach could run into that wall with more sequences.

Moreover, in order for this to work in Chromium-and-co I need to replace the last key stroke with a macro like macro(leftcontrol+leftshift+u 2 0 1 c enter). But the issue is I have to duplicate the whole configuration, adding _alt as a suffix to each layer. Unless there's a way to override just the last keypress — not just the entrypoint.

Also, it took a lot of trial and error to arrive here — composite layers aren’t documented in depth, and I didn’t find examples using this multi-layer chaining pattern.

N.B. Composite layers are documented, but can track key sequences (not just combos), and they can leverage the release of a key too, but this wasn’t obvious until I watched keyd listen closely.

Feedback?

  • Is this approach reasonable?
  • Is there a simpler way to represent such sequences?

Thanks again for Keyd. It’s powerful — just hoping to help others skip the learning curve I hit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions