SnipExpand
Short triggers. Complete text.
SnipExpand is a focused text expander for Linux/Wayland, developed specifically to
work flawlessly on Omarchy. It listens for shortcuts
through evdev and types their replacements through a persistent Wayland
virtual keyboard, with uinput as a compatibility fallback. It avoids
application-specific integrations and clipboard mutation.
It is built and tested primarily on Omarchy and Hyprland. SnipExpand is young and its first release is deliberately focused, but the ambition is larger: make text expansion best-in-class again, beginning with the Linux/Wayland environments where reliability remains difficult. Cross-platform backends may follow without changing the config-first product at its core.
Why SnipExpand?
Espanso became the gold standard for config-based text expansion for good reason. It made shortcuts portable, inspectable, versionable, and independent of any one editor or application. It also grew into a capable cross-platform automation system with scripts, forms, packages, rich content, and a mature community.
SnipExpand builds on that model, a personal shorthand library owned as ordinary files, while starting with reliable Linux/Wayland input. Its kernel-level input path works without waiting for every application to expose an integration. The first release keeps a tight scope so the foundation can be understood, validated, and trusted; that scope is a starting point, not the ceiling.
AI makes that kind of shorthand more useful, not less. Generative tools are excellent when the answer needs thought or variation. Text expansion is better when the answer is already known: an email address, a signature, a command, a template, a link, or a phrase you type every day. SnipExpand is instant, deterministic, local, available in every text field, and easy for either you or an AI coding agent to maintain as plain YAML.
Why start with Linux and Wayland?
Wayland intentionally limits global input and injection, and dependable text expansion still falls between the compositor, keyboard layout, permissions, and application toolkit. This is not only a theoretical concern. Espanso users have reported the service appearing active while expansions stop until a manual restart (#2223), expansion stopping after hours of use (#2423), input detection silently ending on Arch/KDE Wayland (#2262), application-specific failures (#2162), and keyboard layout or Unicode problems (#1868, #2497). These reports cover different systems and do not imply that Espanso fails for everyone. They do show why Linux/Wayland deserves a focused reliability effort of its own.
Omarchy and Wayland are SnipExpand's first-class platforms. Omarchy provides an opinionated Arch/Hyprland environment where the whole path can be tested as one experience. Additional operating systems and desktop backends may follow, but they do not replace Omarchy as the reference environment and reliability bar.
SnipExpand and Espanso
Espanso remains the benchmark for breadth and ecosystem. SnipExpand is competing first on reliability, immediacy, configuration quality, and a polished Linux/Wayland experience, then growing from that foundation.
| Capability | SnipExpand | Espanso |
|---|---|---|
| Primary focus | Best-in-class expansion, starting on Linux/Wayland | Cross-platform expansion and automation |
| Tested desktop | Hyprland | Linux, macOS, and Windows environments |
| Configuration | Espanso-style YAML | YAML |
| Static and multiline matches | Yes | Yes |
| Cursor placement | `$ | $` |
| Word boundaries | Yes | Yes |
| Case propagation | Yes | Yes |
| Date variables | Yes | Yes |
| Backspace undo | Simple expansions | Yes |
| Application exclusions | Title/class/executable regex | Per-app configuration and filters |
| Unicode outside the active layout | Persistent modifier-free Wayland keymaps, with wtype fallback |
Platform-dependent backends |
| Shell/scripts and dynamic variables | No | Yes |
| Forms, choices, images, and rich text | No | Yes |
| Packages and community ecosystem | No | Yes |
| Clipboard injection backend | No | Yes |
| Configuration validation | Rejects unknown fields | Broader schema |
| Maturity | Early release | Established project |
See the exact compatibility matrix before reusing an existing Espanso configuration.
Highlights
- Immediate or terminator-based expansion
- Recursive YAML match files with automatic reload
- Multiple triggers for one replacement
- Multiline replacements and
$|$cursor placement - Word boundaries and case propagation
- Date variables with formatting and offsets
- Persistent, modifier-free Unicode injection on supported Wayland compositors
- Warnings for immediate-mode triggers made unreachable by shorter prefixes
- Immediate Backspace undo for simple expansions
- Regex-based application exclusions
- Strict configuration validation and runtime diagnostics
- A small CLI for managing generated shortcuts
Platform support
SnipExpand currently supports Linux/Wayland and is tested on Hyprland. Basic text
expansion may work on other compositors, but they are not yet in the supported
test matrix. Active-application detection uses hyprctl on Hyprland and falls
back to wlrctl where available.
Requirements:
- Linux with readable
/dev/input/event*devices - A Wayland session
libxkbcommonand Wayland client librarieswtypeas a fallback for Unicode added after daemon startup- Membership in the system
inputgroup, unless equivalent device permissions are configured
Security model
SnipExpand reads physical keyboard events at the kernel input layer. Membership in
the input group therefore allows SnipExpand, and any other process running as your
user that opens those devices, to observe keyboard input across applications,
including sensitive text. Install only binaries you trust.
SnipExpand cannot determine whether a browser currently focuses a password field.
Use app_exclusions for password managers and other sensitive applications.
Exclusions prevent expansion; they do not prevent the process from receiving
the underlying keyboard events.
SnipExpand does not execute shell commands from match files and does not use or
modify the clipboard. Its persistent Wayland text keyboards map configured
replacement characters without shared modifier state. The optional wtype
fallback handles characters added after daemon startup.
Install
Prebuilt binary
Download the binary for your architecture from GitHub Releases:
# x86_64
An aarch64 binary and SHA-256 checksum files are attached to each release.
Cargo
Building from source requires libxkbcommon-dev and libwayland-dev on
Debian/Ubuntu, or libxkbcommon and wayland on Arch Linux.
Set up
Create a starter configuration:
Grant access to the physical input devices, then log out and back in so the new group membership reaches your graphical session:
After logging back in, install and start the systemd user service:
For logs and service state:
Configure
SnipExpand keeps settings separate from match files:
~/.config/snipexpand/
├── config.yml
└── match/
├── personal.yml
├── coding.yml
└── generated.yml
All .yml and .yaml files below match/ are loaded recursively and watched
for changes. snipexpand add and snipexpand remove touch only generated.yml, so
they never reformat handwritten match files.
Settings
# ~/.config/snipexpand/config.yml
trigger_mode: space
terminators:
injection_backend: auto
injection_delay_ms: 1
wayland_injection_delay_ms: 0
uinput_injection_delay_ms: 1
injection_settle_ms: 10
undo_enabled: true
app_exclusions:
- class: "^1Password$"
- class: "^org\\.keepassxc\\.KeePassXC$"
trigger_mode can be immediate or space. In space mode, any configured
space, enter, or tab terminator completes a match; SnipExpand removes the
terminator along with the trigger.
injection_backend can be auto, wayland, or uinput. auto prefers a
persistent Wayland virtual keyboard and falls back to uinput when the
compositor does not expose the required protocol. Changing the backend requires
a daemon restart; other settings continue to hot-reload.
injection_delay_ms controls the pause after each synthetic key release. The
shared starter value is 1 ms. The Wayland-specific 0 ms override is the tested
Omarchy default and keeps replacement transactions as short as possible. The
uinput fallback retains 1 ms pacing. Raise a backend-specific value if an
application or compositor drops or reorders characters.
wayland_injection_delay_ms and uinput_injection_delay_ms optionally override
the shared delay for one transport. Omit either value to inherit
injection_delay_ms. This is especially useful with auto when one transport
needs more pacing than the other on a particular compositor.
In immediate mode, a short trigger can make a longer trigger unreachable. For
example, ;eur expands before ;euro can be completed. snipexpand check and
the daemon log identify these conflicts with both source files.
injection_settle_ms is a one-time pause before SnipExpand deletes the trigger.
It gives the focused application time to receive the physical keystrokes and
does not pace the replacement itself.
Each application exclusion accepts title, class, and/or exec regular
expressions. Fields within one entry must all match; separate entries are
alternatives. Focus an application and run snipexpand detect to discover its
properties.
Matches
# ~/.config/snipexpand/match/personal.yml
global_vars:
- name: today
type: date
params:
format: "%Y-%m-%d"
matches:
- triggers:
replace: "user@example.com"
- trigger: ";sig"
word: true
replace: |
Best regards,
Silouan
- trigger: ";function"
replace: |
fn example() {
$|$
}
- trigger: ";today"
replace: "{{today}}"
- trigger: ";hello"
propagate_case: true
uppercase_style: capitalize_words
replace: "good morning"
The first $|$ marker is removed and the cursor is placed there after
expansion. word, left_word, and right_word follow Espanso's core boundary
model. With case propagation enabled, ;hello, ;Hello, and ;HELLO produce
good morning, Good Morning, and GOOD MORNING respectively.
Press Backspace immediately after a plain, single-line expansion to restore its trigger. Multiline and cursor-positioned expansions deliberately do not arm undo because their cursor state is ambiguous.
CLI
snipexpand Run the daemon in the foreground
snipexpand init Create starter files without overwriting anything
snipexpand add TRIGGER TEXT Add or overwrite a generated expansion
snipexpand remove TRIGGER Remove a generated expansion
snipexpand list List loaded triggers, values, and source files
snipexpand check Validate configuration and report loaded counts
snipexpand detect Show the focused application's properties
snipexpand reload Ask the running daemon to reload immediately
snipexpand status Query daemon health and active configuration
snipexpand status --json Emit health information for scripts and widgets
snipexpand doctor Diagnose the session, permissions, and runtime
snipexpand install Install and enable the systemd user service
Use \n in the second argument to add for a multiline replacement:
Add and remove operations automatically notify a running daemon. Watched files
also reload automatically after manual edits, so a restart is unnecessary.
reload is available when an immediate, explicit refresh is useful.
Current scope
SnipExpand intentionally rejects unknown configuration fields instead of silently changing their meaning. The first release does not support regex triggers, scripts or shell variables, forms, rich text, images, clipboard injection, per-application match sets, imports, or an online package registry.
See the compatibility matrix, the legacy TOML migration guide, and the Espanso-informed roadmap for details.
Development
The end-to-end helper in examples/e2e_type.rs creates a temporary virtual
keyboard and requires access to /dev/uinput. It is intended for interactive
testing in a disposable text field; it is not run by the normal unit-test suite.
License
MIT. See LICENSE.