Skip to content

andresousadotpt/texpand

Repository files navigation

texpand

Lightweight Wayland text expander. Reads raw keyboard events via evdev, types replacements directly via uinput. Works on any Wayland compositor (KDE, GNOME, Hyprland, Sway, etc.).

Single static binary. YAML config (espanso-compatible format). Zero runtime dependencies (optional: wtype for Unicode, wl-clipboard as last-resort fallback).

Warning: This was vibe coded. It works, but don't expect anything from it xD.

How it works

[Keyboard] ──evdev──→ texpand ──uinput──→ [Any App]
  1. Monitors /dev/input/event* devices via evdev (non-exclusive)
  2. Maintains a rolling buffer of recent keystrokes
  3. On match: backspaces the trigger, types the replacement via uinput (falls back to wtype for Unicode, then clipboard paste as last resort)

texpand watches /dev/input while it runs. If a keyboard disappears and reappears, for example when a monitor with an attached USB hub changes input or powers off and on, texpand rescans devices and starts monitoring the new event node without requiring a service restart.

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

Keyboard stops working after monitor input changes

texpand automatically rescans /dev/input when keyboard event devices are created, removed, renamed, or have permissions updated. It also performs a periodic rescan as a fallback.

Run with debug logging if expansions stop after changing monitor input or power cycling a monitor:

./texpand --debug

The logs include keyboard disconnect, reconnect, and rescan messages. If no keyboard is reconnected, check that the new /dev/input/event* device is readable by your user and that your user is still in the input group.

"/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

About

Lightweight Wayland text expander. Vibe Coded πŸ’€

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages