vikingowl aa1a38bbcf feat(lua): owlry config validate covers the Lua surface (Phase 3.9)
Implements the validator per docs/lua-api.md §8. Replaces the
TOML-only "load and pass/fail" check from 2.0 with categorised
findings: errors (exit 1), warnings (exit 2), clean (exit 0).

lua/config.rs:
- LuaConfig grows `duplicate_user_provider_ids: Vec<String>`. The dedup
  in apply_provider would otherwise erase duplicates by the time the
  snapshot is read.

lua/api.rs:
- apply_provider records the id on the duplicate list when replacing
  an existing entry. The original warn! log is unchanged.

lua/validate.rs (new):
- ValidationReport { errors, warnings } with exit_code() honouring the
  §8 codes (0/1/2).
- validate(cfg) is a pure function over a LuaConfig snapshot. Surfaces:
  * Unknown keys in owlry.set
  * Unknown colour keys in owlry.theme
  * Unknown ids in owlry.providers (built-in alias table consulted;
    user-provider ids are recognised)
  * Providers compiled out — Cargo features checked via cfg! macros;
    always-on ids (app, cmd, calc, conv, power, dmenu) skip the check
  * Unknown / not-in-providers ids in owlry.tabs (user providers
    auto-join the enabled set, so they pass when listed in tabs)
  * Duplicate provider ids
  * Unknown ids inside owlry.profiles values (per-profile label)
- canonical_provider_id() mirrors the apply_providers_list alias table
  in lua/config.rs so an id accepted by the merger is never flagged.

commands.rs:
- run_config_validate now branches: when owlry.lua exists, build the
  LoadedConfig, run validate::validate on the snapshot, print the
  report with `<n> warning(s):` / `<n> error(s):` headers. Eval
  failures print the chained mlua error (file path + line/col).
- TOML fallback path unchanged when no owlry.lua is present.
- Whole feature path is `#[cfg(feature = "lua")]`-gated.

Tests: 13 new in lua::validate::tests
- clean config emits nothing, exit 0
- unknown set key warns
- unknown theme key warns
- unknown provider id warns (and `app` passes through unflagged)
- user_provider ids in owlry.providers list are recognised
- tabs with unknown id warn
- tabs with id not in owlry.providers warn ("dropped from tab bar")
- tabs with user provider id passes (auto-joins enabled set)
- duplicate provider id warns
- profile with unknown id warns and names the profile
- pre-v2 aliases (sys, uuctl) validate cleanly
- always-compiled-in providers never trigger compile-out warning
- multi-warning report keeps every finding

Live smoke (release build, isolated XDG) for each category:
- clean → "config: OK (Lua, ...)", exit 0
- unknown set key → "owlry.set: unknown key `mystery_key` — ignored
  (forward-compat for 2.2+ keys)", exit 2
- unknown provider id → "owlry.providers: unknown id `fictional_provider`
  — not a built-in and not registered by any owlry.provider call", exit 2
- tabs not in providers → "owlry.tabs: `cmd` is not in owlry.providers
  — it will be dropped from the tab bar", exit 2
- duplicate provider id → "owlry.provider: id `hs` was registered more
  than once — the last definition wins (earlier registrations are
  dropped)", exit 2
- profile with unknown id → "owlry.profiles.dev: unknown id `phantom`",
  exit 2
- syntax error → "config: ERROR — Lua evaluation error in /.../
  owlry.lua: syntax error: [string]:5: '}' expected (to close '{' at
  line 2) near <eof>", exit 1
- 5-warning combo → all five surfaced with stable ordering, exit 2

352/352 lib tests with --features full. Clippy silent across full,
--features lua, and --no-default-features.
2026-05-13 05:12:52 +02:00
2026-05-13 03:55:16 +02:00
2026-05-13 02:57:46 +02:00

Owlry

AUR Rust License GTK4 Wayland

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 install consumers can pick a subset. See docs/RESTRUCTURE-V2.md for 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 full on 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.lua replaces TOML. User-defined providers via owlry.provider {} in the same file (Hyprland-style configs-as-code). owlry migrate-config lands 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-sys dep 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

S
Description
No description provided
Readme GPL-3.0 1.3 MiB
Languages
Rust 86.9%
CSS 7.3%
Shell 2.7%
Lua 1.2%
Just 1.1%
Other 0.8%