Site banner Colonel's Pensieve

HHKB Layout and macOS-Style Keybindings on Ubuntu 24.04

If you’re coming from macOS, the muscle memory for Cmd+C/V/X is hard to break. Or if you’ve used Omarchy, you’ll find how the remap makes total sense given today’s terminal-focused trend reversal. This post shows how to set up macOS-style keybindings on Ubuntu 24.04 using a two-layer approach:

  1. keyd - low-level keyboard remapping (remap internal laptop keyboard to HHKB layout)
  2. xremap - application-aware Super→Ctrl shortcuts

The Problem

The naive solution of swapping Alt and Super in GNOME settings doesn’t work because:

  • GNOME intercepts Super key presses for the Activities overview
  • Terminals need Ctrl+C for SIGINT, not copy
  • Some apps (like Chrome) don’t respect GTK keybinding overrides

Install keyd

keyd runs at the kernel level, remapping keys before they reach the desktop.

# Install dependencies
sudo apt install build-essential git

# Clone and build
cd ~/dev
git clone https://github.com/rvaiya/keyd
cd keyd
make
sudo make install

# Enable service
sudo systemctl enable keyd
sudo systemctl start keyd

Configure keyd

Create /etc/keyd/default.conf:

[ids]
*

[main]
capslock = leftcontrol
leftcontrol = capslock
leftmeta = leftalt
leftalt = leftmeta
rightalt = rightmeta
rightcontrol = rightalt

This swaps:

  • Caps Lock ↔ Left Ctrl - Caps Lock becomes a useful key
  • Left Meta ↔ Left Alt - Super key moves next to spacebar (macOS position)
  • Right Alt → Right Meta - Consistent right side
  • Right Ctrl → Right Alt - Free up for compose/AltGr

Reload after changes:

sudo keyd reload

Internal Keyboard Only

If you use an external keyboard with a different layout, target only the internal keyboard:

# Find your keyboard ID
sudo keyd -m
# Press keys on internal keyboard, note the ID (e.g., 0001:0001)

Create /etc/keyd/internal.conf with specific IDs:

[ids]
0001:0001

[main]
capslock = leftcontrol
leftcontrol = capslock
backslash = backspace
backspace = backslash
grave = esc
delete = grave
leftmeta = leftalt
leftalt = leftmeta
rightalt = rightmeta
rightcontrol = rightalt

Additional HHKB-style mappings:

  • Backslash ↔ Backspace - Backspace moves to a more accessible position
  • Delete → Grave/tilde - Grave moves to Delete key position
  • Grave/tilde → Escape - Escape at top-left corner

Install xremap

xremap provides application-aware key remapping. It can remap Super+C to Ctrl+C everywhere except terminals.

# Install Rust if needed
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

# Install xremap with GNOME support
cargo install xremap --features gnome

udev Rules

xremap needs access to input devices:

# Add user to input group
sudo usermod -aG input $USER

# Create udev rule
echo 'KERNEL=="uinput", GROUP="input", MODE="0660"' | \
  sudo tee /etc/udev/rules.d/99-uinput.rules

# Reload rules
sudo udevadm control --reload-rules
sudo udevadm trigger

Important: You must log out and back in for group changes to take effect. xremap will not work until you do this.

Create Config Directory

mkdir -p ~/.config/xremap

GNOME Shell Extension

For app-specific remapping, xremap needs to know which app is focused. Install the GNOME extension:

# Clone extension
mkdir -p ~/.local/share/gnome-shell/extensions
cd ~/.local/share/gnome-shell/extensions
git clone https://github.com/xremap/xremap-gnome.git xremap@k0kubun.com

# Enable it
gnome-extensions enable xremap@k0kubun.com

Important: After installing the extension, you must restart GNOME Shell or log out/in for it to be recognized. Without this, xremap cannot detect which app is focused.

Configure xremap

Create ~/.config/xremap/config.yml:

keymap:
  - name: Super clipboard for all apps except terminals
    application:
      not:
        - gnome-terminal
        - gnome-terminal-server
        - Gnome-terminal
        - ghostty
        - com.mitchellh.ghostty
        - kitty
        - alacritty
    remap:
      Super-c: C-c
      Super-v: C-v
      Super-x: C-x
      Super-a: C-a
      Super-z: C-z
      Super-Shift-z: C-Shift-z

This maps Super+C/V/X/A/Z to their Ctrl equivalents, except in terminal emulators where Ctrl+C means interrupt.

Create xremap Service

Create ~/.config/systemd/user/xremap.service:

[Unit]
Description=xremap key remapper
After=graphical-session.target

[Service]
ExecStart=%h/.cargo/bin/xremap --device "keyd virtual keyboard" %h/.config/xremap/config.yml
Restart=always
RestartSec=3

[Install]
WantedBy=default.target

Important: The --device "keyd virtual keyboard" flag is critical. Without it, xremap won’t see any devices because keyd intercepts the physical keyboard.

Enable and start:

systemctl --user daemon-reload
systemctl --user enable xremap
systemctl --user start xremap

GNOME Settings

I moved away Super key mapping from GNOME - this should be optional, but who knows :-)

GNOME intercepts Super key by default. Disable these behaviors:

# Prevent Super from opening Activities
gsettings set org.gnome.mutter overlay-key ''

# Or remap to a different key
gsettings set org.gnome.mutter overlay-key 'Alt_R'

# Disable mouse-button-modifier (interferes with Super+click)
gsettings set org.gnome.desktop.wm.preferences mouse-button-modifier ''

# Disable message tray shortcut
gsettings set org.gnome.shell.keybindings toggle-message-tray '[]'

Terminal Clipboard Shortcuts

Terminals excluded from xremap need native Super+C/V bindings:

GNOME Terminal: Edit → Preferences → Shortcuts

Ghostty (~/.config/ghostty/config):

keybind = super+c=copy_to_clipboard
keybind = super+v=paste_from_clipboard

Kitty (~/.config/kitty/kitty.conf):

map super+c copy_to_clipboard
map super+v paste_from_clipboard

Troubleshooting

xremap not detecting apps

Check if the GNOME extension is working:

gdbus call --session \
  --dest org.gnome.Shell \
  --object-path /com/k0kubun/Xremap \
  --method com.k0kubun.Xremap.ActiveWindow

Should return the current window’s WM_CLASS.

keyd not working

# Check service status
sudo systemctl status keyd

# Test interactively
sudo keyd -m
# Press keys and verify output

Super key still opens Activities

# Verify setting
gsettings get org.gnome.mutter overlay-key
# Should be '' or your chosen alternative

What Didn’t Work

GTK CSS keybindings: GTK3’s -gtk-key-bindings CSS property was removed in GTK4. Even for GTK3, Chrome ignores it.

input-remapper: GUI tool that works but lacks app-specific remapping. keyd + xremap is more flexible.

GNOME Tweaks: Can swap Alt/Super globally but doesn’t help with the Super+C → Ctrl+C translation.

Setup Order & Critical Notes

The order matters because of how these tools interact:

  1. Install and start keyd first - Creates a virtual keyboard that intercepts physical input
  2. Add yourself to input group and log out/in - Required for xremap permissions
  3. Install GNOME extension and restart shell - Required for app detection
  4. Configure xremap to use keyd’s virtual keyboard - Must use --device "keyd virtual keyboard"
  5. Start xremap - Now it can see keyd’s virtual keyboard and do app-specific remapping

Without these steps in order, xremap won’t see any input devices or won’t know which app is focused.

Summary

LayerToolPurpose
KernelkeydPhysical key swaps (Caps↔Ctrl, Meta↔Alt), HHKB layout
UserxremapSuper→Ctrl translation, app-specific rules
AppNative configTerminal Super+C/V bindings
DesktopgsettingsDisable GNOME Super key interception

After this setup, your muscle memory from macOS just works: Super+C copies, Super+V pastes, and terminals still get Ctrl+C for interrupt.

← Back to home