Compare commits

...

18 Commits

Author SHA1 Message Date
f8d011447e chore(owlry): bump version to 1.0.8 2026-04-06 02:39:19 +02:00
9163b1ea6c chore(owlry-rune): bump version to 1.1.4 2026-04-06 02:38:47 +02:00
6586f5d6c2 fix(plugins): close remaining gaps in new plugin format support
- Fix cmd_runtimes() help text: init.lua/init.rn → main.lua/main.rn
- Add .lua extension validation to owlry-lua manifest (mirrors Rune)
- Replace eprintln! with log::warn!/log::debug! in owlry-lua loader
- Add log = "0.4" dependency to owlry-lua
- Add tests: [[providers]] deserialization in owlry-core manifest,
  manifest provider fallback in owlry-lua and owlry-rune loaders,
  non-runtime plugin filtering in both runtimes
2026-04-06 02:38:42 +02:00
a6e94deb3c fix(runtime): prevent dlclose() to avoid SIGSEGV on runtime teardown
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.
2026-04-06 02:26:12 +02:00
de74cac67d chore(owlry-lua): bump version to 1.1.3 2026-04-06 02:22:08 +02:00
2f396306fd chore(owlry-core): bump version to 1.3.4 2026-04-06 02:22:07 +02:00
133d5264ea feat(plugins): update plugin format to new entry_point + [[providers]] style
- 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
2026-04-06 02:22:03 +02:00
a16c3a0523 fix(just): skip meta packages without PKGBUILD in aur-publish-all 2026-04-06 02:11:32 +02:00
33b4f410e5 fix(scripts): fix duplicate inject deduplication in aur-local-test
dedup_inject_files() was called before its definition in the script's
execution order. Moved the call to the Main section where all functions
are already defined.
2026-04-06 02:04:24 +02:00
a7683f16bf docs: update config example and README to reflect provider field removal 2026-04-06 02:02:06 +02:00
178f81082a fix(owlry-core,owlry): preserve config on save, fix tab filtering, clean provider fields
- 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
2026-04-06 01:59:48 +02:00
7863de9971 chore(owlry): bump version to 1.0.7 2026-04-06 01:57:42 +02:00
dacc194d02 chore(owlry-core): bump version to 1.3.3 2026-04-06 01:57:39 +02:00
5871609c73 chore(aur): update owlry-rune to 1.1.3 2026-04-05 18:18:11 +02:00
e3c4988e01 chore(owlry-rune): bump version to 1.1.3 2026-04-05 18:18:05 +02:00
46b5d8518f chore(aur): update owlry-rune to 1.1.2 2026-04-05 18:05:59 +02:00
95a698225c chore(owlry-rune): bump version to 1.1.2 2026-04-05 18:05:54 +02:00
709e1b04cb chore(aur): update owlry-lua to 1.1.2 2026-04-05 18:05:31 +02:00
26 changed files with 661 additions and 904 deletions

492
Cargo.lock generated
View File

@@ -50,12 +50,6 @@ dependencies = [
"core_extensions", "core_extensions",
] ]
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.8.12" version = "0.8.12"
@@ -178,18 +172,6 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "async-compression"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1"
dependencies = [
"compression-codecs",
"compression-core",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "async-executor" name = "async-executor"
version = "1.14.0" version = "1.14.0"
@@ -503,23 +485,6 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "compression-codecs"
version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7"
dependencies = [
"compression-core",
"flate2",
"memchr",
]
[[package]]
name = "compression-core"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.5.0" version = "2.5.0"
@@ -569,15 +534,6 @@ version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533d38ecd2709b7608fb8e18e4504deb99e9a72879e6aa66373a76d8dc4259ea" checksum = "533d38ecd2709b7608fb8e18e4504deb99e9a72879e6aa66373a76d8dc4259ea"
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "critical-section" name = "critical-section"
version = "1.2.0" version = "1.2.0"
@@ -800,16 +756,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@@ -1053,24 +999,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi", "wasi",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi 5.3.0",
"wasip2",
"wasm-bindgen",
] ]
[[package]] [[package]]
@@ -1081,7 +1011,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi 6.0.0", "r-efi",
"wasip2", "wasip2",
"wasip3", "wasip3",
] ]
@@ -1532,23 +1462,6 @@ dependencies = [
"want", "want",
] ]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
"webpki-roots",
]
[[package]] [[package]]
name = "hyper-tls" name = "hyper-tls"
version = "0.6.0" version = "0.6.0"
@@ -1952,12 +1865,6 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
] ]
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]] [[package]]
name = "lua-src" name = "lua-src"
version = "550.0.0" version = "550.0.0"
@@ -2032,16 +1939,6 @@ dependencies = [
"nom", "nom",
] ]
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.1.1" version = "1.1.1"
@@ -2119,15 +2016,6 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
"getrandom 0.2.17",
]
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.18" version = "0.2.18"
@@ -2460,7 +2348,7 @@ dependencies = [
[[package]] [[package]]
name = "owlry" name = "owlry"
version = "1.0.6" version = "1.0.8"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
@@ -2481,7 +2369,7 @@ dependencies = [
[[package]] [[package]]
name = "owlry-core" name = "owlry-core"
version = "1.3.2" version = "1.3.4"
dependencies = [ dependencies = [
"chrono", "chrono",
"ctrlc", "ctrlc",
@@ -2497,26 +2385,28 @@ dependencies = [
"notify-debouncer-mini", "notify-debouncer-mini",
"notify-rust", "notify-rust",
"owlry-plugin-api", "owlry-plugin-api",
"reqwest 0.13.2", "reqwest",
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
"tempfile", "tempfile",
"thiserror 2.0.18", "thiserror 2.0.18",
"toml 0.8.23", "toml 0.8.23",
"toml_edit 0.22.27",
] ]
[[package]] [[package]]
name = "owlry-lua" name = "owlry-lua"
version = "1.1.2" version = "1.1.3"
dependencies = [ dependencies = [
"abi_stable", "abi_stable",
"chrono", "chrono",
"dirs", "dirs",
"log",
"meval", "meval",
"mlua", "mlua",
"owlry-plugin-api", "owlry-plugin-api",
"reqwest 0.13.2", "reqwest",
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
@@ -2534,16 +2424,15 @@ dependencies = [
[[package]] [[package]]
name = "owlry-rune" name = "owlry-rune"
version = "1.1.1" version = "1.1.4"
dependencies = [ dependencies = [
"chrono", "chrono",
"dirs", "dirs",
"env_logger", "env_logger",
"log", "log",
"owlry-plugin-api", "owlry-plugin-api",
"reqwest 0.13.2", "reqwest",
"rune", "rune",
"rune-modules",
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
@@ -2715,15 +2604,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]] [[package]]
name = "prettyplease" name = "prettyplease"
version = "0.2.37" version = "0.2.37"
@@ -2761,61 +2641,6 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"socket2",
"thiserror 2.0.18",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
"thiserror 2.0.18",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.45" version = "1.0.45"
@@ -2825,47 +2650,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "r-efi" name = "r-efi"
version = "6.0.0" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.18" version = "0.5.18"
@@ -2933,44 +2723,6 @@ dependencies = [
"tstr", "tstr",
] ]
[[package]]
name = "reqwest"
version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64",
"bytes",
"futures-core",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-util",
"js-sys",
"log",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-rustls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
]
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.13.2" version = "0.13.2"
@@ -3008,20 +2760,6 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "rune" name = "rune"
version = "0.14.1" version = "0.14.1"
@@ -3095,21 +2833,6 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "rune-modules"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f4ef5dc3546042989f4abc70d6b9f707a539d5cbb5cb2fb167f8fbe891e1b64"
dependencies = [
"base64",
"nanorand",
"reqwest 0.12.28",
"rune",
"serde_json",
"tokio",
"toml 0.8.23",
]
[[package]] [[package]]
name = "rune-tracing" name = "rune-tracing"
version = "0.14.1" version = "0.14.1"
@@ -3158,41 +2881,15 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "rustls"
version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.14.0" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [ dependencies = [
"web-time",
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-webpki"
version = "0.103.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
@@ -3346,18 +3043,6 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@@ -3383,12 +3068,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simd-adler32"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
[[package]] [[package]]
name = "simdutf8" name = "simdutf8"
version = "0.1.5" version = "0.1.5"
@@ -3429,12 +3108,6 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.109" version = "1.0.109"
@@ -3620,21 +3293,6 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.50.0" version = "1.50.0"
@@ -3645,7 +3303,6 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@@ -3660,29 +3317,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.23" version = "0.8.23"
@@ -3805,18 +3439,13 @@ version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [ dependencies = [
"async-compression",
"bitflags 2.11.0", "bitflags 2.11.0",
"bytes", "bytes",
"futures-core",
"futures-util", "futures-util",
"http", "http",
"http-body", "http-body",
"http-body-util",
"iri-string", "iri-string",
"pin-project-lite", "pin-project-lite",
"tokio",
"tokio-util",
"tower", "tower",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
@@ -3975,12 +3604,6 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]] [[package]]
name = "url" name = "url"
version = "2.5.8" version = "2.5.8"
@@ -4186,25 +3809,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki-roots"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "which" name = "which"
version = "8.0.2" version = "8.0.2"
@@ -4411,15 +4015,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.2" version = "0.61.2"
@@ -4453,30 +4048,13 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6", "windows_i686_gnullvm",
"windows_i686_msvc 0.52.6", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6", "windows_x86_64_msvc 0.52.6",
] ]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
]
[[package]] [[package]]
name = "windows-threading" name = "windows-threading"
version = "0.1.0" version = "0.1.0"
@@ -4507,12 +4085,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.48.5"
@@ -4525,12 +4097,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.48.5"
@@ -4543,24 +4109,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.48.5"
@@ -4573,12 +4127,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.48.5"
@@ -4591,12 +4139,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.5" version = "0.48.5"
@@ -4609,12 +4151,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.48.5"
@@ -4627,12 +4163,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.15" version = "0.7.15"

View File

@@ -254,7 +254,7 @@ Type `:config` to browse and modify settings without editing files:
| Command | What it does | | Command | What it does |
|---------|-------------| |---------|-------------|
| `:config` | Show all setting categories | | `:config` | Show all setting categories |
| `:config providers` | Toggle providers on/off | | `:config providers` | Toggle built-in providers on/off (calculator, converter, system, frecency) |
| `:config theme` | Select color theme | | `:config theme` | Select color theme |
| `:config engine` | Select web search engine | | `:config engine` | Select web search engine |
| `:config frecency` | Toggle frecency, set weight | | `:config frecency` | Toggle frecency, set weight |
@@ -265,6 +265,8 @@ Type `:config` to browse and modify settings without editing files:
Changes are saved to `config.toml` immediately. Some settings (theme, frecency) take effect on the next search. Others (font size, dimensions) require a restart. Changes are saved to `config.toml` immediately. Some settings (theme, frecency) take effect on the next search. Others (font size, dimensions) require a restart.
> **Note:** `:config providers` only covers built-in providers. To enable or disable plugins, use `owlry plugin enable/disable <name>` or set `disabled_plugins` in `[plugins]`.
### Search Prefixes ### Search Prefixes
| Prefix | Provider | Example | | Prefix | Provider | Example |
@@ -332,33 +334,54 @@ Or configure from within the launcher: type `:config` to interactively change se
```toml ```toml
[general] [general]
show_icons = true show_icons = true
max_results = 10 max_results = 100
tabs = ["app", "cmd", "uuctl"] tabs = ["app", "cmd", "uuctl"] # Provider tabs shown in the header bar
# terminal_command = "kitty" # Auto-detected # terminal_command = "kitty" # Auto-detected; overrides $TERMINAL and xdg-terminal-exec
# use_uwsm = false # Enable for systemd session integration # use_uwsm = false # Enable for systemd session integration (uwsm app --)
[appearance] [appearance]
width = 850 width = 850
height = 650 height = 650
font_size = 14 font_size = 14
border_radius = 12 border_radius = 12
# theme = "owl" # Or: catppuccin-mocha, nord, dracula, etc. # theme = "owl" # Or: catppuccin-mocha, nord, dracula, etc. (see Theming section)
[plugins] # Optional per-element color overrides — all fields are optional, unset inherits from theme
disabled = [] # Plugin IDs to disable, e.g., ["emoji", "pomodoro"] # [appearance.colors]
# background = "#1e1e2e"
# background_secondary = "#313244"
# border = "#45475a"
# text = "#cdd6f4"
# accent = "#cba6f7"
# badge_app = "#a6e3a1" # All badge_* keys: app, cmd, clip, ssh, emoji, file,
# badge_web = "#89dceb" # script, sys, uuctl, web, calc, bm, dmenu,
# badge_media = "#f38ba8" # media, weather, pomo
[providers] [providers]
applications = true # .desktop files applications = true # .desktop files
commands = true # PATH executables commands = true # PATH executables
calculator = true # Built-in math expressions calculator = true # Built-in math expressions (= or calc trigger)
converter = true # Built-in unit/currency conversion converter = true # Built-in unit/currency converter (> trigger)
system = true # Built-in shutdown/reboot/lock actions system = true # Built-in shutdown/reboot/lock actions
frecency = true # Boost frequently used items frecency = true # Boost frequently used items
frecency_weight = 0.3 # 0.0-1.0 frecency_weight = 0.3 # 0.0 = disabled, 1.0 = strong boost
# Web search engine: google, duckduckgo, bing, startpage, brave, ecosia # 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" search_engine = "duckduckgo"
[plugins]
# disabled_plugins = ["emoji", "pomodoro"] # Plugin IDs to disable
# enabled_plugins = [] # Empty = all discovered plugins are loaded
# registry_url = "https://..." # Custom plugin registry URL
# Sandboxing for Lua/Rune user plugins (~/.config/owlry/plugins/)
# [plugins.sandbox]
# allow_filesystem = false # Allow access outside plugin directory
# allow_network = false # Allow outbound network requests
# allow_commands = false # Allow shell command execution
# memory_limit = 67108864 # Lua memory cap in bytes (default: 64 MB)
# Profiles: named sets of modes # Profiles: named sets of modes
[profiles.dev] [profiles.dev]
modes = ["app", "cmd", "ssh"] modes = ["app", "cmd", "ssh"]
@@ -382,10 +405,17 @@ Add plugin IDs to the disabled list in your config:
```toml ```toml
[plugins] [plugins]
disabled = ["emoji", "pomodoro"] disabled_plugins = ["emoji", "pomodoro"]
``` ```
Or toggle providers interactively: type `:config providers` in the launcher. Or use the CLI:
```bash
owlry plugin disable emoji
owlry plugin enable emoji
```
> **Note:** `:config providers` in the launcher only manages built-in providers (calculator, converter, system). Use `disabled_plugins` or `owlry plugin disable` for plugins.
### Plugin Management CLI ### Plugin Management CLI

View File

@@ -1,13 +1,14 @@
pkgbase = owlry-lua pkgbase = owlry-lua
pkgdesc = Lua scripting runtime for Owlry — enables user-created Lua plugins pkgdesc = Lua scripting runtime for Owlry — enables user-created Lua plugins
pkgver = 1.1.1 pkgver = 1.1.2
pkgrel = 1 pkgrel = 1
url = https://somegit.dev/Owlibou/owlry url = https://somegit.dev/Owlibou/owlry
arch = x86_64 arch = x86_64
license = GPL-3.0-or-later license = GPL-3.0-or-later
makedepends = cargo makedepends = cargo
depends = owlry-core depends = owlry-core
source = owlry-lua-1.1.1.tar.gz::https://somegit.dev/Owlibou/owlry/archive/owlry-lua-v1.1.1.tar.gz depends = openssl
b2sums = a0e1fa032db8dda8e6bc24457f3c04948129d3f14c1d3e61b8e080340b24f560d43294beb133ad4b1c6eb7942d401108ea91c367b074eaeeefa284e9b2a9dbc8 source = owlry-lua-1.1.2.tar.gz::https://somegit.dev/Owlibou/owlry/archive/owlry-lua-v1.1.2.tar.gz
b2sums = 42e6221e6e07c629ece1493e7f5feb1b2cb2e77632d1d7779dfbe544bd89a17d77d1839d63e50d71d4f0e0322ca8a1cc39b872101039019bdf08d9bcaeda7603
pkgname = owlry-lua pkgname = owlry-lua

View File

@@ -1,6 +1,6 @@
# Maintainer: vikingowl <christian@nachtigall.dev> # Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-lua pkgname=owlry-lua
pkgver=1.1.1 pkgver=1.1.2
pkgrel=1 pkgrel=1
pkgdesc="Lua scripting runtime for Owlry — enables user-created Lua plugins" pkgdesc="Lua scripting runtime for Owlry — enables user-created Lua plugins"
arch=('x86_64') arch=('x86_64')
@@ -9,7 +9,7 @@ license=('GPL-3.0-or-later')
depends=('owlry-core' 'openssl') depends=('owlry-core' 'openssl')
makedepends=('cargo') makedepends=('cargo')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry/archive/owlry-lua-v$pkgver.tar.gz") source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry/archive/owlry-lua-v$pkgver.tar.gz")
b2sums=('a0e1fa032db8dda8e6bc24457f3c04948129d3f14c1d3e61b8e080340b24f560d43294beb133ad4b1c6eb7942d401108ea91c367b074eaeeefa284e9b2a9dbc8') b2sums=('42e6221e6e07c629ece1493e7f5feb1b2cb2e77632d1d7779dfbe544bd89a17d77d1839d63e50d71d4f0e0322ca8a1cc39b872101039019bdf08d9bcaeda7603')
_cratename=owlry-lua _cratename=owlry-lua

View File

@@ -1,13 +1,14 @@
pkgbase = owlry-rune pkgbase = owlry-rune
pkgdesc = Rune scripting runtime for Owlry — enables user-created Rune plugins pkgdesc = Rune scripting runtime for Owlry — enables user-created Rune plugins
pkgver = 1.1.1 pkgver = 1.1.3
pkgrel = 1 pkgrel = 1
url = https://somegit.dev/Owlibou/owlry url = https://somegit.dev/Owlibou/owlry
arch = x86_64 arch = x86_64
license = GPL-3.0-or-later license = GPL-3.0-or-later
makedepends = cargo makedepends = cargo
depends = owlry-core depends = owlry-core
source = owlry-rune-1.1.1.tar.gz::https://somegit.dev/Owlibou/owlry/archive/owlry-rune-v1.1.1.tar.gz depends = openssl
b2sums = a0e1fa032db8dda8e6bc24457f3c04948129d3f14c1d3e61b8e080340b24f560d43294beb133ad4b1c6eb7942d401108ea91c367b074eaeeefa284e9b2a9dbc8 source = owlry-rune-1.1.3.tar.gz::https://somegit.dev/Owlibou/owlry/archive/owlry-rune-v1.1.3.tar.gz
b2sums = 3dd1db5783b0e0c814f8e6064fd5d8cb736da5bd4759d648f48ea69a476451f64d27ecd0894378f3e41e7cc85557b7d9dcb0adf80114c32e561d4688aae6bb53
pkgname = owlry-rune pkgname = owlry-rune

View File

@@ -1,6 +1,6 @@
# Maintainer: vikingowl <christian@nachtigall.dev> # Maintainer: vikingowl <christian@nachtigall.dev>
pkgname=owlry-rune pkgname=owlry-rune
pkgver=1.1.1 pkgver=1.1.3
pkgrel=1 pkgrel=1
pkgdesc="Rune scripting runtime for Owlry — enables user-created Rune plugins" pkgdesc="Rune scripting runtime for Owlry — enables user-created Rune plugins"
arch=('x86_64') arch=('x86_64')
@@ -9,7 +9,7 @@ license=('GPL-3.0-or-later')
depends=('owlry-core' 'openssl') depends=('owlry-core' 'openssl')
makedepends=('cargo') makedepends=('cargo')
source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry/archive/owlry-rune-v$pkgver.tar.gz") source=("$pkgname-$pkgver.tar.gz::https://somegit.dev/Owlibou/owlry/archive/owlry-rune-v$pkgver.tar.gz")
b2sums=('a0e1fa032db8dda8e6bc24457f3c04948129d3f14c1d3e61b8e080340b24f560d43294beb133ad4b1c6eb7942d401108ea91c367b074eaeeefa284e9b2a9dbc8') b2sums=('3dd1db5783b0e0c814f8e6064fd5d8cb736da5bd4759d648f48ea69a476451f64d27ecd0894378f3e41e7cc85557b7d9dcb0adf80114c32e561d4688aae6bb53')
_cratename=owlry-rune _cratename=owlry-rune

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "owlry-core" name = "owlry-core"
version = "1.3.2" version = "1.3.4"
edition.workspace = true edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
license.workspace = true license.workspace = true
@@ -30,6 +30,7 @@ semver = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
toml = "0.8" toml = "0.8"
toml_edit = "0.22"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
dirs = "5" dirs = "5"

View File

@@ -2,6 +2,7 @@ use log::{debug, info, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use toml_edit::{DocumentMut, Item};
use crate::paths; use crate::paths;
@@ -157,82 +158,26 @@ pub struct ProvidersConfig {
pub applications: bool, pub applications: bool,
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub commands: bool, pub commands: bool,
#[serde(default = "default_true")] /// Enable built-in calculator provider (= or calc trigger)
pub uuctl: bool,
/// Enable calculator provider (= expression or calc expression)
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub calculator: bool, pub calculator: bool,
/// Enable converter provider (> expression or auto-detect) /// Enable built-in unit/currency converter (> trigger)
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub converter: bool, pub converter: bool,
/// Enable built-in system actions (shutdown, reboot, lock, etc.)
#[serde(default = "default_true")]
pub system: bool,
/// Enable frecency-based result ranking /// Enable frecency-based result ranking
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub frecency: bool, pub frecency: bool,
/// Weight for frecency boost (0.0 = disabled, 1.0 = strong boost) /// Weight for frecency boost (0.0 = disabled, 1.0 = strong boost)
#[serde(default = "default_frecency_weight")] #[serde(default = "default_frecency_weight")]
pub frecency_weight: f64, pub frecency_weight: f64,
/// Enable web search provider (? query or web query) /// Search engine for web search (used by owlry-plugin-websearch)
#[serde(default = "default_true")]
pub websearch: bool,
/// Search engine for web search
/// Options: google, duckduckgo, bing, startpage, searxng, brave, ecosia /// Options: google, duckduckgo, bing, startpage, searxng, brave, ecosia
/// Or custom URL with {query} placeholder /// Or a custom URL with a {query} placeholder
#[serde(default = "default_search_engine")] #[serde(default = "default_search_engine")]
pub search_engine: String, pub search_engine: String,
/// Enable system commands (shutdown, reboot, etc.)
#[serde(default = "default_true")]
pub system: bool,
/// Enable SSH connections from ~/.ssh/config
#[serde(default = "default_true")]
pub ssh: bool,
/// Enable clipboard history (requires cliphist)
#[serde(default = "default_true")]
pub clipboard: bool,
/// Enable browser bookmarks
#[serde(default = "default_true")]
pub bookmarks: bool,
/// Enable emoji picker
#[serde(default = "default_true")]
pub emoji: bool,
/// Enable custom scripts from ~/.config/owlry/scripts/
#[serde(default = "default_true")]
pub scripts: bool,
/// Enable file search (requires fd or locate)
#[serde(default = "default_true")]
pub files: bool,
// ─── Widget Providers ───────────────────────────────────────────────
/// Enable MPRIS media player widget
#[serde(default = "default_true")]
pub media: bool,
/// Enable weather widget
#[serde(default)]
pub weather: bool,
/// Weather provider: wttr.in (default), openweathermap, open-meteo
#[serde(default = "default_weather_provider")]
pub weather_provider: String,
/// API key for weather services that require it (e.g., OpenWeatherMap)
#[serde(default)]
pub weather_api_key: Option<String>,
/// Location for weather (city name or coordinates)
#[serde(default)]
pub weather_location: Option<String>,
/// Enable pomodoro timer widget
#[serde(default)]
pub pomodoro: bool,
/// Pomodoro work duration in minutes
#[serde(default = "default_pomodoro_work")]
pub pomodoro_work_mins: u32,
/// Pomodoro break duration in minutes
#[serde(default = "default_pomodoro_break")]
pub pomodoro_break_mins: u32,
} }
impl Default for ProvidersConfig { impl Default for ProvidersConfig {
@@ -240,28 +185,12 @@ impl Default for ProvidersConfig {
Self { Self {
applications: true, applications: true,
commands: true, commands: true,
uuctl: true,
calculator: true, calculator: true,
converter: true, converter: true,
system: true,
frecency: true, frecency: true,
frecency_weight: 0.3, frecency_weight: 0.3,
websearch: true,
search_engine: "duckduckgo".to_string(), search_engine: "duckduckgo".to_string(),
system: true,
ssh: true,
clipboard: true,
bookmarks: true,
emoji: true,
scripts: true,
files: true,
media: true,
weather: false,
weather_provider: "wttr.in".to_string(),
weather_api_key: None,
weather_location: Some("Berlin".to_string()),
pomodoro: false,
pomodoro_work_mins: 25,
pomodoro_break_mins: 5,
} }
} }
} }
@@ -399,18 +328,6 @@ fn default_frecency_weight() -> f64 {
0.3 0.3
} }
fn default_weather_provider() -> String {
"wttr.in".to_string()
}
fn default_pomodoro_work() -> u32 {
25
}
fn default_pomodoro_break() -> u32 {
5
}
/// Detect the best available terminal emulator /// Detect the best available terminal emulator
/// Fallback chain: /// Fallback chain:
/// 1. $TERMINAL env var (user's explicit preference) /// 1. $TERMINAL env var (user's explicit preference)
@@ -539,6 +456,38 @@ fn command_exists(cmd: &str) -> bool {
// Note: Config derives Default via #[derive(Default)] - all sub-structs have impl Default // Note: Config derives Default via #[derive(Default)] - all sub-structs have impl Default
/// Merge `new` into `existing`, updating values while preserving comments and unknown keys.
///
/// Tables are recursed into so that section-level comments survive. For leaf values
/// (scalars, arrays) the item is replaced but the surrounding table structure — and
/// any keys in `existing` that are absent from `new` — are left untouched.
fn merge_toml_doc(existing: &mut DocumentMut, new: &DocumentMut) {
for (key, new_item) in new.iter() {
match existing.get_mut(key) {
Some(existing_item) => merge_item(existing_item, new_item),
None => {
existing.insert(key, new_item.clone());
}
}
}
}
fn merge_item(existing: &mut Item, new: &Item) {
match (existing.as_table_mut(), new.as_table()) {
(Some(e), Some(n)) => {
for (key, new_child) in n.iter() {
match e.get_mut(key) {
Some(existing_child) => merge_item(existing_child, new_child),
None => {
e.insert(key, new_child.clone());
}
}
}
}
_ => *existing = new.clone(),
}
}
impl Config { impl Config {
pub fn config_path() -> Option<PathBuf> { pub fn config_path() -> Option<PathBuf> {
paths::config_file() paths::config_file()
@@ -585,13 +534,34 @@ impl Config {
Ok(config) Ok(config)
} }
#[allow(dead_code)]
pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> { pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
let path = Self::config_path().ok_or("Could not determine config path")?; let path = Self::config_path().ok_or("Could not determine config path")?;
paths::ensure_parent_dir(&path)?; paths::ensure_parent_dir(&path)?;
let content = toml::to_string_pretty(self)?; let new_content = toml::to_string_pretty(self)?;
// If a config file already exists, merge into it to preserve comments and
// any keys the user has added that are not part of the Config struct.
let content = if path.exists() {
let existing = std::fs::read_to_string(&path)?;
match existing.parse::<toml_edit::DocumentMut>() {
Ok(mut doc) => {
if let Ok(new_doc) = new_content.parse::<toml_edit::DocumentMut>() {
merge_toml_doc(&mut doc, &new_doc);
}
doc.to_string()
}
Err(_) => {
// Existing file is malformed — fall back to full rewrite.
warn!("Existing config is malformed; overwriting with current settings");
new_content
}
}
} else {
new_content
};
std::fs::write(&path, content)?; std::fs::write(&path, content)?;
info!("Saved config to {:?}", path); info!("Saved config to {:?}", path);
Ok(()) Ok(())

View File

@@ -26,11 +26,17 @@ pub struct ParsedQuery {
} }
impl ProviderFilter { impl ProviderFilter {
/// Create filter from CLI args and config /// Create filter from CLI args and config.
///
/// `tabs` is `general.tabs` from config and drives which provider tabs are
/// shown in the UI when no explicit CLI mode is active. It has no effect on
/// query routing: when no CLI mode is set, `accept_all=true` causes
/// `is_active()` to return `true` for every provider regardless.
pub fn new( pub fn new(
cli_mode: Option<ProviderType>, cli_mode: Option<ProviderType>,
cli_providers: Option<Vec<ProviderType>>, cli_providers: Option<Vec<ProviderType>>,
config_providers: &ProvidersConfig, config_providers: &ProvidersConfig,
tabs: &[String],
) -> Self { ) -> Self {
let accept_all = cli_mode.is_none() && cli_providers.is_none(); let accept_all = cli_mode.is_none() && cli_providers.is_none();
@@ -41,50 +47,23 @@ impl ProviderFilter {
// --providers overrides config // --providers overrides config
providers.into_iter().collect() providers.into_iter().collect()
} else { } else {
// Use config file settings, default to apps only // No CLI restriction: accept_all=true, so is_active() returns true for
let mut set = HashSet::new(); // everything. Build the enabled set only for UI tab display, driven by
// Core providers // general.tabs. Falls back to Application + Command if tabs is empty.
if config_providers.applications { let mut set: HashSet<ProviderType> = tabs
set.insert(ProviderType::Application); .iter()
} .map(|s| Self::mode_string_to_provider_type(s))
if config_providers.commands { .collect();
set.insert(ProviderType::Command);
}
// Plugin providers - use Plugin(type_id) for all
if config_providers.uuctl {
set.insert(ProviderType::Plugin("uuctl".to_string()));
}
if config_providers.system {
set.insert(ProviderType::Plugin("system".to_string()));
}
if config_providers.ssh {
set.insert(ProviderType::Plugin("ssh".to_string()));
}
if config_providers.clipboard {
set.insert(ProviderType::Plugin("clipboard".to_string()));
}
if config_providers.bookmarks {
set.insert(ProviderType::Plugin("bookmarks".to_string()));
}
if config_providers.emoji {
set.insert(ProviderType::Plugin("emoji".to_string()));
}
if config_providers.scripts {
set.insert(ProviderType::Plugin("scripts".to_string()));
}
// Dynamic providers
if config_providers.files {
set.insert(ProviderType::Plugin("filesearch".to_string()));
}
if config_providers.calculator {
set.insert(ProviderType::Plugin("calc".to_string()));
}
if config_providers.websearch {
set.insert(ProviderType::Plugin("websearch".to_string()));
}
// Default to apps if nothing enabled
if set.is_empty() { if set.is_empty() {
set.insert(ProviderType::Application); if config_providers.applications {
set.insert(ProviderType::Application);
}
if config_providers.commands {
set.insert(ProviderType::Command);
}
if set.is_empty() {
set.insert(ProviderType::Application);
}
} }
set set
}; };
@@ -114,7 +93,8 @@ impl ProviderFilter {
} }
} }
/// Toggle a provider on/off /// Toggle a provider on/off. Clears accept_all so the enabled set is
/// actually used for routing — use restore_all_mode() to go back to All.
pub fn toggle(&mut self, provider: ProviderType) { pub fn toggle(&mut self, provider: ProviderType) {
if self.enabled.contains(&provider) { if self.enabled.contains(&provider) {
self.enabled.remove(&provider); self.enabled.remove(&provider);
@@ -137,6 +117,7 @@ impl ProviderFilter {
provider_debug, self.enabled provider_debug, self.enabled
); );
} }
self.accept_all = false;
} }
/// Enable a specific provider /// Enable a specific provider
@@ -156,6 +137,12 @@ impl ProviderFilter {
pub fn set_single_mode(&mut self, provider: ProviderType) { pub fn set_single_mode(&mut self, provider: ProviderType) {
self.enabled.clear(); self.enabled.clear();
self.enabled.insert(provider); self.enabled.insert(provider);
self.accept_all = false;
}
/// Restore accept-all mode (used when cycling back to the "All" tab).
pub fn restore_all_mode(&mut self) {
self.accept_all = true;
} }
/// Set prefix mode (from :app, :cmd, etc.) /// Set prefix mode (from :app, :cmd, etc.)

View File

@@ -10,6 +10,10 @@ use super::error::{PluginError, PluginResult};
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginManifest { pub struct PluginManifest {
pub plugin: PluginInfo, pub plugin: PluginInfo,
/// Provider declarations from [[providers]] sections (new-style)
#[serde(default)]
pub providers: Vec<ProviderSpec>,
/// Legacy provides block (old-style)
#[serde(default)] #[serde(default)]
pub provides: PluginProvides, pub provides: PluginProvides,
#[serde(default)] #[serde(default)]
@@ -43,7 +47,7 @@ pub struct PluginInfo {
#[serde(default = "default_owlry_version")] #[serde(default = "default_owlry_version")]
pub owlry_version: String, pub owlry_version: String,
/// Entry point file (relative to plugin directory) /// Entry point file (relative to plugin directory)
#[serde(default = "default_entry")] #[serde(default = "default_entry", alias = "entry_point")]
pub entry: String, pub entry: String,
} }
@@ -52,7 +56,27 @@ fn default_owlry_version() -> String {
} }
fn default_entry() -> String { fn default_entry() -> String {
"init.lua".to_string() "main.lua".to_string()
}
/// A provider declared in a [[providers]] section
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderSpec {
pub id: String,
pub name: String,
#[serde(default)]
pub prefix: Option<String>,
#[serde(default)]
pub icon: Option<String>,
/// "static" (default) or "dynamic"
#[serde(default = "default_provider_type", rename = "type")]
pub provider_type: String,
#[serde(default)]
pub type_id: Option<String>,
}
fn default_provider_type() -> String {
"static".to_string()
} }
/// What the plugin provides /// What the plugin provides
@@ -278,7 +302,7 @@ version = "1.0.0"
assert_eq!(manifest.plugin.id, "test-plugin"); assert_eq!(manifest.plugin.id, "test-plugin");
assert_eq!(manifest.plugin.name, "Test Plugin"); assert_eq!(manifest.plugin.name, "Test Plugin");
assert_eq!(manifest.plugin.version, "1.0.0"); assert_eq!(manifest.plugin.version, "1.0.0");
assert_eq!(manifest.plugin.entry, "init.lua"); assert_eq!(manifest.plugin.entry, "main.lua");
} }
#[test] #[test]
@@ -317,6 +341,70 @@ api_url = "https://api.example.com"
assert_eq!(manifest.permissions.run_commands, vec!["myapp"]); assert_eq!(manifest.permissions.run_commands, vec!["myapp"]);
} }
#[test]
fn test_parse_new_format_with_providers_array() {
let toml_str = r#"
[plugin]
id = "my-plugin"
name = "My Plugin"
version = "0.1.0"
description = "Test"
entry_point = "main.rn"
[[providers]]
id = "my-plugin"
name = "My Plugin"
type = "static"
type_id = "myplugin"
icon = "system-run"
prefix = ":mp"
"#;
let manifest: PluginManifest = toml::from_str(toml_str).unwrap();
assert_eq!(manifest.plugin.entry, "main.rn");
assert_eq!(manifest.providers.len(), 1);
let p = &manifest.providers[0];
assert_eq!(p.id, "my-plugin");
assert_eq!(p.name, "My Plugin");
assert_eq!(p.provider_type, "static");
assert_eq!(p.type_id.as_deref(), Some("myplugin"));
assert_eq!(p.icon.as_deref(), Some("system-run"));
assert_eq!(p.prefix.as_deref(), Some(":mp"));
}
#[test]
fn test_parse_new_format_entry_point_alias() {
let toml_str = r#"
[plugin]
id = "test"
name = "Test"
version = "1.0.0"
entry_point = "main.lua"
"#;
let manifest: PluginManifest = toml::from_str(toml_str).unwrap();
assert_eq!(manifest.plugin.entry, "main.lua");
}
#[test]
fn test_provider_spec_defaults() {
let toml_str = r#"
[plugin]
id = "test"
name = "Test"
version = "1.0.0"
[[providers]]
id = "test"
name = "Test"
"#;
let manifest: PluginManifest = toml::from_str(toml_str).unwrap();
assert_eq!(manifest.providers.len(), 1);
let p = &manifest.providers[0];
assert_eq!(p.provider_type, "static"); // default
assert!(p.prefix.is_none());
assert!(p.icon.is_none());
assert!(p.type_id.is_none());
}
#[test] #[test]
fn test_version_compatibility() { fn test_version_compatibility() {
let toml_str = r#" let toml_str = r#"

View File

@@ -10,6 +10,7 @@
//! Note: This module is infrastructure for the runtime architecture. Full integration //! Note: This module is infrastructure for the runtime architecture. Full integration
//! is pending Phase 5 (AUR Packaging) when runtime packages will be available. //! is pending Phase 5 (AUR Packaging) when runtime packages will be available.
use std::mem::ManuallyDrop;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
@@ -69,8 +70,11 @@ pub struct ScriptRuntimeVTable {
pub struct LoadedRuntime { pub struct LoadedRuntime {
/// Runtime name (for logging) /// Runtime name (for logging)
name: &'static str, name: &'static str,
/// Keep library alive /// Keep library alive — wrapped in ManuallyDrop so we never call dlclose().
_library: Arc<Library>, /// dlclose() unmaps the library code; any thread-local destructors inside the
/// library then SIGSEGV when they try to run against the unmapped addresses.
/// Runtime libraries live for the process lifetime, so leaking the handle is safe.
_library: ManuallyDrop<Arc<Library>>,
/// Runtime vtable /// Runtime vtable
vtable: &'static ScriptRuntimeVTable, vtable: &'static ScriptRuntimeVTable,
/// Runtime handle (state) /// Runtime handle (state)
@@ -138,7 +142,7 @@ impl LoadedRuntime {
Ok(Self { Ok(Self {
name, name,
_library: library, _library: ManuallyDrop::new(library),
vtable, vtable,
handle, handle,
providers, providers,
@@ -166,6 +170,8 @@ impl LoadedRuntime {
impl Drop for LoadedRuntime { impl Drop for LoadedRuntime {
fn drop(&mut self) { fn drop(&mut self) {
(self.vtable.drop)(self.handle); (self.vtable.drop)(self.handle);
// Do NOT drop _library: ManuallyDrop ensures dlclose() is never called.
// See field comment for rationale.
} }
} }

View File

@@ -23,24 +23,15 @@ const SEARCH_ENGINES: &[&str] = &[
const BUILTIN_THEMES: &[&str] = &["owl"]; const BUILTIN_THEMES: &[&str] = &["owl"];
/// Boolean provider fields that can be toggled via CONFIG:toggle:providers.*. /// Boolean provider fields that can be toggled via CONFIG:toggle:providers.*.
/// Only built-in providers are listed here; plugins are enabled/disabled via
/// [plugins] disabled_plugins in config.toml or `owlry plugin enable/disable`.
const PROVIDER_TOGGLES: &[(&str, &str)] = &[ const PROVIDER_TOGGLES: &[(&str, &str)] = &[
("applications", "Applications"), ("applications", "Applications"),
("commands", "Commands"), ("commands", "Commands"),
("uuctl", "Systemd Units"),
("calculator", "Calculator"), ("calculator", "Calculator"),
("converter", "Unit Converter"), ("converter", "Unit Converter"),
("frecency", "Frecency Ranking"),
("websearch", "Web Search"),
("system", "System Actions"), ("system", "System Actions"),
("ssh", "SSH Connections"), ("frecency", "Frecency Ranking"),
("clipboard", "Clipboard History"),
("bookmarks", "Bookmarks"),
("emoji", "Emoji Picker"),
("scripts", "Scripts"),
("files", "File Search"),
("media", "Media Widget"),
("weather", "Weather Widget"),
("pomodoro", "Pomodoro Widget"),
]; ];
/// Built-in config editor provider. Interprets query text as a navigation path /// Built-in config editor provider. Interprets query text as a navigation path
@@ -70,12 +61,8 @@ impl ConfigProvider {
false false
}; };
if result { if result && let Ok(cfg) = self.config.read() && let Err(e) = cfg.save() {
if let Ok(cfg) = self.config.read() { warn!("Failed to save config: {}", e);
if let Err(e) = cfg.save() {
warn!("Failed to save config: {}", e);
}
}
} }
result result
@@ -98,10 +85,6 @@ impl ConfigProvider {
cfg.providers.commands = !cfg.providers.commands; cfg.providers.commands = !cfg.providers.commands;
true true
} }
"providers.uuctl" => {
cfg.providers.uuctl = !cfg.providers.uuctl;
true
}
"providers.calculator" => { "providers.calculator" => {
cfg.providers.calculator = !cfg.providers.calculator; cfg.providers.calculator = !cfg.providers.calculator;
true true
@@ -110,52 +93,12 @@ impl ConfigProvider {
cfg.providers.converter = !cfg.providers.converter; cfg.providers.converter = !cfg.providers.converter;
true true
} }
"providers.frecency" => {
cfg.providers.frecency = !cfg.providers.frecency;
true
}
"providers.websearch" => {
cfg.providers.websearch = !cfg.providers.websearch;
true
}
"providers.system" => { "providers.system" => {
cfg.providers.system = !cfg.providers.system; cfg.providers.system = !cfg.providers.system;
true true
} }
"providers.ssh" => { "providers.frecency" => {
cfg.providers.ssh = !cfg.providers.ssh; cfg.providers.frecency = !cfg.providers.frecency;
true
}
"providers.clipboard" => {
cfg.providers.clipboard = !cfg.providers.clipboard;
true
}
"providers.bookmarks" => {
cfg.providers.bookmarks = !cfg.providers.bookmarks;
true
}
"providers.emoji" => {
cfg.providers.emoji = !cfg.providers.emoji;
true
}
"providers.scripts" => {
cfg.providers.scripts = !cfg.providers.scripts;
true
}
"providers.files" => {
cfg.providers.files = !cfg.providers.files;
true
}
"providers.media" => {
cfg.providers.media = !cfg.providers.media;
true
}
"providers.weather" => {
cfg.providers.weather = !cfg.providers.weather;
true
}
"providers.pomodoro" => {
cfg.providers.pomodoro = !cfg.providers.pomodoro;
true true
} }
"general.show_icons" => { "general.show_icons" => {
@@ -762,21 +705,10 @@ fn get_provider_bool(cfg: &Config, field: &str) -> bool {
match field { match field {
"applications" => cfg.providers.applications, "applications" => cfg.providers.applications,
"commands" => cfg.providers.commands, "commands" => cfg.providers.commands,
"uuctl" => cfg.providers.uuctl,
"calculator" => cfg.providers.calculator, "calculator" => cfg.providers.calculator,
"converter" => cfg.providers.converter, "converter" => cfg.providers.converter,
"frecency" => cfg.providers.frecency,
"websearch" => cfg.providers.websearch,
"system" => cfg.providers.system, "system" => cfg.providers.system,
"ssh" => cfg.providers.ssh, "frecency" => cfg.providers.frecency,
"clipboard" => cfg.providers.clipboard,
"bookmarks" => cfg.providers.bookmarks,
"emoji" => cfg.providers.emoji,
"scripts" => cfg.providers.scripts,
"files" => cfg.providers.files,
"media" => cfg.providers.media,
"weather" => cfg.providers.weather,
"pomodoro" => cfg.providers.pomodoro,
_ => false, _ => false,
} }
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "owlry-lua" name = "owlry-lua"
version = "1.1.2" version = "1.1.3"
edition.workspace = true edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
license.workspace = true license.workspace = true
@@ -30,6 +30,9 @@ serde_json = "1.0"
# Version compatibility # Version compatibility
semver = "1" semver = "1"
# Logging
log = "0.4"
# HTTP client for plugins # HTTP client for plugins
reqwest = { version = "0.13", default-features = false, features = ["native-tls", "blocking", "json"] } reqwest = { version = "0.13", default-features = false, features = ["native-tls", "blocking", "json"] }

View File

@@ -50,3 +50,8 @@ pub fn call_refresh(lua: &Lua, provider_name: &str) -> LuaResult<Vec<PluginItem>
pub fn call_query(lua: &Lua, provider_name: &str, query: &str) -> LuaResult<Vec<PluginItem>> { pub fn call_query(lua: &Lua, provider_name: &str, query: &str) -> LuaResult<Vec<PluginItem>> {
provider::call_query(lua, provider_name, query) provider::call_query(lua, provider_name, query)
} }
/// Call the global `refresh()` function (for manifest-declared providers)
pub fn call_global_refresh(lua: &Lua) -> LuaResult<Vec<PluginItem>> {
provider::call_global_refresh(lua)
}

View File

@@ -76,6 +76,15 @@ fn register_provider(lua: &Lua, config: Table) -> LuaResult<()> {
Ok(()) Ok(())
} }
/// Call the top-level `refresh()` global function (for manifest-declared providers)
pub fn call_global_refresh(lua: &Lua) -> LuaResult<Vec<PluginItem>> {
let globals = lua.globals();
match globals.get::<Function>("refresh") {
Ok(refresh_fn) => parse_items_result(refresh_fn.call(())?),
Err(_) => Ok(Vec::new()),
}
}
/// Get all registered providers /// Get all registered providers
pub fn get_registrations(lua: &Lua) -> LuaResult<Vec<ProviderRegistration>> { pub fn get_registrations(lua: &Lua) -> LuaResult<Vec<ProviderRegistration>> {
// Suppress unused warning // Suppress unused warning

View File

@@ -96,8 +96,28 @@ impl LoadedPlugin {
.as_ref() .as_ref()
.ok_or_else(|| "Plugin not initialized".to_string())?; .ok_or_else(|| "Plugin not initialized".to_string())?;
api::get_provider_registrations(lua) let mut regs = api::get_provider_registrations(lua)
.map_err(|e| format!("Failed to get registrations: {}", e)) .map_err(|e| format!("Failed to get registrations: {}", e))?;
// Fall back to manifest [[providers]] declarations when the script
// doesn't call owlry.provider.register() (new-style plugins)
if regs.is_empty() {
for decl in &self.manifest.providers {
regs.push(ProviderRegistration {
name: decl.id.clone(),
display_name: decl.name.clone(),
type_id: decl.type_id.clone().unwrap_or_else(|| decl.id.clone()),
default_icon: decl
.icon
.clone()
.unwrap_or_else(|| "application-x-addon".to_string()),
prefix: decl.prefix.clone(),
is_dynamic: decl.provider_type == "dynamic",
});
}
}
Ok(regs)
} }
/// Call a provider's refresh function /// Call a provider's refresh function
@@ -107,7 +127,17 @@ impl LoadedPlugin {
.as_ref() .as_ref()
.ok_or_else(|| "Plugin not initialized".to_string())?; .ok_or_else(|| "Plugin not initialized".to_string())?;
api::call_refresh(lua, provider_name).map_err(|e| format!("Refresh failed: {}", e)) let items = api::call_refresh(lua, provider_name)
.map_err(|e| format!("Refresh failed: {}", e))?;
// If the API path returned nothing, try calling the global refresh()
// function directly (new-style plugins with manifest [[providers]])
if items.is_empty() {
return api::call_global_refresh(lua)
.map_err(|e| format!("Refresh failed: {}", e));
}
Ok(items)
} }
/// Call a provider's query function /// Call a provider's query function
@@ -156,9 +186,18 @@ pub fn discover_plugins(
match PluginManifest::load(&manifest_path) { match PluginManifest::load(&manifest_path) {
Ok(manifest) => { Ok(manifest) => {
// Skip plugins whose entry point is not a Lua file
if !manifest.plugin.entry.ends_with(".lua") {
log::debug!(
"owlry-lua: Skipping non-Lua plugin at {} (entry: {})",
path.display(),
manifest.plugin.entry
);
continue;
}
let id = manifest.plugin.id.clone(); let id = manifest.plugin.id.clone();
if plugins.contains_key(&id) { if plugins.contains_key(&id) {
eprintln!( log::warn!(
"owlry-lua: Duplicate plugin ID '{}', skipping {}", "owlry-lua: Duplicate plugin ID '{}', skipping {}",
id, id,
path.display() path.display()
@@ -168,7 +207,7 @@ pub fn discover_plugins(
plugins.insert(id, (manifest, path)); plugins.insert(id, (manifest, path));
} }
Err(e) => { Err(e) => {
eprintln!( log::warn!(
"owlry-lua: Failed to load plugin at {}: {}", "owlry-lua: Failed to load plugin at {}: {}",
path.display(), path.display(),
e e
@@ -229,4 +268,79 @@ version = "1.0.0"
let plugins = discover_plugins(Path::new("/nonexistent/path")).unwrap(); let plugins = discover_plugins(Path::new("/nonexistent/path")).unwrap();
assert!(plugins.is_empty()); assert!(plugins.is_empty());
} }
#[test]
fn test_discover_skips_non_lua_plugins() {
let temp = TempDir::new().unwrap();
let plugins_dir = temp.path();
// Rune plugin — should be skipped by the Lua runtime
let rune_dir = plugins_dir.join("rune-plugin");
fs::create_dir_all(&rune_dir).unwrap();
fs::write(
rune_dir.join("plugin.toml"),
r#"
[plugin]
id = "rune-plugin"
name = "Rune Plugin"
version = "1.0.0"
entry_point = "main.rn"
[[providers]]
id = "rune-plugin"
name = "Rune Plugin"
"#,
)
.unwrap();
fs::write(rune_dir.join("main.rn"), "pub fn refresh() { [] }").unwrap();
// Lua plugin — should be discovered
create_test_plugin(plugins_dir, "lua-plugin");
let plugins = discover_plugins(plugins_dir).unwrap();
assert_eq!(plugins.len(), 1);
assert!(plugins.contains_key("lua-plugin"));
assert!(!plugins.contains_key("rune-plugin"));
}
#[test]
fn test_manifest_provider_fallback() {
let temp = TempDir::new().unwrap();
let plugin_dir = temp.path().join("test-plugin");
fs::create_dir_all(&plugin_dir).unwrap();
fs::write(
plugin_dir.join("plugin.toml"),
r#"
[plugin]
id = "test-plugin"
name = "Test Plugin"
version = "1.0.0"
entry_point = "main.lua"
[[providers]]
id = "test-plugin"
name = "Test Plugin"
type = "static"
type_id = "testplugin"
icon = "system-run"
prefix = ":tp"
"#,
)
.unwrap();
// Script that does NOT call owlry.provider.register()
fs::write(plugin_dir.join("main.lua"), "function refresh() return {} end").unwrap();
let manifest =
crate::manifest::PluginManifest::load(&plugin_dir.join("plugin.toml")).unwrap();
let mut plugin = LoadedPlugin::new(manifest, plugin_dir);
plugin.initialize().unwrap();
let regs = plugin.get_provider_registrations().unwrap();
assert_eq!(regs.len(), 1, "should fall back to [[providers]] declaration");
assert_eq!(regs[0].name, "test-plugin");
assert_eq!(regs[0].type_id, "testplugin");
assert_eq!(regs[0].prefix.as_deref(), Some(":tp"));
assert!(!regs[0].is_dynamic);
}
} }

View File

@@ -8,6 +8,10 @@ use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginManifest { pub struct PluginManifest {
pub plugin: PluginInfo, pub plugin: PluginInfo,
/// Provider declarations from [[providers]] sections (new-style)
#[serde(default)]
pub providers: Vec<ProviderDecl>,
/// Legacy provides block (old-style)
#[serde(default)] #[serde(default)]
pub provides: PluginProvides, pub provides: PluginProvides,
#[serde(default)] #[serde(default)]
@@ -16,6 +20,26 @@ pub struct PluginManifest {
pub settings: HashMap<String, toml::Value>, pub settings: HashMap<String, toml::Value>,
} }
/// A provider declared in a [[providers]] section
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderDecl {
pub id: String,
pub name: String,
#[serde(default)]
pub prefix: Option<String>,
#[serde(default)]
pub icon: Option<String>,
/// "static" (default) or "dynamic"
#[serde(default = "default_provider_type", rename = "type")]
pub provider_type: String,
#[serde(default)]
pub type_id: Option<String>,
}
fn default_provider_type() -> String {
"static".to_string()
}
/// Core plugin information /// Core plugin information
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginInfo { pub struct PluginInfo {
@@ -127,6 +151,11 @@ impl PluginManifest {
)); ));
} }
// Lua plugins must have a .lua entry point
if !self.plugin.entry.ends_with(".lua") {
return Err("Entry point must be a .lua file for Lua plugins".to_string());
}
Ok(()) Ok(())
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "owlry-rune" name = "owlry-rune"
version = "1.1.1" version = "1.1.4"
edition = "2024" edition = "2024"
rust-version = "1.90" rust-version = "1.90"
description = "Rune scripting runtime for owlry plugins" description = "Rune scripting runtime for owlry plugins"
@@ -15,7 +15,6 @@ owlry-plugin-api = { path = "../owlry-plugin-api" }
# Rune scripting language # Rune scripting language
rune = "0.14" rune = "0.14"
rune-modules = { version = "0.14", features = ["full"] }
# Logging # Logging
log = "0.4" log = "0.4"

View File

@@ -203,6 +203,7 @@ pub fn discover_rune_plugins(plugins_dir: &Path) -> Result<HashMap<String, Loade
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::fs;
use tempfile::TempDir; use tempfile::TempDir;
#[test] #[test]
@@ -211,4 +212,81 @@ mod tests {
let plugins = discover_rune_plugins(temp.path()).unwrap(); let plugins = discover_rune_plugins(temp.path()).unwrap();
assert!(plugins.is_empty()); assert!(plugins.is_empty());
} }
#[test]
fn test_discover_skips_non_rune_plugins() {
let temp = TempDir::new().unwrap();
let plugins_dir = temp.path();
// Lua plugin — should be skipped by the Rune runtime
let lua_dir = plugins_dir.join("lua-plugin");
fs::create_dir_all(&lua_dir).unwrap();
fs::write(
lua_dir.join("plugin.toml"),
r#"
[plugin]
id = "lua-plugin"
name = "Lua Plugin"
version = "1.0.0"
entry_point = "main.lua"
[[providers]]
id = "lua-plugin"
name = "Lua Plugin"
"#,
)
.unwrap();
fs::write(lua_dir.join("main.lua"), "function refresh() return {} end").unwrap();
let plugins = discover_rune_plugins(plugins_dir).unwrap();
assert!(plugins.is_empty(), "Lua plugin should be skipped by Rune runtime");
}
#[test]
fn test_manifest_provider_fallback() {
let temp = TempDir::new().unwrap();
let plugin_dir = temp.path().join("test-plugin");
fs::create_dir_all(&plugin_dir).unwrap();
fs::write(
plugin_dir.join("plugin.toml"),
r#"
[plugin]
id = "test-plugin"
name = "Test Plugin"
version = "1.0.0"
entry_point = "main.rn"
[[providers]]
id = "test-plugin"
name = "Test Plugin"
type = "static"
type_id = "testplugin"
icon = "system-run"
prefix = ":tp"
"#,
)
.unwrap();
// Script that exports refresh() but doesn't call register_provider()
fs::write(
plugin_dir.join("main.rn"),
r#"use owlry::Item;
pub fn refresh() {
[]
}
"#,
)
.unwrap();
let manifest =
crate::manifest::PluginManifest::load(&plugin_dir.join("plugin.toml")).unwrap();
let plugin = LoadedPlugin::new(manifest, plugin_dir).unwrap();
let regs = plugin.provider_registrations();
assert_eq!(regs.len(), 1, "should fall back to [[providers]] declaration");
assert_eq!(regs[0].name, "test-plugin");
assert_eq!(regs[0].type_id, "testplugin");
assert_eq!(regs[0].prefix.as_deref(), Some(":tp"));
assert!(regs[0].is_static);
}
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "owlry" name = "owlry"
version = "1.0.6" version = "1.0.8"
edition = "2024" edition = "2024"
rust-version = "1.90" rust-version = "1.90"
description = "A lightweight, owl-themed application launcher for Wayland" description = "A lightweight, owl-themed application launcher for Wayland"

View File

@@ -91,17 +91,20 @@ impl OwlryApp {
.iter() .iter()
.map(|s| ProviderFilter::mode_string_to_provider_type(s)) .map(|s| ProviderFilter::mode_string_to_provider_type(s))
.collect(); .collect();
let tabs = &config.borrow().general.tabs.clone();
if provider_types.len() == 1 { if provider_types.len() == 1 {
ProviderFilter::new( ProviderFilter::new(
Some(provider_types[0].clone()), Some(provider_types[0].clone()),
None, None,
&config.borrow().providers, &config.borrow().providers,
tabs,
) )
} else { } else {
ProviderFilter::new(None, Some(provider_types), &config.borrow().providers) ProviderFilter::new(None, Some(provider_types), &config.borrow().providers, tabs)
} }
} else { } else {
ProviderFilter::new(None, None, &config.borrow().providers) let tabs = config.borrow().general.tabs.clone();
ProviderFilter::new(None, None, &config.borrow().providers, &tabs)
}; };
let filter = Rc::new(RefCell::new(filter)); let filter = Rc::new(RefCell::new(filter));

View File

@@ -366,6 +366,14 @@ fn cmd_info_installed(name: &str, json_output: bool) -> CommandResult {
"runtime": runtime.to_string(), "runtime": runtime.to_string(),
"runtime_available": runtime_available, "runtime_available": runtime_available,
"path": plugin_path.display().to_string(), "path": plugin_path.display().to_string(),
"providers": manifest.providers.iter().map(|p| serde_json::json!({
"id": p.id,
"name": p.name,
"type": p.provider_type,
"type_id": p.type_id,
"prefix": p.prefix,
"icon": p.icon,
})).collect::<Vec<_>>(),
"provides": { "provides": {
"providers": manifest.provides.providers, "providers": manifest.provides.providers,
"actions": manifest.provides.actions, "actions": manifest.provides.actions,
@@ -406,9 +414,13 @@ fn cmd_info_installed(name: &str, json_output: bool) -> CommandResult {
); );
println!("Path: {}", plugin_path.display()); println!("Path: {}", plugin_path.display());
println!(); println!();
println!("Provides:"); println!("Providers:");
for p in &manifest.providers {
let prefix = p.prefix.as_deref().map(|s| format!(" ({})", s)).unwrap_or_default();
println!(" {} [{}]{}", p.name, p.provider_type, prefix);
}
if !manifest.provides.providers.is_empty() { if !manifest.provides.providers.is_empty() {
println!(" Providers: {}", manifest.provides.providers.join(", ")); println!(" {}", manifest.provides.providers.join(", "));
} }
if manifest.provides.actions { if manifest.provides.actions {
println!(" Actions: yes"); println!(" Actions: yes");
@@ -754,10 +766,16 @@ fn cmd_create(
let desc = description.unwrap_or("A custom owlry plugin"); let desc = description.unwrap_or("A custom owlry plugin");
let (entry_file, entry_ext) = match runtime { let (entry_file, entry_ext) = match runtime {
PluginRuntime::Lua => ("init.lua", "lua"), PluginRuntime::Lua => ("main.lua", "lua"),
PluginRuntime::Rune => ("init.rn", "rn"), PluginRuntime::Rune => ("main.rn", "rn"),
}; };
// Derive a short type_id from the plugin name (strip common prefixes)
let type_id = name
.strip_prefix("owlry-")
.unwrap_or(name)
.replace('-', "_");
// Create plugin.toml // Create plugin.toml
let manifest = format!( let manifest = format!(
r#"[plugin] r#"[plugin]
@@ -765,25 +783,21 @@ id = "{name}"
name = "{display}" name = "{display}"
version = "0.1.0" version = "0.1.0"
description = "{desc}" description = "{desc}"
author = "" entry_point = "{entry_file}"
owlry_version = ">=0.3.0"
entry = "{entry_file}"
[provides] [[providers]]
providers = ["{name}"] id = "{name}"
actions = false name = "{display}"
themes = [] type = "static"
hooks = false type_id = "{type_id}"
icon = "application-x-addon"
[permissions] # prefix = ":{type_id}"
network = false
filesystem = []
run_commands = []
"#, "#,
name = name, name = name,
display = display, display = display,
desc = desc, desc = desc,
entry_file = entry_file, entry_file = entry_file,
type_id = type_id,
); );
fs::write(plugin_dir.join("plugin.toml"), manifest) fs::write(plugin_dir.join("plugin.toml"), manifest)
@@ -792,91 +806,51 @@ run_commands = []
// Create entry point template based on runtime // Create entry point template based on runtime
match runtime { match runtime {
PluginRuntime::Lua => { PluginRuntime::Lua => {
let init_lua = format!( let main_lua = format!(
r#"-- {display} Plugin for Owlry r#"-- {display} Plugin for Owlry
-- {desc} -- {desc}
-- Register the provider function refresh()
owlry.provider.register({{ return {{
name = "{name}", {{
display_name = "{display}", id = "{name}:example",
type_id = "{name}", name = "Example Item",
default_icon = "application-x-executable", description = "This is an example item from {display}",
icon = "dialog-information",
refresh = function() command = "echo 'Hello from {name}!'",
-- Return a list of items tags = {{}},
return {{ }},
{{ }}
id = "{name}:example", end
name = "Example Item",
description = "This is an example item from {display}",
icon = "dialog-information",
command = "echo 'Hello from {name}!'",
terminal = false,
tags = {{}}
}}
}}
end
}})
owlry.log.info("{display} plugin loaded")
"#, "#,
name = name, name = name,
display = display, display = display,
desc = desc, desc = desc,
); );
fs::write(plugin_dir.join(entry_file), init_lua) fs::write(plugin_dir.join(entry_file), main_lua)
.map_err(|e| format!("Failed to write {}: {}", entry_file, e))?; .map_err(|e| format!("Failed to write {}: {}", entry_file, e))?;
} }
PluginRuntime::Rune => { PluginRuntime::Rune => {
// Note: Rune uses #{{ for object literals, so we build manually let main_rn = format!(
let init_rn = format!( r#"use owlry::Item;
r#"//! {display} Plugin for Owlry
//! {desc}
/// Plugin item structure pub fn refresh() {{
struct Item {{{{ let items = [];
id: String,
name: String,
description: String,
icon: String,
command: String,
terminal: bool,
tags: Vec<String>,
}}}}
/// Provider registration items.push(
pub fn register(owlry) {{{{ Item::new("{name}:example", "Example Item", "echo 'Hello from {name}!'")
owlry.provider.register(#{{{{ .description("This is an example item from {display}")
name: "{name}", .icon("dialog-information")
display_name: "{display}", .keywords(["example"]),
type_id: "{name}", );
default_icon: "application-x-executable",
refresh: || {{{{ items
// Return a list of items }}
[
Item {{{{
id: "{name}:example",
name: "Example Item",
description: "This is an example item from {display}",
icon: "dialog-information",
command: "echo 'Hello from {name}!'",
terminal: false,
tags: [],
}}}},
]
}}}},
}}}});
owlry.log.info("{display} plugin loaded");
}}}}
"#, "#,
name = name, name = name,
display = display, display = display,
desc = desc,
); );
fs::write(plugin_dir.join(entry_file), init_rn) fs::write(plugin_dir.join(entry_file), main_rn)
.map_err(|e| format!("Failed to write {}: {}", entry_file, e))?; .map_err(|e| format!("Failed to write {}: {}", entry_file, e))?;
} }
} }
@@ -955,13 +929,14 @@ fn cmd_validate(path: Option<&str>) -> CommandResult {
)); ));
} }
// Check for empty provides // Check for empty provides (accept either [[providers]] or [provides])
if manifest.provides.providers.is_empty() let has_providers = !manifest.providers.is_empty()
&& !manifest.provides.actions || !manifest.provides.providers.is_empty()
&& manifest.provides.themes.is_empty() || manifest.provides.actions
&& !manifest.provides.hooks || !manifest.provides.themes.is_empty()
{ || manifest.provides.hooks;
warnings.push("Plugin does not provide any features".to_string()); if !has_providers {
warnings.push("Plugin does not declare any providers".to_string());
} }
println!(" Plugin ID: {}", manifest.plugin.id); println!(" Plugin ID: {}", manifest.plugin.id);
@@ -1013,11 +988,11 @@ fn cmd_runtimes() -> CommandResult {
if lua_available { if lua_available {
println!(" ✓ Lua - Installed"); println!(" ✓ Lua - Installed");
println!(" Package: owlry-lua"); println!(" Package: owlry-lua");
println!(" Entry point: init.lua"); println!(" Entry point: main.lua");
} else { } else {
println!(" ✗ Lua - Not installed"); println!(" ✗ Lua - Not installed");
println!(" Install: yay -S owlry-lua"); println!(" Install: yay -S owlry-lua");
println!(" Entry point: init.lua"); println!(" Entry point: main.lua");
} }
println!(); println!();
@@ -1026,11 +1001,11 @@ fn cmd_runtimes() -> CommandResult {
if rune_available { if rune_available {
println!(" ✓ Rune - Installed"); println!(" ✓ Rune - Installed");
println!(" Package: owlry-rune"); println!(" Package: owlry-rune");
println!(" Entry point: init.rn"); println!(" Entry point: main.rn");
} else { } else {
println!(" ✗ Rune - Not installed"); println!(" ✗ Rune - Not installed");
println!(" Install: yay -S owlry-rune"); println!(" Install: yay -S owlry-rune");
println!(" Entry point: init.rn"); println!(" Entry point: main.rn");
} }
println!(); println!();

View File

@@ -394,7 +394,9 @@ impl MainWindow {
format!("Search {}...", active.join(", ")) format!("Search {}...", active.join(", "))
} }
/// Build dynamic hints based on enabled providers /// Build hints string for the status bar based on enabled built-in providers.
/// Plugin trigger hints (? web, / files, etc.) are not included here since
/// plugin availability is not tracked in ProvidersConfig.
fn build_hints(config: &owlry_core::config::ProvidersConfig) -> String { fn build_hints(config: &owlry_core::config::ProvidersConfig) -> String {
let mut parts: Vec<String> = vec![ let mut parts: Vec<String> = vec![
"Tab: cycle".to_string(), "Tab: cycle".to_string(),
@@ -403,38 +405,14 @@ impl MainWindow {
"Esc: close".to_string(), "Esc: close".to_string(),
]; ];
// Add trigger hints for enabled dynamic providers
if config.calculator { if config.calculator {
parts.push("= calc".to_string()); parts.push("= calc".to_string());
} }
if config.websearch { if config.converter {
parts.push("? web".to_string()); parts.push("> conv".to_string());
} }
if config.files {
parts.push("/ files".to_string());
}
// Add prefix hints for static providers
let mut prefixes = Vec::new();
if config.system { if config.system {
prefixes.push(":sys"); parts.push(":sys".to_string());
}
if config.emoji {
prefixes.push(":emoji");
}
if config.ssh {
prefixes.push(":ssh");
}
if config.clipboard {
prefixes.push(":clip");
}
if config.bookmarks {
prefixes.push(":bm");
}
// Only show first few prefixes to avoid overflow
if !prefixes.is_empty() {
parts.push(prefixes[..prefixes.len().min(4)].join(" "));
} }
parts.join(" ") parts.join(" ")
@@ -1159,6 +1137,7 @@ impl MainWindow {
for provider in tab_order { for provider in tab_order {
f.enable(provider.clone()); f.enable(provider.clone());
} }
f.restore_all_mode();
} }
for (_, button) in buttons.borrow().iter() { for (_, button) in buttons.borrow().iter() {
button.set_active(true); button.set_active(true);

View File

@@ -59,8 +59,10 @@ max_results = 100
# Requires: uwsm to be installed # Requires: uwsm to be installed
# use_uwsm = true # use_uwsm = true
# Header tabs - providers shown as toggle buttons (Ctrl+1, Ctrl+2, etc.) # Header tabs provider tabs shown in the UI bar (Ctrl+1..9 to toggle)
# Values: app, cmd, uuctl, bookmark, calc, clip, dmenu, emoji, file, script, ssh, sys, web # Core values: app, cmd, dmenu
# Plugin values: uuctl, calc, clip, emoji, file, script, ssh, sys, web, bm
# Any installed plugin's type_id is valid (e.g. "weather", "media")
tabs = ["app", "cmd", "uuctl"] tabs = ["app", "cmd", "uuctl"]
# ═══════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════
@@ -140,55 +142,25 @@ disabled_plugins = []
# PROVIDERS # PROVIDERS
# ═══════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════
# #
# Enable/disable providers and configure their settings. # Controls built-in providers only. Plugins are enabled/disabled via
# Core providers (applications, commands) are built into the binary. # [plugins] disabled_plugins or `owlry plugin enable/disable <name>`.
# Plugin providers require their .so to be installed.
[providers] [providers]
# Core providers (always available) # Core providers
applications = true # .desktop applications from XDG dirs applications = true # .desktop applications from XDG dirs
commands = true # Executables from $PATH commands = true # Executables from $PATH
# Frecency - boost frequently/recently used items # Built-in providers (compiled into owlry-core)
calculator = true # Math expressions (= or calc trigger)
converter = true # Unit/currency conversion (> trigger)
system = true # System actions: shutdown, reboot, lock, etc.
# Frecency — boost frequently/recently used items
# Data stored in: ~/.local/share/owlry/frecency.json # Data stored in: ~/.local/share/owlry/frecency.json
frecency = true frecency = true
frecency_weight = 0.3 # 0.0 = disabled, 1.0 = strong boost frecency_weight = 0.3 # 0.0 = disabled, 1.0 = strong boost
# ───────────────────────────────────────────────────────────────────────── # Web search engine (used by owlry-plugin-websearch)
# Plugin provider toggles (require corresponding plugin installed)
# ─────────────────────────────────────────────────────────────────────────
uuctl = true # systemd user units
system = true # System commands (shutdown, reboot, etc.)
ssh = true # SSH hosts from ~/.ssh/config
clipboard = true # Clipboard history (requires cliphist)
bookmarks = true # Browser bookmarks
emoji = true # Emoji picker
scripts = true # Custom scripts from ~/.local/share/owlry/scripts/
files = true # File search (requires fd or mlocate)
calculator = true # Calculator (= expression)
websearch = true # Web search (? query)
# ─────────────────────────────────────────────────────────────────────────
# Widget providers (displayed at top of results)
# ─────────────────────────────────────────────────────────────────────────
media = true # MPRIS media player controls
weather = false # Weather widget (disabled by default)
pomodoro = false # Pomodoro timer (disabled by default)
# ─────────────────────────────────────────────────────────────────────────
# Provider settings
# ─────────────────────────────────────────────────────────────────────────
# Web search engine
# Options: google, duckduckgo, bing, startpage, searxng, brave, ecosia # Options: google, duckduckgo, bing, startpage, searxng, brave, ecosia
# Or custom URL: "https://search.example.com/?q={query}" # Or a custom URL: "https://search.example.com/?q={query}"
search_engine = "duckduckgo" search_engine = "duckduckgo"
# Weather settings (when weather = true)
# weather_provider = "wttr.in" # Options: wttr.in, openweathermap, open-meteo
# weather_location = "Berlin" # City name or coordinates
# weather_api_key = "" # Required for openweathermap
# Pomodoro settings (when pomodoro = true)
# pomodoro_work_mins = 25 # Work session duration
# pomodoro_break_mins = 5 # Break duration

View File

@@ -252,6 +252,7 @@ aur-publish-all:
for dir in aur/*/; do for dir in aur/*/; do
pkg=$(basename "$dir") pkg=$(basename "$dir")
[ -d "$dir/.git" ] || continue [ -d "$dir/.git" ] || continue
[ -f "$dir/PKGBUILD" ] || continue
echo "=== $pkg ===" echo "=== $pkg ==="
just aur-publish-pkg "$pkg" just aur-publish-pkg "$pkg"
echo "" echo ""

View File

@@ -135,6 +135,45 @@ done
[[ ${#INPUT_PKGS[@]} -eq 0 ]] && usage [[ ${#INPUT_PKGS[@]} -eq 0 ]] && usage
# ─── Inject deduplication ────────────────────────────────────────────────────
# Extract the package name from a .pkg.tar.zst filename.
# Arch package filenames follow: {pkgname}-{pkgver}-{pkgrel}-{arch}.pkg.tar.zst
# pkgver is guaranteed to have no dashes, so stripping the last three
# dash-separated segments leaves pkgname.
pkg_name_from_file() {
local base
base=$(basename "$1" .pkg.tar.zst)
base="${base%-*}" # strip arch
base="${base%-*}" # strip pkgrel
base="${base%-*}" # strip pkgver (no dashes in pkgver by Arch policy)
echo "$base"
}
# Deduplicate a list of .pkg.tar.zst paths by package name.
# When the same package name appears more than once, keep the highest version
# (determined by sort -V on the filenames) and warn about the dropped ones.
dedup_inject_files() {
[[ $# -eq 0 ]] && return 0
local -A best=()
local f name winner
for f in "$@"; do
name=$(pkg_name_from_file "$f")
if [[ -v "best[$name]" ]]; then
winner=$(printf '%s\n%s\n' "${best[$name]}" "$f" | sort -V | tail -1)
if [[ "$winner" == "$f" ]]; then
warn "Dropping duplicate inject (older): $(basename "${best[$name]}")"
best[$name]="$f"
else
warn "Dropping duplicate inject (older): $(basename "$f")"
fi
else
best[$name]="$f"
fi
done
printf '%s\n' "${best[@]}"
}
# ─── Dependency resolution ─────────────────────────────────────────────────── # ─── Dependency resolution ───────────────────────────────────────────────────
# Return the names of local AUR packages that PKG depends on. # Return the names of local AUR packages that PKG depends on.
@@ -296,6 +335,11 @@ build_one() {
# ─── Main ──────────────────────────────────────────────────────────────────── # ─── Main ────────────────────────────────────────────────────────────────────
# Deduplicate external inject files by package name (keep highest version)
if [[ ${#EXTRA_INJECT[@]} -gt 1 ]]; then
mapfile -t EXTRA_INJECT < <(dedup_inject_files "${EXTRA_INJECT[@]}")
fi
# Validate all requested packages exist # Validate all requested packages exist
for pkg in "${INPUT_PKGS[@]}"; do for pkg in "${INPUT_PKGS[@]}"; do
[[ -d "$AUR_DIR/$pkg" && -f "$AUR_DIR/$pkg/PKGBUILD" ]] \ [[ -d "$AUR_DIR/$pkg" && -f "$AUR_DIR/$pkg/PKGBUILD" ]] \