SkillAgentSearch skills...

Dvorak

Wayland keyboard remapping with dvorak - make ctrl-c ctrl-c again :)

Install / Use

/learn @tbocek/Dvorak
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Dvorak <> Qwerty - Keyboard remapping for Linux when pressing L-CTRL, R-CTRL, L-ALT, L-WIN, CAPSLOCK

Since I type with the "Dvorak" keyboard layout, the shortcuts such as ctrl-c, ctrl-x, or ctrl-v are not comfortable anymore for using the left hand only.

Furthermore, many applications have their default shortcuts, which I'm used to. So for these shortcuts I prefer "Qwerty". Since there is no way to configure this, this program intercepts these keys and remaps them from "Dvorak" to "Qwerty" when pressing L-CTRL, R-CTRL, L-ALT, L-WIN, CAPSLOCK, or any of those combinations. CAPSLOCK is also used as a modifier, but can be disabled with the "-c" flag.

With X11 I was relying on the xdq from Kenton Varda. However, this does not work reliably with Wayland.

Keyboard remapping with dvorak that works reliably with Wayland - make ctrl-c ctrl-c again (and not ctrl-i)

X11's XGrabKey() works partially with some application but not with others (e.g., gedit is not working). Since XGrabKey() is an X11 function with some support in Wayland, I was looking for a more stable solution. After a quick look to this repo, I saw that Kenton added a systemtap script to implement the mapping. It scared me a bit to follow the systemtap path, so I implemented an other solution based on /dev/uinput. The idea is to read /dev/input, grab keys with EVIOCGRAB, create a virtual device that can emit the keys and pass the keys from /dev/input to /dev/uinput. If L-CTRL, R-CTRL, L-ALT, L-WIN, CAPSLOCK is pressed it will map the keys back to "Qwerty".

This program is tested with Arch and Ubuntu, and Kenton Varda reported that it also works with Chrome OS.

The input scrambling problem

Because dvorak works at the /dev/input level — grabbing raw events with EVIOCGRAB and re-emitting them through a virtual /dev/uinput device — it operates completely independently of the desktop environment's keyboard layout settings. This means:

  • Switching layouts scrambles shortcuts. If you switch the OS keyboard layout from Dvorak to Qwerty (e.g., for a different language, a different user, or a non-Dvorak keyboard), dvorak keeps remapping modifier combinations. The result is double-remapped shortcuts: pressing Ctrl+C no longer produces Ctrl+C in either layout — it produces something unexpected like Ctrl+I.

  • Multiple keyboards, one mapping. If you have multiple keyboards attached (e.g., a laptop built-in keyboard and an external USB keyboard), each running its own dvorak daemon, switching the OS layout for one keyboard affects the other, but the daemons have no way to know.

  • No feedback from the desktop. Since dvorak sits below the display server, it cannot observe layout changes made via GNOME Settings, setxkbmap, swaymsg input, or any other desktop-level tool.

Solution: signal-based mode switching

The dvorak program now supports two Unix signals to externally control whether remapping is active:

| Signal | Effect | |-----------|-----------------------------------------------------------------| | SIGUSR1 | On — enable Dvorak-to-Qwerty remapping (original behavior) | | SIGUSR2 | Off — passthrough mode (no remapping; all keys forwarded as-is) |

This allows an external script, desktop shortcut, or layout-switching hook to tell all running dvorak daemons to disable remapping when the OS layout is not Dvorak, and re-enable it when switching back.

Thread safety: if a signal arrives while a modifier-based shortcut is in progress (e.g., the user is holding Ctrl+C), the mode change is deferred until all modifier and remapped keys are released. This prevents key events from being split across two different mapping states, which would result in stuck or phantom keys.

Signal authority: once a signal has set the mode, the Left-Alt triple-press toggle is suppressed. Only another signal can change the mode. This prevents accidental toggling via the keyboard when the mode is being managed externally.

Project structure

.
├── 80-dvorak.rules              # udev rule — triggers dvorak on device attach
├── dvorak                       # compiled binary
├── dvorak.c                     # source code
├── dvorak@.service              # systemd template service (basic udev-triggered mode)
├── examples/
│   ├── dvorak-signal-sudoers    # sudoers rule for passwordless signaling
│   ├── dvorak-signal.sh         # send on/off signals to all dvorak daemons
│   ├── dvorak-start.sh          # daemon launcher with retry, cleanup, and PID files
│   ├── dvorak-usb.service       # systemd template service for dvorak-start.sh
│   └── sway_layout_switch_example.sh  # example Sway layout switcher with signal integration
├── LICENSE
├── Makefile
└── README.md

Installation

Option A: Basic installation (udev-triggered)

This is the simplest setup. It automatically starts dvorak whenever a matching keyboard is attached, but does not support signal-based mode switching.

make
sudo make install

This will copy 3 files: dvorak, 80-dvorak.rules, and dvorak@.service.

The udev rule triggers the dvorak systemd service whenever an input device is attached. The rule contains a search term (e.g., "keyb k360 k750") that matches the device name case-insensitively. Only devices whose name contains a matching substring will be considered. The newly created virtual device is excluded from mapping itself to prevent an endless loop.

If your keyboard name does not match the default keywords, edit the udev rule and service file to add a keyword matching your keyboard.

Option B: Installation with signal support (recommended for multi-layout setups)

If you switch between keyboard layouts at the OS level (e.g., Dvorak, Qwerty, Ukrainian, Russian), use this setup. It adds PID file tracking, signal-based on/off control, and a sudoers rule so your user can signal the root-owned daemons without a password.

Prerequisites

Your user must be in the input group (for the sudoers rule and /dev/input access):

sudo usermod -aG input $USER

Log out and back in for the group change to take effect. Verify with:

groups | grep input

Step 1: Build and install the binary

make
sudo cp dvorak /usr/local/bin/dvorak
sudo chmod 755 /usr/local/bin/dvorak

Step 2: Install the daemon start script

sudo cp examples/dvorak-start.sh /usr/local/bin/dvorak-start.sh
sudo chmod 755 /usr/local/bin/dvorak-start.sh

dvorak-start.sh takes a keyboard name as an argument and:

  • Searches /sys/class/input/ for a device matching the given name
  • Retries for up to 120 seconds (useful for USB devices that appear after boot)
  • Kills any stale dvorak process already holding the device
  • Starts dvorak with a PID file at /run/dvorak-<eventN>.pid

Step 3: Install the signal script

sudo cp examples/dvorak-signal.sh /usr/local/bin/dvorak-signal.sh
sudo chmod 755 /usr/local/bin/dvorak-signal.sh

dvorak-signal.sh reads PID files from /run/dvorak-*.pid, verifies each process is actually a dvorak daemon, and sends the appropriate signal. It auto-elevates to root via sudo -n (non-interactive) when run as a regular user. Falls back to pkill if no valid PID files are found.

Step 4: Install the sudoers rule

The dvorak daemons run as root (started via systemd), so signaling them requires root. The sudoers rule allows members of the input group to run dvorak-signal.sh on and dvorak-signal.sh off as root without a password:

sudo cp examples/dvorak-signal-sudoers /etc/sudoers.d/dvorak-signal
sudo chmod 440 /etc/sudoers.d/dvorak-signal
sudo visudo -cf /etc/sudoers.d/dvorak-signal   # verify syntax — must print "parsed OK"

Security note: the sudoers rule points to /usr/local/bin/dvorak-signal.sh, which is root-owned. Never point a sudoers rule to a user-writable path (e.g., ~/.config/...) as that would allow arbitrary code execution as root.

Step 5: Install the systemd service

Copy the template service and reload systemd:

sudo cp examples/dvorak-usb.service /etc/systemd/system/dvorak-usb@.service
sudo systemctl daemon-reload

Enable and start one instance per keyboard, using the keyboard name as the instance identifier:

sudo systemctl enable --now "dvorak-usb@Logitech K750.service"
sudo systemctl enable --now "dvorak-usb@Das Keyboard.service"

Check the status:

systemctl status "dvorak-usb@Logitech K750.service"
journalctl -u "dvorak-usb@Logitech K750.service" -f

Step 6: Test signaling

# From a terminal — should auto-elevate via sudo and succeed:
dvorak-signal.sh off    # all daemons -> passthrough
dvorak-signal.sh on     # all daemons -> Dvorak remapping enabled

Step 7 (optional): Integrate with your desktop layout switcher

An example Sway layout switching script is provided at examples/sway_layout_switch_example.sh. It switches the Sway keyboard layout and automatically signals all dvorak daemons:

  • Switching to Dvorak (index 0) sends dvorak-signal.sh on
  • Switching to any other layout sends dvorak-signal.sh off

To use it with Sway, copy it to your scripts directory and bind it to a key:

cp examples/sway_layout_switch_example.sh ~/.config/sway/scripts/layout_switch.sh
chmod +x ~/.config/sway/scripts/layout_switch.sh

Add to your Sway config (~/.config/sway/config):

# Cycle layouts with Super+Space
bindsym $mod+space exec ~/.config/sway/scripts/layout_switch.sh

# Or switch to a specific layout
bindsym $mod+F1 exec ~/.config/sway/scripts/layout_switch.sh dvorak
bindsym $mod+F2 exec ~/.config/sway/scripts/layout_switch.sh us
bindsym $mod+F3 exec ~/.config/sway/scripts/layout_switch.sh ua
bindsym $mod+F4 exec ~/.config/sway/scripts/layout_switch.sh ru

Ada

View on GitHub
GitHub Stars82
CategoryDevelopment
Updated11d ago
Forks23

Languages

C

Security Score

95/100

Audited on Mar 16, 2026

No findings