README
¶
texpand
Lightweight Wayland text expander. Reads raw keyboard events via evdev, expands triggers via clipboard paste. Works on any Wayland compositor (KDE, GNOME, Hyprland, Sway, etc.).
Single static binary. YAML config (espanso-compatible format). Zero runtime dependencies beyond wl-clipboard.
Warning: This was vibe coded. It works, but don't expect anything from it xD.
How it works
[Keyboard] ──evdev──→ texpand ──wl-copy + Ctrl+V──→ [Any App]
- Monitors
/dev/input/event*devices via evdev (non-exclusive) - Maintains a rolling buffer of recent keystrokes
- On match: backspace the trigger, copy replacement to clipboard, Ctrl+V paste, restore clipboard
Two trigger modes (set globally in config.yml):
- Space (default): fires when space is pressed after the trigger
- Immediate: fires as soon as the trigger is typed
Config changes are picked up automatically — no restart needed.
Install
go install github.com/andresousadotpt/texpand@latest
Initialize config
texpand init
Creates ~/.config/texpand/match/ with default YAML trigger files.
Set up permissions
texpand reads from /dev/input/ and writes to /dev/uinput.
# Add your user to the input group
sudo usermod -aG input $USER
# Ensure the uinput module loads at boot
echo uinput | sudo tee /etc/modules-load.d/uinput.conf
sudo modprobe uinput
# Allow input group to write to /dev/uinput
sudo cp 99-uinput.rules /etc/udev/rules.d/99-uinput.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
# Log out and back in for group change to take effect
Systemd service
cp texpand.service ~/.config/systemd/user/texpand.service
systemctl --user daemon-reload
systemctl --user enable --now texpand.service
Update
go install github.com/andresousadotpt/texpand@latest
systemctl --user restart texpand.service
After updating, run the migration command to update your config files to the latest format:
texpand migrate
This safely removes deprecated fields, creates .bak backups of modified files, and is idempotent (safe to run multiple times).
To pick up new default config files (without overwriting your existing ones):
texpand init
Config format
YAML files in ~/.config/texpand/match/*.yml. Espanso-compatible subset.
Global settings (config.yml)
~/.config/texpand/config.yml controls global behavior:
# "space" (default) - triggers fire on space
# "immediate" - triggers fire as soon as typed
trigger_mode: space
Simple trigger
matches:
- trigger: "'date"
replace: "{{_date}}"
Multiple triggers for same replacement
matches:
- triggers: ["'binsh", "'#!"]
replace: "#!/bin/sh"
Date variables
global_vars:
- name: _date
type: date
params:
format: "%d/%m/%Y"
matches:
- trigger: "'date"
replace: "{{_date}}"
Date with offset (tomorrow/yesterday)
matches:
- trigger: "'tdate"
replace: "{{tomorrow}}"
vars:
- name: tomorrow
type: date
params:
format: "%a %m/%d/%Y"
offset: 86400
Cursor positioning
Use $|$ to mark where the cursor should land after expansion:
matches:
- trigger: "'11"
replace: "{{time_with_ampm}} - 1:1 with [$|$]"
Supported strftime tokens
| Token | Meaning | Example |
|---|---|---|
%Y |
4-digit year | 2026 |
%m |
Month (zero-padded) | 02 |
%d |
Day (zero-padded) | 23 |
%H |
Hour 24h | 14 |
%I |
Hour 12h | 02 |
%M |
Minute | 30 |
%S |
Second | 05 |
%p |
AM/PM | PM |
%a |
Short weekday | Mon |
%A |
Full weekday | Monday |
%b |
Short month | Jan |
%B |
Full month | January |
All default triggers
Accented characters (fire immediately)
| Trigger | Output | Trigger | Output |
|---|---|---|---|
]a |
á | ]A |
Á |
}a |
à | }A |
Á |
~a |
ã | ~o |
õ |
]e |
é | ]E |
É |
}e |
è | }E |
È |
]i |
í | ]I |
Í |
}i |
ì | }I |
Ì |
]o |
ó | ]O |
Ó |
}o |
ò | }O |
Ò |
]u |
ú | ]U |
Ú |
}u |
ù | }U |
Ù |
'c, |
ç |
Symbols (fire on space)
| Trigger | Output |
|---|---|
'deg |
º |
'... |
... |
euros |
€ |
Coding shortcuts (fire on space)
| Trigger | Output |
|---|---|
'binsh / '#! |
#!/bin/sh |
'gsm |
git switch main && git pull origin main |
'gpomr |
git pull origin main --rebase |
Date & time (fire on space)
Usage
texpand [--debug] [init|version|migrate]
| Command | Description |
|---|---|
| (none) | Run texpand (monitor keyboards, expand triggers) |
init |
Create default config in ~/.config/texpand/ |
version |
Print version |
migrate |
Migrate config files to the latest format |
| Trigger | Example output |
|---|---|
'n |
10:56 AM - |
'date |
23/02/2026 |
'ddate |
Mon 23/02/2026 |
'nn |
Mon 23/02/2026 - 10:56 AM - |
'st |
Mon 23/02/2026 - 10:56 AM - meeting start |
'end |
Mon 23/02/2026 - 10:56 AM - meeting end |
'11 |
10:56 AM - 1:1 with [cursor] |
'tdate |
Tomorrow's date |
'ydate |
Yesterday's date |
Adding triggers
Edit or create YAML files in ~/.config/texpand/match/. Changes are picked up automatically — no restart needed.
Managing the service
systemctl --user status texpand.service # Check status
journalctl --user -u texpand.service -f # View logs
systemctl --user restart texpand.service # Restart after config changes
systemctl --user stop texpand.service # Stop
systemctl --user disable texpand.service # Disable auto-start
Debugging
Run texpand directly in a terminal (not via systemd) to see diagnostic output:
# Stop the service first to avoid conflicts
systemctl --user stop texpand.service
# Run in foreground — shows detected keyboards and trigger count
./texpand
You'll see output like:
texpand: monitoring 2 keyboard(s) — 35 triggers loaded
AT Translated Set 2 keyboard
Logitech USB Receiver
Debug mode
Use --debug (or -d) for verbose output on stderr — shows config loading, trigger mode, loaded triggers, buffer state, and match decisions:
./texpand --debug
Checking what config was loaded
Run texpand init to see the config directory, then inspect the YAML files:
texpand init # Shows config path, skips existing files
ls ~/.config/texpand/match/
Watching events in real time
To see raw kernel input events (useful for verifying your keyboard is detected):
# List all input devices
ls -la /dev/input/event*
# Watch events from a specific device (Ctrl+C to stop)
# Requires: sudo pacman -S evtest
sudo evtest /dev/input/event0
Checking clipboard operations
If triggers fire but paste wrong text, verify wl-clipboard works:
echo "test" | wl-copy
wl-paste -n # Should print "test"
Systemd logs
# Live logs
journalctl --user -u texpand.service -f
# Last 50 lines
journalctl --user -u texpand.service -n 50
# Since last boot
journalctl --user -u texpand.service -b
Troubleshooting
"No keyboard devices found"
groups # Should include 'input'
sudo usermod -aG input $USER
# Log out and back in
"/dev/uinput" permission denied
The most common cause is the uinput kernel module not being loaded at boot:
# Ensure the module loads at boot and load it now
echo uinput | sudo tee /etc/modules-load.d/uinput.conf
sudo modprobe uinput
# Install the udev rule and reload
sudo cp 99-uinput.rules /etc/udev/rules.d/99-uinput.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
# If udevadm trigger fails with "No such device", fix permissions manually:
sudo chgrp input /dev/uinput && sudo chmod 0660 /dev/uinput
ls -la /dev/uinput # Should show crw-rw---- root input
WAYLAND_DISPLAY not set
texpand auto-detects the Wayland socket at startup. If it fails:
systemctl --user import-environment WAYLAND_DISPLAY
systemctl --user restart texpand.service
Wrong characters
The keymap assumes US/International layout. Letters and numbers work across layouts, but symbol keys (], }, ~, ') may differ.
License
MIT
Documentation
¶
There is no documentation for this package.