Wrap LoadedRuntime._library in ManuallyDrop so dlclose() is never called.
dlclose() unmaps the library code; thread-local destructors inside liblua.so
then SIGSEGV when they try to run against the unmapped addresses.
Also filter out non-.lua plugins in the Lua runtime's discover_plugins()
so liblua.so does not attempt to load Rune plugins.
- owlry-core/manifest: add entry_point alias for entry field, add ProviderSpec
struct for [[providers]] array, change default entry to main.lua
- owlry-lua/manifest: add ProviderDecl struct and providers: Vec<ProviderDecl>
for [[providers]] support
- owlry-lua/loader: fall back to manifest [[providers]] when script has no API
registrations; fall back to global refresh() for manifest-declared providers
- owlry-lua/api: expose call_global_refresh() that calls the top-level Lua
refresh() function directly
- owlry/plugin_commands: update create templates to emit new format:
entry_point instead of entry, [[providers]] instead of [provides],
main.rn/main.lua instead of init.rn/init.lua, Rune uses Item::new() builder
pattern, Lua uses standalone refresh() function
- cmd_validate: accept [[providers]] declarations as a valid provides source
- Config::save() now merges into existing file via toml_edit to preserve
user comments and unknown keys instead of overwriting
- Remove 16 dead ProvidersConfig plugin-toggle fields (uuctl, ssh, clipboard,
bookmarks, emoji, scripts, files, media, weather_*, pomodoro_*) that became
unreachable after accept_all=true was introduced
- Wire general.tabs to ProviderFilter::new() to drive UI tab display
- Fix set_single_mode() and toggle() not clearing accept_all, making tab
filtering a no-op in default launch mode
- Add restore_all_mode() for cycle-back-to-All path in tab cycling
- Reduce PROVIDER_TOGGLES to built-in providers only
Merge four separate prefix arrays (core full, plugin full, core
partial, plugin partial) into two arrays with a single loop each
that checks both full and partial match. Halves the data and
eliminates the duplicate iteration.
Replace while-contains-replace loop with a single-pass char
iterator. Eliminates O(n²) behavior and repeated allocations
on pathological desktop file Exec values.
record_launch no longer calls save() synchronously. The dirty flag
is set and the Drop impl flushes on shutdown. Removes a JSON
serialize + fs::write from the hot launch path.
Refactor search_with_frecency to score static provider items by
reference (&LaunchItem, i64) instead of cloning every match.
Use select_nth_unstable_by for O(n) partial sort, then clone
only the max_results survivors. Reduces clones from O(total_matches)
to O(max_results) — typically from hundreds to ~15.
Replace RwLock<Vec<LaunchItem>> with plain Vec. The inner RwLock
was unnecessary — refresh() takes &mut self (exclusive access
guaranteed by the outer Arc<RwLock<ProviderManager>>). The unsafe
block in items() dropped the RwLockReadGuard while returning a
slice backed by the raw pointer, creating a dangling reference.
:config and :conv were not in the prefix lists, so typing them
showed 'Plugin' mode but didn't route to the config/converter
providers. Also added :settings, :converter aliases.
Register ConfigProvider as built-in dynamic provider. Extend
execute_plugin_action to dispatch CONFIG:* commands via the
DynamicProvider::execute_action trait method.
Calculator and converter registered as built-in dynamic providers.
System registered as built-in static provider. All gated by config
toggles (calculator, converter, system — default true).
Foundation for built-in calculator, converter, and system providers.
DynamicProvider trait for per-keystroke providers. ProviderManager
iterates builtin_dynamic alongside native dynamic plugins in search.
Calculator and converter results now get a 10k grouping bonus so all
their results stay together above websearch/filesearch. Previously
websearch (priority 9000) would interleave with converter results
(9000, 8999, 8998...) since they had the same base priority.
Highlighting:
- Dynamic plugin results (calculator, converter, websearch, filesearch)
get a subtle accent left-border + background tint when auto-detected
- Exact name matches (case-insensitive) are highlighted the same way
- Exact match on apps gets a higher score boost (50k) than other
providers (30k), so apps rank first when names match exactly
Shadow:
- Removed hardcoded box-shadow from all theme CSS files
- Added --owlry-shadow variable in base.css (defaults to none)
- Themes can opt into shadow via --owlry-shadow if desired
CSS class: .owlry-result-highlight on ResultRow
get_score() called Utc::now() inside calculate_frecency() for every
item in the search loop. Added get_score_at() that accepts a pre-sampled
timestamp. Eliminates hundreds of unnecessary clock_gettime syscalls
per keystroke.
detect_terminal() was spawning up to 17 'which' subprocesses sequentially
on every startup. Replace with std::env::split_paths + is_file() check.
Eliminates 200-500ms of fork+exec overhead on cold cache.
Watch ~/.config/owlry/plugins/ for changes using notify-debouncer-mini
(500ms debounce) and trigger a full runtime reload on file modifications.
Respects OWLRY_SKIP_RUNTIMES=1 to skip watcher in tests.
- Shrink Lua RuntimeInfo from 5 fields to 2 (name, version), matching
core and Rune. The mismatch caused SIGSEGV across the ABI boundary.
- Add owlry_version parameter to vtable init in all three crates
(core, Lua, Rune) so runtimes receive the version at init time
instead of hardcoding it.
- Remove unused Lua constants (RUNTIME_ID, RUNTIME_NAME, etc.) and
LUA_RUNTIME_API_VERSION.
- Update plugin_commands.rs call sites to pass CARGO_PKG_VERSION.
Replace Mutex with RwLock for ProviderManager and FrecencyStore in the
IPC server. Most request types (Query, Providers, Submenu, PluginAction)
only need read access and can now proceed concurrently. Only Launch
(frecency write) and Refresh (provider write) acquire exclusive locks.
Also adds a warn!() log for malformed JSON requests before sending the
error response, improving observability for debugging client issues.
Provider trait now requires Send + Sync to satisfy RwLock's Sync bound
on the inner type. RuntimeProvider and LuaProvider gain the
corresponding unsafe impl Sync.
Replace hardcoded list of 13 plugin IDs in ProviderFilter::all() with
an accept_all flag. When set, is_active()/is_enabled() return true for
any ProviderType, so dynamically loaded plugins are accepted without
maintaining a static list. Prefix-based filtering still narrows scope
as before, and from_mode_strings() still filters to explicit modes only.
Add ProfileConfig struct and profiles map to Config, allowing named
mode presets in config.toml (e.g. [profiles.dev] modes = ["app","cmd"]).
Remove the --providers/-p CLI flag and repurpose -p as the short form
for --prompt. Add --profile flag that loads modes from a named profile.
Mode resolution priority: --mode > --profile > config defaults.
The UI now uses a SearchBackend abstraction that wraps either:
- CoreClient (daemon mode): connects to owlry-core via IPC for search,
frecency tracking, submenu queries, and plugin actions
- Local ProviderManager (dmenu mode): unchanged direct provider access
Key changes:
- New backend.rs with SearchBackend enum abstracting IPC vs local
- app.rs creates CoreClient in normal mode, falls back to local if
daemon unavailable
- main_window.rs uses SearchBackend instead of ProviderManager+FrecencyStore
- Command execution stays in the UI (daemon only tracks frecency)
- dmenu mode path is completely unchanged (no daemon involvement)
- Added terminal field to IPC ResultItem for proper terminal launch
- Added PluginAction IPC request for plugin command execution
Add [[bin]] target and main.rs that starts the IPC server with
env_logger, socket path from XDG_RUNTIME_DIR, and graceful shutdown
via ctrlc signal handler. Also add socket_path() to paths module.
Adds Server struct that listens on a Unix domain socket, accepts
client connections (thread-per-client), reads newline-delimited JSON
requests, dispatches to ProviderManager/FrecencyStore/Config, and
sends JSON responses back. Includes stale socket cleanup and Drop
impl for socket removal.
Add methods needed by the IPC server (Task 9) to create filters from
mode strings, query provider metadata, and refresh individual providers.
ProviderFilter:
- from_mode_strings(): create filter from ["app", "cmd", "calc"] etc.
- all(): create permissive filter accepting all provider types
- mode_string_to_provider_type(): public helper for string-to-type mapping
ProviderManager:
- ProviderDescriptor struct for IPC provider metadata responses
- available_providers() -> Vec<ProviderDescriptor> (replaces ProviderType version)
- refresh_provider(id): refresh a single provider by type_id
- new_with_config(config): self-contained init for daemon use
NativeProvider:
- icon(): get provider's default icon name
- position_str(): get position as "normal"/"widget" string