Implements the hot-reload pipeline per docs/lua-api.md §7: the daemon
watches the user's owlry.lua and, on save, re-evaluates in a fresh
LuaContext. On success the new state hot-swaps atomically; on failure
the previous state is preserved and the user is told what broke via
BOTH the daemon log AND a desktop notification — no need to tail the
journal to discover the config is dead.
Cargo.toml:
- New optional dep `notify = "6"` gated by the `lua` feature alongside
mlua and glob. inotify backend on Linux; no platform-specific feature
flags needed.
lua/watcher.rs (new):
- ConfigWatcher::spawn(lua_path, config, pm, lua_ctx) wires a
notify::RecommendedWatcher on the parent directory (atomic-rename
saves from vim/JetBrains/etc rebind to a new inode, so watching the
file directly misses them) and spawns the event-pump thread.
- Debounce: 200ms after the first event, drain follow-ups, then reload
once. Coalesces burst saves into a single re-eval.
- reload() builds a fresh state in isolation; on Err keeps the old
Config/PM/LuaContext untouched.
- Swap order: config → ProviderManager → LuaContext. Old user-provider
Boxes inside the dropped PM retain the old Arc<Lua> until they
themselves drop, so the OLD Lua state survives the swap until the
OLD PM finishes dropping. New LuaProviders in new_pm hold the new
Arc<Lua>.
- report_reload_failure() formats the full error chain and fires a
notify_rust notification with summary/body/icon/urgency so the user
sees "config reload failed — /path/to/owlry.lua — <exact line>" the
moment a save breaks the file.
- Success path also emits a low-noise notification ("config reloaded").
lua/error.rs:
- LuaConfigError::Eval and ::Read no longer embed `{source}` in their
Display strings — error_chain() walks `.source()` already, and the
doubled rendering produced duplicated text in notifications and
logs. Comment explains why for the next reader.
server.rs:
- _lua_ctx changed from `Option<LuaContext>` to
`Arc<Mutex<Option<LuaContext>>>` so the watcher thread can atomically
replace it without taking the daemon down.
- New `_lua_watcher: Option<ConfigWatcher>` field, populated only when
loaded.lua_path is Some. Failure to spawn the watcher logs at warn
but doesn't fail Server::bind — the daemon stays usable even if
inotify is exhausted or perms are wrong.
- Mutex import feature-gated so --no-default-features stays
warning-free.
Tests: 5 new in lua::watcher::tests
- reload_swaps_in_new_config_state: change max_results in the file,
call reload() directly, verify Config got the new value.
- reload_failure_preserves_previous_state: write broken Lua, verify
Config is untouched.
- reload_replaces_user_providers_with_new_definitions: replace user
provider v1 with v2, verify a search for the v1 item returns nothing
and v2 is found (the websearch dynamic provider initially false-
positived on substring "v1 unique" via "Search: v1 unique" — locked
the test to the exact name "v1 unique item").
- event_touches_matches_only_watched_path: paths outside the watched
file are ignored.
- error_chain_renders_nested_causes: confirms chain walking works.
Live smoke (release build, isolated XDG, file edits via shell):
- v1 → save v2 → daemon log "hot-reload: applied"; :test prefix now
returns 2 v2 items.
- v2 → save broken Lua → daemon log "hot-reload: re-eval of /.../
owlry.lua failed, keeping previous config — Lua evaluation error in
/.../owlry.lua: syntax error: [string]:2: '}' expected (to close
'{' at line 1) near <eof>". Notification body shows the same
detail (file path + precise error). :test prefix still returns v2
items.
- Invalid provider id ("BadId With Spaces"): "runtime error: owlry.
provider: id 'BadId With Spaces' invalid — must be lowercase
alphanumeric with `-`/`_`". Old state preserved.
322/322 lib tests with --features full. Clippy silent across full,
--features lua, and --no-default-features.
Owlry
A lightweight, owl-themed application launcher for Wayland, built with GTK4 and Layer Shell. Single-binary, configurable, fast.
2.0 highlights. Owlry collapsed from 15 AUR packages and a dynamic plugin system into one binary. All providers (apps, commands, calculator, converter, power, bookmarks, clipboard, emoji, ssh, systemd, websearch, filesearch) are compiled in and gated by cargo features. The AUR build ships everything;
cargo installconsumers can pick a subset. Seedocs/RESTRUCTURE-V2.mdfor the full rewrite story.
Features
- Single binary — UI client, daemon, and providers in one
/usr/bin/owlry - Client/daemon architecture — Daemon (
owlry -d) keeps providers warm; UI appears instantly - Built-in providers — Apps, PATH commands, calculator, unit/currency converter, power actions
- Optional providers (compiled in via
--features fullon AUR) — Clipboard history, emoji, SSH hosts, systemd user units, web search, filesystem search - Fuzzy search with tags — Fast matching across names, descriptions, category tags
- Config profiles — Named mode presets for different workflows
- Filter prefixes — Scope searches with
:app,:cmd,:power,:uuctl,:tag:X, etc. - Frecency ranking — Frequently/recently used items rank higher
- Toggle behavior — Bind one key to open/close the launcher
- GTK4 theming — System theme by default, 10 built-in themes shipped
- Wayland native — Uses Layer Shell for proper overlay behavior
- dmenu compatible — Pipe-based selection, no daemon required
Installation
Arch Linux (AUR)
paru -S owlry # or yay -S owlry
Upgrading from 1.x: paru/pacman transparently swaps the old owlry-core, owlry-lua, owlry-rune, and every owlry-plugin-* and owlry-meta-* package for the new unified owlry. The .install hook prints a banner with the systemd unit rename instructions (owlryd.{service,socket} → owlry.{service,socket}).
Build from Source
System dependencies:
# Arch
sudo pacman -S gtk4 gtk4-layer-shell
# Ubuntu / Debian
sudo apt install libgtk-4-dev libgtk4-layer-shell-dev
# Fedora
sudo dnf install gtk4-devel gtk4-layer-shell-devel
Rust 1.90+:
git clone https://somegit.dev/Owlibou/owlry.git
cd owlry
cargo build --release --features full # full = all providers (matches the AUR build)
just install-local # installs binary + systemd units (sudo)
Cargo features (pick a subset if you don't need everything):
| Feature | Provider | Default? |
|---|---|---|
app |
XDG desktop applications | yes |
cmd |
Executables on $PATH |
yes |
calc |
Calculator | yes |
conv |
Unit & currency converter | yes |
power |
Shutdown/reboot/lock | yes |
dmenu |
Pipe-based selection | yes |
clipboard |
Clipboard history (cliphist) |
opt-in |
emoji |
Emoji picker (wl-clipboard) |
opt-in |
ssh |
SSH hosts from ~/.ssh/config |
opt-in |
systemd |
systemd user units (type_id: uuctl) |
opt-in |
websearch |
Web search (DuckDuckGo / configurable) | opt-in |
filesearch |
fd / mlocate shellout |
opt-in |
full |
All of the above | — |
cargo build --release --no-default-features --features "app,cmd,calc,conv,power,dmenu,systemd"
Getting Started
Starting the Daemon
Three options:
1. Systemd user service (recommended)
systemctl --user enable --now owlry.service
Reload config from disk without restarting:
systemctl --user reload owlry.service # or: kill -HUP $(pidof owlry)
2. Socket activation
systemctl --user enable owlry.socket
Daemon starts the first time the UI connects.
3. Compositor autostart
# Hyprland
exec-once = owlry -d
# Sway
exec owlry -d
Launching the UI
# Hyprland
bind = SUPER, Space, exec, owlry
# Sway
bindsym $mod+space exec owlry
Running owlry while a window is already open sends a toggle command — single keybind acts as open/close. If the daemon isn't running, the UI tries to start it via systemd.
Usage
owlry launch UI, auto mode
owlry -m auto launch UI, auto mode (explicit alias)
owlry -m <mode> launch UI in single-provider mode
owlry --profile <name> launch UI with a named profile
owlry -d run the daemon (alias: `owlry daemon`)
owlry dmenu [-p <prompt>] dmenu mode (reads stdin, prints selection)
owlry doctor diagnostics: config + socket + providers
owlry providers [<id>] list providers (or show details for one)
owlry config validate parse config, report errors
owlry config show print the resolved effective config as TOML
owlry migrate-config TOML → init.lua (stub in 2.0; lands in a later 2.x release)
Profiles
[profiles.dev]
modes = ["app", "cmd", "ssh"]
[profiles.media]
modes = ["emoji", "clipboard"]
owlry --profile dev
dmenu Mode
owlry dmenu (or the legacy owlry -m dmenu) reads stdin and prints the selection to stdout. It runs locally — no daemon required.
# Screenshot menu
printf '%s\n' \
"grimblast --notify copy screen" \
"grimblast --notify copy area" \
"grimblast --notify edit screen" \
| owlry dmenu -p "Screenshot" \
| sh
# Git branch checkout
git branch | owlry dmenu -p "checkout" | xargs git checkout
# Kill a process
ps -eo comm | sort -u | owlry dmenu -p "kill" | xargs pkill
# Open a project
find ~/projects -maxdepth 1 -type d | owlry dmenu | xargs code
# Package manager
pacman -Ssq | owlry dmenu -p "install" | xargs sudo pacman -S
Keyboard Shortcuts
| Key | Action |
|---|---|
Enter |
Launch selected item |
Escape |
Close launcher / exit submenu |
Up / Down |
Navigate results |
Tab |
Cycle filter tabs |
Shift+Tab |
Cycle filter tabs (reverse) |
Ctrl+1..9 |
Toggle tab by position |
Search Prefixes
| Prefix | Provider | Example |
|---|---|---|
:app |
Applications | :app firefox |
:cmd |
PATH commands | :cmd git |
:power (:sys, :system) |
Power & session actions | :power shutdown |
:calc |
Calculator | :calc sqrt(16) |
:conv |
Converter | :conv 5 ft to m |
:clip |
Clipboard | :clip password |
:emoji |
Emoji | :emoji heart |
:ssh |
SSH hosts | :ssh server |
:uuctl (:systemd) |
systemd user units | :uuctl dbus |
:web |
Web search | :web rust docs |
:file |
Files | :file config |
:tag:X |
Filter all results by tag | :tag:development |
Trigger Prefixes
| Trigger | Provider | Example |
|---|---|---|
= |
Calculator | = 5+3 |
> |
Converter | > 20 km to mi |
? |
Web search | ? rust programming |
/ |
File search | / .bashrc |
Configuration
Owlry follows the XDG Base Directory Specification:
| Path | Purpose |
|---|---|
~/.config/owlry/config.toml |
Main configuration |
~/.config/owlry/themes/*.css |
Custom themes |
~/.config/owlry/style.css |
CSS overrides |
~/.local/share/owlry/frecency.json |
Usage history |
$XDG_RUNTIME_DIR/owlry/owlry.sock |
IPC socket (overridable via $OWLRY_SOCKET) |
/usr/share/doc/owlry/config.example.toml |
Example configuration |
/usr/share/owlry/themes/ |
Bundled themes |
Quick Start
mkdir -p ~/.config/owlry
cp /usr/share/doc/owlry/config.example.toml ~/.config/owlry/config.toml
$EDITOR ~/.config/owlry/config.toml
owlry config validate
Example Configuration
[general]
show_icons = true
max_results = 100
tabs = ["app", "cmd", "uuctl"] # tabs shown in the header bar
# terminal_command = "kitty" # auto-detected; overrides $TERMINAL and xdg-terminal-exec
# use_uwsm = false # enable for systemd session integration (uwsm app --)
[appearance]
width = 850
height = 650
font_size = 14
border_radius = 12
# theme = "owl" # or: catppuccin-mocha, nord, dracula, ... (see Theming)
# Optional per-element color overrides. All fields are optional; unset inherits from the theme.
# [appearance.colors]
# background = "#1e1e2e"
# accent = "#cba6f7"
# badge_app = "#a6e3a1" # badge_* keys: app, cmd, clip, ssh, emoji, file,
# badge_web = "#89dceb" # power (alias: badge_sys), uuctl, web, calc, bm, dmenu
[providers]
applications = true # .desktop files
commands = true # PATH executables
calculator = true # `=` or :calc
converter = true # `>` or :conv
power = true # `:power` shutdown/reboot/lock (alias: system)
systemd = true # `:uuctl` user units (alias: uuctl)
clipboard = true # via cliphist
emoji = true # picker via wl-clipboard
ssh = true # ~/.ssh/config hosts
websearch = true # `?` or :web
filesearch = true # `/` or :file
frecency = true # boost frequently used items
frecency_weight = 0.3 # 0.0 disabled .. 1.0 strong
# Web search engine: google, duckduckgo, bing, startpage, searxng, brave, ecosia
# Or a custom URL with a {query} placeholder: "https://example.com/search?q={query}"
search_engine = "duckduckgo"
# Profiles — named mode sets
[profiles.dev]
modes = ["app", "cmd", "ssh"]
[profiles.minimal]
modes = ["app"]
See /usr/share/doc/owlry/config.example.toml for every option with documentation.
owlry config show prints the resolved effective config (defaults merged with your file). owlry config validate parses it and reports errors.
Theming
Built-in Themes
| Theme | Description |
|---|---|
owl |
Dark theme with amber accents |
catppuccin-mocha |
Soothing pastel |
nord |
Arctic blue palette |
rose-pine |
Natural pine vibes |
dracula |
Dark vampire theme |
gruvbox-dark |
Retro groove |
tokyo-night |
Tokyo city lights |
solarized-dark |
Precision colors |
one-dark |
Atom's One Dark |
apex-neon |
Neon cyberpunk |
[appearance]
theme = "catppuccin-mocha"
Custom Theme
Create ~/.config/owlry/themes/mytheme.css:
:root {
--owlry-bg: #1e1e2e;
--owlry-bg-secondary: #313244;
--owlry-border: #45475a;
--owlry-text: #cdd6f4;
--owlry-text-secondary: #a6adc8;
--owlry-accent: #f38ba8;
--owlry-accent-bright: #f5c2e7;
}
CSS Variables
| Variable | Description |
|---|---|
--owlry-bg |
Main background |
--owlry-bg-secondary |
Secondary surfaces |
--owlry-border |
Border color |
--owlry-text |
Primary text |
--owlry-text-secondary |
Muted text |
--owlry-accent |
Accent color |
--owlry-accent-bright |
Bright accent |
--owlry-shadow |
Window shadow (default: none) |
Architecture
owlry (single binary)
├── default invocation GTK4 UI client (connects to daemon over socket)
├── owlry -d / owlry daemon IPC daemon (loads providers, listens on the socket)
├── owlry dmenu stdin → selection (no daemon)
└── owlry doctor / providers / config diagnostics & config tools
Daemon:
├── Built-in providers applications, commands, power, calculator, converter
├── Optional providers bookmarks, clipboard, emoji, ssh, systemd, websearch, filesearch
│ (compiled in per cargo feature)
├── Frecency tracking auto-saved every 5 min; flushed on SIGTERM/SIGINT
└── IPC server $XDG_RUNTIME_DIR/owlry/owlry.sock (newline-delimited JSON)
The daemon keeps providers and items warm in memory; the UI launches instantly because there's no work to do at startup. The UI client is a thin GTK4 layer that streams queries and renders results.
Set OWLRY_SOCKET=/path/to/sock to override the socket location — useful for running a development daemon alongside a production one.
Roadmap
See ROADMAP.md for feature ideas and docs/RESTRUCTURE-V2.md for the v2 rewrite story.
Headline upcoming work:
- Lua-driven configuration (2.1 / 3.0) —
~/.config/owlry/init.luareplaces TOML. User-defined providers viaowlry.provider {}in the same file (Hyprland-style configs-as-code).owlry migrate-configlands at the same time. - Widget providers return — weather, MPRIS media controls, pomodoro timer. Deferred from 2.0 while the UI positioning is reworked.
- Bookmarks return — Firefox + Chromium. Deferred from 2.0 to avoid a hard rusqlite/
libsqlite3-sysdep in the chroot build path; returns with a pure-Rust reader (likely via Firefox's JSON backup files).
License
GNU General Public License v3.0 — see LICENSE.
Acknowledgments
- GTK4 — UI toolkit
- gtk4-layer-shell — Wayland Layer Shell
- fuzzy-matcher — Fuzzy search
- expr-solver-lib — Calculator backend