Commit Graph

117 Commits

Author SHA1 Message Date
vikingowl eba590bad8 feat(lua): owlry.lua hot reload with desktop-notification errors (Phase 3.7)
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.
2026-05-13 04:59:30 +02:00
vikingowl 82133edade feat(lua): owlry.util.* host helpers (Phase 3.6)
Implements the six host helpers per docs/lua-api.md §5.1:

  owlry.util.shell(cmd)        -> string  (stdout, trim_end)
  owlry.util.shell_lines(cmd)  -> table   (split, no trailing empty)
  owlry.util.read_file(path)   -> string|nil  (nil on any I/O error)
  owlry.util.glob(pattern)     -> table   (tilde-expanded; PatternError → Lua err)
  owlry.util.env(name, def?)   -> string|nil
  owlry.util.hostname()        -> string

Cargo.toml:
- New optional dep `glob = "0.3"` gated by the `lua` feature alongside
  mlua. Pure-Rust, no system Lua/C deps.

lua/util.rs:
- `build(lua) -> mlua::Result<Table>` constructs the owlry.util sub-table
  with every helper registered. Stateless; no Arc<Mutex<LuaConfig>> needed.
- shell uses `sh -c`; stderr is logged at warn on non-zero exit but
  stdout is still returned (don't crash user configs on missing commands).
- shell_lines drops the trailing empty element so a terminating newline
  doesn't produce a phantom "" item.
- read_file extends "nil if missing" (per spec) to "nil on any I/O
  failure" — saves users wrapping every read in pcall.
- glob expands a leading `~/` via `dirs::home_dir()`. `~user/`, embedded
  `~`, and env-var refs are intentionally left as-is (predictable surface).
- hostname uses libc::gethostname directly (libc is already a hard dep).

lua/api.rs:
- install() now attaches `owlry.util = util::build(lua)?` to the parent
  owlry table.

Tests: 17 new
- util.rs (15): shell trim, interior newlines preserved, shell_lines
  split/empty cases, read_file present/missing, glob match/empty/error,
  env present/missing/default, hostname non-empty, tilde expansion
  positive/negative.
- runtime.rs (2): owlry.util surface exists with all 6 functions after
  api::install; util helpers feed into a user provider's items()
  end-to-end (hostname-as-launch-item, via LuaProvider::refresh).

Smoke (release build, isolated XDG, user provider :host_info using
hostname/shell/env): daemon loads 4 items per the user provider —
"host: cn-arch", "kernel: 7.0.5-zen1-1.1-zen", "home: /home/...",
"user: cnachtigall". 317/317 lib tests with --features full. Clippy
silent across all three feature configurations.
2026-05-13 04:47:59 +02:00
vikingowl 5b8bc8bac9 feat(lua): scaffold mlua-based config module (Phase 3.1)
Adds the lua cargo feature and a stubbed crates/owlry/src/lua/ module
in preparation for Phase 3 (Lua config layer per docs/lua-api.md).

- mlua 0.11 (lua54, vendored, send, serialize) as an optional dep so
  AUR clean-chroot builds don't depend on system Lua.
- New `lua` feature gates the module; included in the `full` feature
  set so the AUR build ships it. Disabled by default so minimal
  `cargo install` consumers don't pay for the C compile.
- Stub submodules: runtime (LuaContext), api (owlry.* surface), error
  (LuaConfigError), util (owlry.util.*). Every function is a no-op
  placeholder for the sub-phases that will wire them.
- pub mod lua gated behind #[cfg(feature = "lua")] in lib.rs.

cargo check passes with --no-default-features, --features lua, and
--features full. 221/221 lib tests still green. Zero clippy warnings
from the new module.
2026-05-13 04:03:29 +02:00
vikingowl 0242e48707 chore(owlry): bump version to 2.0.1
Patch release covering:
- fix(providers): dynamic providers (calc, conv, websearch, filesearch)
  now appear in owlry doctor and owlry providers <id> output
- feat(aur/install-hook): proactive detection of stale owlryd references
  in user-level systemd units and compositor configs

No behavioural changes beyond diagnostic visibility and upgrade-time
warnings. No config or API breakage; safe drop-in upgrade.

Cargo.toml + PKGBUILD bumped together; .SRCINFO regenerated; b2sum
will be refreshed by 'just aur-update' once the tag is pushed.
2026-05-13 03:52:23 +02:00
vikingowl 2cac6556f3 chore(owlry): bump version to 2.0.0 2026-05-13 03:24:08 +02:00
vikingowl a7af0e5d46 feat(v2): defer bookmarks provider (D22) — drop rusqlite dep
The bookmarks provider opened Firefox's places.sqlite directly via
rusqlite, which pulled libsqlite3-sys with its bundled feature to
compile sqlite3.c statically. In clean Arch chroots the bundled feature
kept slipping out of the resolved feature graph and the chroot build
failed at link time with every sqlite3_* symbol undefined. Inline
features = ["bundled"] on the optional dep didn't help; neither did
the explicit "rusqlite/bundled" feature wiring (commit 7569b2d).

Rather than fight Cargo's resolver further, defer the bookmarks
provider alongside the widgets per the same pattern (D20 -> D22).
Returns in a later 2.x release with a pure-Rust reader:
- Chromium's Bookmarks file is already JSON.
- Firefox exposes JSON backups under ~/.mozilla/firefox/<profile>/
  bookmarkbackups/<dated>.jsonlz4 — needs lz4 + JSON parse, no SQLite.

Removed:
- crates/owlry/src/providers/bookmarks.rs
- bookmarks module + registration in providers/mod.rs
- bookmarks feature + rusqlite dep in Cargo.toml
- ProvidersConfig.bookmarks field (existing user configs that still
  have 'bookmarks = true' silently ignore it — serde default behavior)
- :bm prefix from README's search-prefix table
- bookmarks acknowledgement in README + ROADMAP roadmap section

Cargo.lock loses rusqlite, libsqlite3-sys, fallible-iterator,
fallible-streaming-iterator, foldhash, hashlink, rsqlite-vfs,
sqlite-wasm-rs (-80 lines).

PKGBUILD: owlry-plugin-bookmarks stays in replaces= (any user who has
it installed still needs a clean upgrade), but moved from the 'folded'
comment block to the 'deferred (D20+)' block.

Docs:
- README: bookmarks removed from optional-providers list, feature
  table, search-prefix table, config example. Added to upcoming
  roadmap section alongside widgets.
- ROADMAP: 'Bookmarks return' subsection added next to 'Widget
  providers return'.
- CLAUDE.md: provider tree updated.
- docs/RESTRUCTURE-V2.md: new decision D22 with chroot-build
  rationale; task #6 plugin count 8 -> 7 -> 6.

245 tests pass with --features full (was 252; the 7 bookmarks unit
tests went with the module). Build verified locally; chroot build
should now succeed since libsqlite3-sys is no longer in the graph
at all.
2026-05-13 03:13:16 +02:00
vikingowl cb2ea5973b feat(providers): convert remaining 6 plugins from C-ABI to native impls
Closes the v2 plugin conversion. Six providers ported from the
owlry-plugins sibling repo into the single owlry crate as feature-
gated modules. Each follows the same pattern established by systemd:
drop extern "C"/PluginItem/ProviderHandle/owlry_plugin! scaffolding,
implement Provider or DynamicProvider directly on a regular struct.

Static providers (Provider trait, populate via refresh):
- providers/bookmarks.rs — Firefox + Chromium bookmarks via rusqlite,
  favicon cache preserved. dep: rusqlite (bundled), feature: bookmarks
- providers/clipboard.rs — cliphist history. feature: clipboard
- providers/emoji.rs — bundled emoji list with keyword tags.
  feature: emoji
- providers/ssh.rs — ~/.ssh/config host extraction. feature: ssh

Dynamic providers (DynamicProvider trait, generate per query):
- providers/filesearch.rs — fd / mlocate shellout with extract_search_term
  for ':file' and '/' triggers. feature: filesearch
- providers/websearch.rs — URL builder with DuckDuckGo/Google/custom
  engines. TODO: plumb engine through constructor once Lua config lands
  (Phase 3). feature: websearch

Wiring:
- Cargo.toml: 7 per-provider features + 'full' meta-feature. rusqlite
  added as optional dep (only pulled in with feature 'bookmarks').
- config/mod.rs: ProvidersConfig gains 6 new bool fields (defaults true)
- providers/mod.rs: gated module declarations + new_with_config takes a
  config snapshot and registers each provider behind its feature flag

Verification across feature axes:
- --no-default-features: 178 tests pass (feature-gated modules excluded)
- default (systemd only): 186 tests pass
- --features full: 233 tests pass (+55 from the 6 new conversions)

Tasks #6 and #7 complete.
2026-05-13 02:17:42 +02:00
vikingowl 0a4a09037e refactor(v2): collapse owlry-core into owlry single crate
Workspace shrinks from 2 members to 1. The daemon, IPC layer,
providers, config, frecency store, GTK4 UI, and CLI now live in a
single `crates/owlry` crate exposing both a library (so integration
tests can reach daemon types) and a binary.

Structural changes:
- crates/owlry-core/ deleted; all source moved into crates/owlry/src/
  via git mv to preserve history
- crates/owlry/src/lib.rs added with module declarations
- crates/owlry/src/main.rs rewritten as thin entry that uses owlry::*
- crates/owlry/src/providers/mod.rs absorbs owlry-core's providers/mod.rs
  and pulls dmenu into the same module tree
- All owlry_core:: refs in src/ rewritten to crate::
- All owlry_core:: refs in tests/ rewritten to owlry::
- systemd/owlryd.service: ExecStart=/usr/bin/owlry -d (single binary)
- justfile: drop owlry-core/owlry-lua/owlry-rune build steps; daemon
  runs via 'cargo run -p owlry -- -d'
- owlry version: 1.0.10 -> 2.0.0-dev

Tests: 178 still pass (156 lib + 14 ipc + 8 server). No test changes
needed — moved files retained their inline test modules.

Task #2 complete.
2026-05-13 02:05:26 +02:00
vikingowl ae4a90352e refactor(v2): demolish C-ABI plugin system
Delete the entire dynamic-loading infrastructure that produced issue #5
and the per-API-bump plugin breakage cycle:

- crates/owlry-plugin-api/  (ABI-stable interface, gone)
- crates/owlry-lua/         (Lua runtime cdylib, gone — replaced by mlua in Phase 3)
- crates/owlry-rune/        (Rune runtime cdylib, gone per D3)
- owlry-core/src/plugins/   (loader, manifest, registry, watcher — all gone)
- owlry-core/src/providers/native_provider.rs
- owlry-core/src/providers/lua_provider.rs
- owlry-core/src/providers/config_editor.rs   (per D11, 1127 LOC)
- owlry/src/plugin_commands.rs                (per CLI restructure, 1296 LOC)

Provider trait gains submenu_actions(), execute_action(), prefix(),
icon(), position(), priority() as default methods so future built-in
providers can declare their UI metadata directly instead of relying on
the hardcoded match table.

ProviderManager simplified: drops native_providers,
static_native_providers, dynamic_providers, widget_providers,
runtimes, runtime_type_ids, plugin_registry fields and the
reload_runtimes / find_native_provider / get_widget_item /
widget_type_ids methods. ProviderPosition enum carries the Normal/
Widget distinction on the trait instead.

IPC Request::PluginList and Response::PluginList removed; Submenu
and PluginAction stay (route to Provider trait methods now).

owlry -d / --daemon flag added as the future daemon entry point; the
old owlryd binary is still produced from owlry-core for Phase 1
compatibility but will fold into the single binary in task #2.

Workspace shrinks from 5 members to 2. Tests: 142 passed.
LOC: -13,796 / +273 (net -13,523).

Tasks #3, #4, #5 complete.
2026-05-13 01:54:33 +02:00
vikingowl 61411cd094 chore(owlry): bump version to 1.0.10 2026-04-09 21:16:48 +02:00
vikingowl 4e310223cf chore(owlry-rune): bump version to 1.1.6 2026-04-09 21:16:45 +02:00
vikingowl 6446a253e8 chore(owlry-lua): bump version to 1.1.5 2026-04-09 21:16:42 +02:00
vikingowl 72dcd74e65 chore(owlry-core): bump version to 1.3.6 2026-04-09 21:16:39 +02:00
vikingowl 34d156fb7d chore(owlry-rune): bump version to 1.1.5 2026-04-09 18:31:29 +02:00
vikingowl afe248c66f chore(owlry-lua): bump version to 1.1.4 2026-04-09 18:31:05 +02:00
vikingowl 2c3c8b8e51 chore(owlry-core): bump version to 1.3.5 2026-04-09 18:31:00 +02:00
vikingowl 3d328d8fa0 chore(owlry): bump version to 1.0.9 2026-04-09 18:30:59 +02:00
vikingowl e11fac3619 chore: replace meval with expr-solver-lib, drop reqwest from runtimes, fix AUR deps
- owlry-core: swap meval → expr-solver-lib for calculator and Lua math API;
  add ln() alias for meval compatibility
- owlry-lua: remove reqwest and meval (network features belong in plugins);
  add vendored feature flag so distro builds can link against system lua54
- owlry-rune: remove reqwest (same reason)
- aur/owlry-rune: fix depends (gcc-libs only; owlry-core → optdepends)
- aur/owlry-lua: fix depends (gcc-libs + lua54; owlry-core → optdepends)
- aur/owlry: add chmod -R a+rX for example plugins
- justfile: log aur-local-test output to build-logs/
- .gitignore: exclude build-logs/ and test-build-output files
- README: minor improvements (SIGHUP reload hint, plugin_config example)
2026-04-09 16:51:12 +02:00
vikingowl 7275fcab35 fix: implement all 24 FIX_PLAN issues across 6 phases
Phase 1 — Critical Safety:
- #11: bounded IPC reads via read_bounded_line (server + client)
- #13: sound Send+Sync via Arc<Mutex<RuntimeHandle>>; remove unsafe impl Sync
- #10: ItemSource enum (Core/NativePlugin/ScriptPlugin) on LaunchItem;
  script plugin allowlist guard in launch_item()

Phase 2 — Config System Overhaul:
- #6: remove dead enabled_plugins field
- #1: replace #[serde(flatten)] with explicit Config::plugin_config
- #4: Server.config Arc<RwLock<Config>>; ConfigProvider shares same Arc
- #2/#3: atomic config save (temp+rename); TOCTOU fixed — write lock held
  across mutation and save() in config_editor
- #23: fs2 lock_exclusive() on .lock sidecar file in Config::save()
- #16: SIGHUP handler reloads config; ExecReload in systemd service

Phase 3 — Plugin Architecture:
- #7: HostAPI v4 with get_config_string/int/bool; PLUGIN_CONFIG OnceLock
  in native_loader, set_shared_config() called from Server::bind()
- #5: PluginEntry + Request::PluginList + Response::PluginList; plugin_registry
  in ProviderManager tracks active and suppressed native plugins;
  cmd_list_installed shows both script and native plugins
- #9: suppressed native plugin log level info! → warn!
- #8: ProviderType doc glossary; plugins/mod.rs terminology table

Phase 4 — Data Integrity:
- #12: all into_inner() in server.rs + providers/mod.rs → explicit Response::Error;
  watcher exits on poisoned lock
- #14: FrecencyStore::prune() (180-day age + 5000-entry cap) called on load
- #17: empty command guard in launch_item(); warn in lua_provider
- #24: 5-min periodic frecency save thread; SIGTERM/SIGINT saves frecency
  before exit (replaces ctrlc handler)

Phase 5 — UI & UX:
- #19: provider_meta.rs ProviderMeta + meta_for(); three match blocks collapsed
- #18: desktop file dedup via seen_basenames HashSet in ApplicationProvider
- #20: search_filtered gains tag_filter param; non-frecency path now filters
- #15: widget refresh 5s→10s; skip when user is typing

Phase 6 — Hardening:
- #22: catch_unwind removed from reload_runtimes(); direct drop()
- #21: AtomicUsize + RAII ConnectionGuard; MAX_CONNECTIONS = 16

Deps: add fs2 = "0.4"; remove ctrlc and toml_edit from owlry-core
2026-04-08 16:43:52 +02:00
vikingowl 9163b1ea6c chore(owlry-rune): bump version to 1.1.4 2026-04-06 02:38:47 +02:00
vikingowl de74cac67d chore(owlry-lua): bump version to 1.1.3 2026-04-06 02:22:08 +02:00
vikingowl 2f396306fd chore(owlry-core): bump version to 1.3.4 2026-04-06 02:22:07 +02:00
vikingowl 7863de9971 chore(owlry): bump version to 1.0.7 2026-04-06 01:57:42 +02:00
vikingowl dacc194d02 chore(owlry-core): bump version to 1.3.3 2026-04-06 01:57:39 +02:00
vikingowl e3c4988e01 chore(owlry-rune): bump version to 1.1.3 2026-04-05 18:18:05 +02:00
vikingowl 95a698225c chore(owlry-rune): bump version to 1.1.2 2026-04-05 18:05:54 +02:00
vikingowl 827bf383ea chore(owlry-lua): bump version to 1.1.2 2026-04-05 18:05:25 +02:00
vikingowl 32b4b144f4 chore(owlry-core): bump version to 1.3.2 2026-04-05 17:59:19 +02:00
vikingowl 5615002062 fix: switch reqwest TLS backend from rustls to native-tls
reqwest 0.13 defaults to rustls -> aws-lc-rs which requires cmake/nasm
in minimal build environments (AUR chroot). Switch all direct reqwest
users to native-tls (system OpenSSL) to fix clean chroot build failures
reported by users.

Affected crates: owlry-core, owlry-lua, owlry-rune
PKGBUILD: add openssl to depends for all three runtime packages
Also add scripts/aur-local-test for clean chroot testing workflow
2026-04-05 17:58:36 +02:00
vikingowl c32b6c5456 chore(owlry-rune): bump version to 1.1.1 2026-03-28 13:43:06 +01:00
vikingowl 2a5f184230 chore(owlry-lua): bump version to 1.1.1 2026-03-28 13:43:04 +01:00
vikingowl 1adec7bf47 chore(owlry-core): bump version to 1.3.1 2026-03-28 13:30:23 +01:00
vikingowl 80312a28f7 chore(owlry-core): bump version to 1.3.0 2026-03-28 13:17:11 +01:00
vikingowl f189f4b1ce chore(owlry): bump version to 1.0.6 2026-03-28 12:40:20 +01:00
vikingowl 422ea6d816 chore(owlry-core): bump version to 1.2.1 2026-03-28 12:40:18 +01:00
vikingowl c8d8298274 chore(owlry-core): bump version to 1.2.0 2026-03-28 12:26:15 +01:00
vikingowl c3c35611fd chore(owlry-core): bump version to 1.1.3 2026-03-28 11:35:23 +01:00
vikingowl 5ecd0a6412 chore(owlry): bump version to 1.0.5 2026-03-28 11:35:22 +01:00
vikingowl b87447156e chore(owlry-core): bump version to 1.1.2 2026-03-28 11:18:27 +01:00
vikingowl 12d554959a chore(owlry): bump version to 1.0.4 2026-03-28 11:18:26 +01:00
vikingowl 94556f1fe0 chore(owlry): bump version to 1.0.3 2026-03-28 10:48:55 +01:00
vikingowl 10a685c62f chore(owlry): bump version to 1.0.2 2026-03-28 09:16:40 +01:00
vikingowl 34db33c75f chore(owlry-core): bump version to 1.1.1 2026-03-28 09:16:38 +01:00
vikingowl 8f7501038d perf(ui): move search IPC off the GTK main thread
Search queries in daemon mode now run on a background thread via
DaemonHandle::query_async(). Results are posted back to the main
thread via glib::spawn_future_local + futures_channel::oneshot.
The GTK event loop is never blocked by IPC, eliminating perceived
input lag.

Local mode (dmenu) continues to use synchronous search since it
has no IPC overhead.
2026-03-28 09:12:20 +01:00
vikingowl 5c0e63f94c chore(owlry-rune): bump version to 1.1.0 2026-03-26 18:51:20 +01:00
vikingowl 5441011d6b chore(owlry-lua): bump version to 1.1.0 2026-03-26 18:51:20 +01:00
vikingowl 317572634f chore(owlry): bump version to 1.0.1 2026-03-26 18:51:12 +01:00
vikingowl 449dc010db chore(owlry-core): bump version to 1.1.0 2026-03-26 18:51:04 +01:00
vikingowl 7273cd3ba7 chore(owlry-plugin-api): bump version to 1.0.1 2026-03-26 18:50:58 +01:00
vikingowl f5d83f1372 chore: format, fix clippy warnings, bump all crates to 1.0.0 2026-03-26 13:37:55 +01:00