feat: simplify ProviderType, add plugin priority, fix bookmarks SQLite

Core changes:
- Simplified ProviderType enum to 4 core types + Plugin(String)
- Added priority field to plugin API (API_VERSION = 3)
- Removed hardcoded plugin-specific code from core
- Updated filter.rs to use Plugin(type_id) for all plugins
- Updated main_window.rs UI mappings to derive from type_id
- Fixed weather/media SVG icon colors

Plugin changes:
- All plugins now declare their own priority values
- Widget plugins: weather(12000), pomodoro(11500), media(11000)
- Dynamic plugins: calc(10000), websearch(9000), filesearch(8000)
- Static plugins: priority 0 (frecency-based)

Bookmarks plugin:
- Replaced SQLx with rusqlite + bundled SQLite
- Fixes "undefined symbol: sqlite3_db_config" build errors
- No longer depends on system SQLite version

Config:
- Fixed config.example.toml invalid nested TOML sections
- Removed [providers.websearch], [providers.weather], etc.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 07:45:49 +01:00
parent ecaaae39e3
commit 8c1cf88474
33 changed files with 436 additions and 1043 deletions

683
Cargo.lock generated
View File

@@ -77,12 +77,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -304,15 +298,6 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "atoi"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
dependencies = [
"num-traits",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@@ -331,20 +316,11 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
dependencies = [
"serde_core",
]
[[package]]
name = "block"
@@ -352,15 +328,6 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "block2"
version = "0.6.2"
@@ -400,12 +367,6 @@ version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.11.0"
@@ -563,12 +524,6 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "const-oid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const_panic"
version = "0.2.15"
@@ -609,30 +564,6 @@ version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533d38ecd2709b7608fb8e18e4504deb99e9a72879e6aa66373a76d8dc4259ea"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crc"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
dependencies = [
"crc-catalog",
]
[[package]]
name = "crc-catalog"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc32fast"
version = "1.5.0"
@@ -657,42 +588,12 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid",
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "deranged"
version = "0.5.5"
@@ -702,18 +603,6 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
"subtle",
]
[[package]]
name = "dirs"
version = "5.0.1"
@@ -756,20 +645,11 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
dependencies = [
"serde",
]
[[package]]
name = "encoding_rs"
@@ -863,17 +743,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "etcetera"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
dependencies = [
"cfg-if",
"home",
"windows-sys 0.48.0",
]
[[package]]
name = "event-listener"
version = "5.4.1"
@@ -895,6 +764,18 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fastrand"
version = "2.3.0"
@@ -927,29 +808,12 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "flume"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
"futures-core",
"futures-sink",
"spin",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -1016,17 +880,6 @@ dependencies = [
"futures-util",
]
[[package]]
name = "futures-intrusive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [
"futures-core",
"lock_api",
"parking_lot",
]
[[package]]
name = "futures-io"
version = "0.3.31"
@@ -1177,16 +1030,6 @@ dependencies = [
"windows-result 0.4.1",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.16"
@@ -1588,13 +1431,11 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
"ahash",
]
[[package]]
@@ -1605,11 +1446,11 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "hashlink"
version = "0.10.0"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
dependencies = [
"hashbrown 0.15.5",
"hashbrown 0.14.5",
]
[[package]]
@@ -1630,33 +1471,6 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hkdf"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
"hmac",
]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "home"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "http"
version = "1.4.0"
@@ -1986,9 +1800,6 @@ name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
"spin",
]
[[package]]
name = "libc"
@@ -2016,12 +1827,6 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
version = "0.1.12"
@@ -2030,7 +1835,6 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags",
"libc",
"redox_syscall 0.7.0",
]
[[package]]
@@ -2152,16 +1956,6 @@ dependencies = [
"regex-automata",
]
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.7.6"
@@ -2369,22 +2163,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
dependencies = [
"lazy_static",
"libm",
"num-integer",
"num-iter",
"num-traits",
"rand 0.8.5",
"smallvec",
"zeroize",
]
[[package]]
name = "num-complex"
version = "0.4.6"
@@ -2438,7 +2216,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@@ -2656,10 +2433,9 @@ dependencies = [
"abi_stable",
"dirs",
"owlry-plugin-api",
"rusqlite",
"serde",
"serde_json",
"sqlx",
"tokio",
]
[[package]]
@@ -2838,7 +2614,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.18",
"redox_syscall",
"smallvec",
"windows-link 0.2.1",
]
@@ -2849,15 +2625,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
dependencies = [
"base64ct",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
@@ -2907,27 +2674,6 @@ dependencies = [
"futures-io",
]
[[package]]
name = "pkcs1"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
"der",
"pkcs8",
"spki",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"spki",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
@@ -3043,7 +2789,7 @@ dependencies = [
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand 0.9.2",
"rand",
"ring",
"rustc-hash",
"rustls",
@@ -3084,35 +2830,14 @@ version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
"rand_chacha",
"rand_core",
]
[[package]]
@@ -3122,16 +2847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.16",
"rand_core",
]
[[package]]
@@ -3152,15 +2868,6 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_syscall"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.6"
@@ -3270,26 +2977,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rsa"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88"
dependencies = [
"const-oid",
"digest",
"num-bigint-dig",
"num-integer",
"num-traits",
"pkcs1",
"pkcs8",
"rand_core 0.6.4",
"signature",
"spki",
"subtle",
"zeroize",
]
[[package]]
name = "rune"
version = "0.14.1"
@@ -3398,6 +3085,20 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "rusqlite"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
@@ -3617,28 +3318,6 @@ dependencies = [
"serde",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
@@ -3664,16 +3343,6 @@ dependencies = [
"libc",
]
[[package]]
name = "signature"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core 0.6.4",
]
[[package]]
name = "simd-adler32"
version = "0.3.8"
@@ -3697,9 +3366,6 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
dependencies = [
"serde",
]
[[package]]
name = "socket2"
@@ -3711,213 +3377,6 @@ dependencies = [
"windows-sys 0.60.2",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "spki"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
"der",
]
[[package]]
name = "sqlx"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
dependencies = [
"sqlx-core",
"sqlx-macros",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
]
[[package]]
name = "sqlx-core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
dependencies = [
"base64",
"bytes",
"crc",
"crossbeam-queue",
"either",
"event-listener",
"futures-core",
"futures-intrusive",
"futures-io",
"futures-util",
"hashbrown 0.15.5",
"hashlink",
"indexmap",
"log",
"memchr",
"once_cell",
"percent-encoding",
"serde",
"serde_json",
"sha2",
"smallvec",
"thiserror 2.0.17",
"tokio",
"tokio-stream",
"tracing",
"url",
]
[[package]]
name = "sqlx-macros"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
dependencies = [
"proc-macro2",
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn 2.0.111",
]
[[package]]
name = "sqlx-macros-core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
dependencies = [
"dotenvy",
"either",
"heck",
"hex",
"once_cell",
"proc-macro2",
"quote",
"serde",
"serde_json",
"sha2",
"sqlx-core",
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn 2.0.111",
"tokio",
"url",
]
[[package]]
name = "sqlx-mysql"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
"base64",
"bitflags",
"byteorder",
"bytes",
"crc",
"digest",
"dotenvy",
"either",
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"generic-array",
"hex",
"hkdf",
"hmac",
"itoa",
"log",
"md-5",
"memchr",
"once_cell",
"percent-encoding",
"rand 0.8.5",
"rsa",
"serde",
"sha1",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 2.0.17",
"tracing",
"whoami",
]
[[package]]
name = "sqlx-postgres"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
"base64",
"bitflags",
"byteorder",
"crc",
"dotenvy",
"etcetera",
"futures-channel",
"futures-core",
"futures-util",
"hex",
"hkdf",
"hmac",
"home",
"itoa",
"log",
"md-5",
"memchr",
"once_cell",
"rand 0.8.5",
"serde",
"serde_json",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
"thiserror 2.0.17",
"tracing",
"whoami",
]
[[package]]
name = "sqlx-sqlite"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
dependencies = [
"atoi",
"flume",
"futures-channel",
"futures-core",
"futures-executor",
"futures-intrusive",
"futures-util",
"libsqlite3-sys",
"log",
"percent-encoding",
"serde",
"serde_urlencoded",
"sqlx-core",
"thiserror 2.0.17",
"tracing",
"url",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@@ -3930,17 +3389,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stringprep"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
"unicode-properties",
]
[[package]]
name = "strsim"
version = "0.11.1"
@@ -4209,17 +3657,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.17"
@@ -4381,7 +3818,6 @@ version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -4476,12 +3912,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "typewit"
version = "1.14.2"
@@ -4505,33 +3935,12 @@ version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-bidi"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "unicode-normalization"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-properties"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
[[package]]
name = "unicode-width"
version = "0.1.14"
@@ -4627,12 +4036,6 @@ dependencies = [
"wit-bindgen",
]
[[package]]
name = "wasite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.106"
@@ -4732,16 +4135,6 @@ dependencies = [
"winsafe",
]
[[package]]
name = "whoami"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
dependencies = [
"libredox",
"wasite",
]
[[package]]
name = "winapi"
version = "0.3.9"

View File

@@ -31,7 +31,9 @@ use abi_stable::StableAbi;
pub use abi_stable::std_types::{ROption, RStr, RString, RVec};
/// Current plugin API version - plugins must match this
pub const API_VERSION: u32 = 1;
/// v2: Added ProviderPosition for widget support
/// v3: Added priority field for plugin-declared result ordering
pub const API_VERSION: u32 = 3;
/// Plugin metadata returned by the info function
#[repr(C)]
@@ -65,6 +67,14 @@ pub struct ProviderInfo {
pub provider_type: ProviderKind,
/// Short type identifier for UI badges (e.g., "calc", "web")
pub type_id: RString,
/// Display position (Normal or Widget)
pub position: ProviderPosition,
/// Priority for result ordering (higher values appear first)
/// Suggested ranges:
/// - Widgets: 10000-12000
/// - Dynamic providers: 7000-10000
/// - Static providers: 0-5000 (use 0 for frecency-based ordering)
pub priority: i32,
}
/// Provider behavior type
@@ -77,6 +87,20 @@ pub enum ProviderKind {
Dynamic,
}
/// Provider display position
///
/// Controls where in the result list this provider's items appear.
#[repr(C)]
#[derive(StableAbi, Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ProviderPosition {
/// Standard position in results (sorted by score/frecency)
#[default]
Normal,
/// Widget position - appears at top of results when query is empty
/// Widgets are always visible regardless of filter settings
Widget,
}
/// A single searchable/launchable item returned by providers
#[repr(C)]
#[derive(StableAbi, Clone, Debug)]

View File

@@ -27,5 +27,5 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# For reading Firefox bookmarks (places.sqlite)
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
tokio = { version = "1", features = ["rt-multi-thread"] }
# Use bundled SQLite to avoid system library version conflicts
rusqlite = { version = "0.32", features = ["bundled"] }

View File

@@ -3,7 +3,7 @@
//! A static provider that reads browser bookmarks from various browsers.
//!
//! Supported browsers:
//! - Firefox (via places.sqlite using SQLx)
//! - Firefox (via places.sqlite using rusqlite with bundled SQLite)
//! - Chrome
//! - Chromium
//! - Brave
@@ -11,11 +11,11 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
use rusqlite::{Connection, OpenFlags};
use serde::Deserialize;
use sqlx::sqlite::SqlitePoolOptions;
use sqlx::Row;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
@@ -248,19 +248,9 @@ impl BookmarksState {
}
}
// Load Firefox bookmarks with favicons (async via tokio)
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => {
loading.store(false, Ordering::SeqCst);
return;
}
};
// Load Firefox bookmarks with favicons (synchronous with rusqlite)
for path in Self::firefox_places_paths() {
rt.block_on(async {
Self::read_firefox_bookmarks_async(&path, &mut items).await;
});
Self::read_firefox_bookmarks(&path, &mut items);
}
// Save to cache for next startup
@@ -323,21 +313,24 @@ impl BookmarksState {
}
}
/// Read Firefox bookmarks asynchronously
async fn read_firefox_bookmarks_async(places_path: &PathBuf, items: &mut Vec<PluginItem>) {
/// Read Firefox bookmarks using rusqlite (synchronous, bundled SQLite)
fn read_firefox_bookmarks(places_path: &PathBuf, items: &mut Vec<PluginItem>) {
let temp_dir = std::env::temp_dir();
let temp_db = temp_dir.join("owlry_places_temp.sqlite");
// Copy database to temp location to avoid locking issues
if fs::copy(places_path, &temp_db).is_err() {
return;
}
// Also copy WAL file if it exists
let wal_path = places_path.with_extension("sqlite-wal");
if wal_path.exists() {
let temp_wal = temp_db.with_extension("sqlite-wal");
let _ = fs::copy(&wal_path, &temp_wal);
}
// Copy favicons database if available
let favicons_path = Self::firefox_favicons_path(places_path);
let temp_favicons = temp_dir.join("owlry_favicons_temp.sqlite");
if let Some(ref fp) = favicons_path {
@@ -348,21 +341,10 @@ impl BookmarksState {
}
}
let db_url = format!("sqlite:{}?mode=ro", temp_db.display());
let favicons_url = if favicons_path.is_some() {
Some(format!("sqlite:{}?mode=ro", temp_favicons.display()))
} else {
None
};
let cache_dir = Self::ensure_favicon_cache_dir();
let bookmarks = Self::fetch_firefox_bookmarks_with_favicons(
&db_url,
favicons_url.as_deref(),
cache_dir.as_ref(),
)
.await;
// Read bookmarks from places.sqlite
let bookmarks = Self::fetch_firefox_bookmarks(&temp_db, &temp_favicons, cache_dir.as_ref());
// Clean up temp files
let _ = fs::remove_file(&temp_db);
@@ -385,19 +367,18 @@ impl BookmarksState {
}
}
/// Fetch Firefox bookmarks with their favicons
async fn fetch_firefox_bookmarks_with_favicons(
places_url: &str,
favicons_url: Option<&str>,
/// Fetch Firefox bookmarks with optional favicons
fn fetch_firefox_bookmarks(
places_path: &Path,
favicons_path: &Path,
cache_dir: Option<&PathBuf>,
) -> Vec<(String, String, Option<String>)> {
// First, fetch bookmarks from places.sqlite
let pool = match SqlitePoolOptions::new()
.max_connections(1)
.connect(places_url)
.await
{
Ok(p) => p,
// Open places.sqlite in read-only mode
let conn = match Connection::open_with_flags(
places_path,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
) {
Ok(c) => c,
Err(_) => return Vec::new(),
};
@@ -416,43 +397,38 @@ impl BookmarksState {
LIMIT 500
"#;
let rows = match sqlx::query(query).fetch_all(&pool).await {
Ok(r) => r,
let mut stmt = match conn.prepare(query) {
Ok(s) => s,
Err(_) => return Vec::new(),
};
let bookmarks: Vec<(String, String)> = rows
.into_iter()
.filter_map(|row| {
let title: Option<String> = row.get("title");
let url: Option<String> = row.get("url");
match (title, url) {
(Some(t), Some(u)) => Some((t, u)),
_ => None,
}
let bookmarks: Vec<(String, String)> = stmt
.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})
.collect();
.ok()
.map(|rows| rows.filter_map(|r| r.ok()).collect())
.unwrap_or_default();
// If no favicons database or cache dir, return without favicons
let (favicons_url, cache_dir) = match (favicons_url, cache_dir) {
(Some(f), Some(c)) => (f, c),
_ => return bookmarks.into_iter().map(|(t, u)| (t, u, None)).collect(),
// If no favicons or cache dir, return without favicons
let cache_dir = match cache_dir {
Some(c) => c,
None => return bookmarks.into_iter().map(|(t, u)| (t, u, None)).collect(),
};
// Connect to favicons database
let fav_pool = match SqlitePoolOptions::new()
.max_connections(1)
.connect(favicons_url)
.await
{
Ok(p) => p,
// Try to open favicons database
let fav_conn = match Connection::open_with_flags(
favicons_path,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
) {
Ok(c) => c,
Err(_) => return bookmarks.into_iter().map(|(t, u)| (t, u, None)).collect(),
};
// Fetch favicons for each URL
let mut results = Vec::new();
for (title, url) in bookmarks {
let favicon_path = Self::get_favicon_for_url(&fav_pool, &url, cache_dir).await;
let favicon_path = Self::get_favicon_for_url(&fav_conn, &url, cache_dir);
results.push((title, url, favicon_path));
}
@@ -460,8 +436,8 @@ impl BookmarksState {
}
/// Get favicon for a URL, caching to file if needed
async fn get_favicon_for_url(
pool: &sqlx::SqlitePool,
fn get_favicon_for_url(
conn: &Connection,
page_url: &str,
cache_dir: &Path,
) -> Option<String> {
@@ -486,13 +462,11 @@ impl BookmarksState {
LIMIT 1
"#;
let row = sqlx::query(query)
.bind(page_url)
.fetch_optional(pool)
.await
.ok()??;
let data: Option<Vec<u8>> = conn
.query_row(query, [page_url], |row| row.get(0))
.ok();
let data: Vec<u8> = row.get("data");
let data = data?;
if data.is_empty() {
return None;
}
@@ -549,6 +523,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 0, // Static: use frecency ordering
}]
.into()
}
@@ -623,7 +599,7 @@ mod tests {
// This will find paths if Firefox is installed
let paths = BookmarksState::firefox_places_paths();
// Path detection should work (may be empty if Firefox not installed)
assert!(paths.len() >= 0);
let _ = paths.len(); // Just ensure it doesn't panic
}
#[test]
@@ -656,7 +632,7 @@ mod tests {
#[test]
fn test_process_folder() {
let mut state = BookmarksState::new();
let mut items = Vec::new();
let folder = ChromeBookmarkNode {
name: Some("Test Folder".to_string()),
@@ -672,9 +648,9 @@ mod tests {
]),
};
BookmarksState::process_chrome_folder_static(&folder, &mut state.items);
assert_eq!(state.items.len(), 1);
assert_eq!(state.items[0].name.as_str(), "Test Bookmark");
BookmarksState::process_chrome_folder_static(&folder, &mut items);
assert_eq!(items.len(), 1);
assert_eq!(items[0].name.as_str(), "Test Bookmark");
}
#[test]

View File

@@ -10,7 +10,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
// Plugin metadata
@@ -51,6 +52,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Dynamic,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 10000, // Dynamic: calculator results first
}]
.into()
}

View File

@@ -9,7 +9,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
use std::process::Command;
@@ -137,6 +138,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 0, // Static: use frecency ordering
}]
.into()
}

View File

@@ -9,7 +9,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
// Plugin metadata
@@ -453,6 +454,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 0, // Static: use frecency ordering
}]
.into()
}

View File

@@ -12,7 +12,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
use std::path::Path;
use std::process::Command;
@@ -207,6 +208,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Dynamic,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 8000, // Dynamic: file search
}]
.into()
}

View File

@@ -5,7 +5,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
use std::process::Command;
@@ -355,6 +356,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Widget,
priority: 11000, // Widget: media player
}]
.into()
}

View File

@@ -16,7 +16,7 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
notify_with_urgency, owlry_plugin, NotifyUrgency, PluginInfo, PluginItem, ProviderHandle,
ProviderInfo, ProviderKind, API_VERSION,
ProviderInfo, ProviderKind, ProviderPosition, API_VERSION,
};
use serde::{Deserialize, Serialize};
use std::fs;
@@ -396,6 +396,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Widget,
priority: 11500, // Widget: pomodoro timer
}]
.into()
}

View File

@@ -12,7 +12,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
use std::fs;
use std::os::unix::fs::PermissionsExt;
@@ -187,6 +188,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 0, // Static: use frecency ordering
}]
.into()
}

View File

@@ -9,7 +9,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
use std::fs;
use std::path::PathBuf;
@@ -204,6 +205,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 0, // Static: use frecency ordering
}]
.into()
}

View File

@@ -13,7 +13,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
// Plugin metadata
@@ -129,6 +130,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 0, // Static: use frecency ordering
}]
.into()
}

View File

@@ -10,7 +10,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
use std::process::Command;
@@ -285,6 +286,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 0, // Static: use frecency ordering
}]
.into()
}

View File

@@ -20,7 +20,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
use serde::{Deserialize, Serialize};
use std::fs;
@@ -642,6 +643,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Static,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Widget,
priority: 12000, // Widget: highest priority
}]
.into()
}

View File

@@ -10,7 +10,8 @@
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind, API_VERSION,
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, API_VERSION,
};
// Plugin metadata
@@ -164,6 +165,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
icon: RString::from(PROVIDER_ICON),
provider_type: ProviderKind::Dynamic,
type_id: RString::from(PROVIDER_TYPE_ID),
position: ProviderPosition::Normal,
priority: 9000, // Dynamic: web search
}]
.into()
}

View File

@@ -37,43 +37,44 @@ impl ProviderFilter {
} else {
// Use config file settings, default to apps only
let mut set = HashSet::new();
// Core providers
if config_providers.applications {
set.insert(ProviderType::Application);
}
if config_providers.commands {
set.insert(ProviderType::Command);
}
// Plugin providers - use Plugin(type_id) for all
if config_providers.uuctl {
set.insert(ProviderType::Uuctl);
set.insert(ProviderType::Plugin("uuctl".to_string()));
}
if config_providers.system {
set.insert(ProviderType::System);
set.insert(ProviderType::Plugin("system".to_string()));
}
if config_providers.ssh {
set.insert(ProviderType::Ssh);
set.insert(ProviderType::Plugin("ssh".to_string()));
}
if config_providers.clipboard {
set.insert(ProviderType::Clipboard);
set.insert(ProviderType::Plugin("clipboard".to_string()));
}
if config_providers.bookmarks {
set.insert(ProviderType::Bookmarks);
set.insert(ProviderType::Plugin("bookmarks".to_string()));
}
if config_providers.emoji {
set.insert(ProviderType::Emoji);
set.insert(ProviderType::Plugin("emoji".to_string()));
}
if config_providers.scripts {
set.insert(ProviderType::Scripts);
set.insert(ProviderType::Plugin("scripts".to_string()));
}
// Dynamic providers: add to filter set so they work in "All" mode
// but can still be excluded when in single-provider mode
// Dynamic providers
if config_providers.files {
set.insert(ProviderType::Files);
set.insert(ProviderType::Plugin("filesearch".to_string()));
}
if config_providers.calculator {
set.insert(ProviderType::Calculator);
set.insert(ProviderType::Plugin("calc".to_string()));
}
if config_providers.websearch {
set.insert(ProviderType::WebSearch);
set.insert(ProviderType::Plugin("websearch".to_string()));
}
// Default to apps if nothing enabled
if set.is_empty() {
@@ -170,6 +171,7 @@ impl ProviderFilter {
}
/// Parse query for prefix syntax
/// Prefixes map to Plugin(type_id) for plugin providers
pub fn parse_query(query: &str) -> ParsedQuery {
let trimmed = query.trim_start();
@@ -197,37 +199,57 @@ impl ProviderFilter {
}
}
// Check for prefix patterns (with trailing space)
let prefixes = [
// Core provider prefixes
let core_prefixes: &[(&str, ProviderType)] = &[
(":app ", ProviderType::Application),
(":apps ", ProviderType::Application),
(":bm ", ProviderType::Bookmarks),
(":bookmark ", ProviderType::Bookmarks),
(":bookmarks ", ProviderType::Bookmarks),
(":calc ", ProviderType::Calculator),
(":calculator ", ProviderType::Calculator),
(":clip ", ProviderType::Clipboard),
(":clipboard ", ProviderType::Clipboard),
(":cmd ", ProviderType::Command),
(":command ", ProviderType::Command),
(":emoji ", ProviderType::Emoji),
(":emojis ", ProviderType::Emoji),
(":file ", ProviderType::Files),
(":files ", ProviderType::Files),
(":find ", ProviderType::Files),
(":script ", ProviderType::Scripts),
(":scripts ", ProviderType::Scripts),
(":ssh ", ProviderType::Ssh),
(":sys ", ProviderType::System),
(":system ", ProviderType::System),
(":power ", ProviderType::System),
(":uuctl ", ProviderType::Uuctl),
(":web ", ProviderType::WebSearch),
(":search ", ProviderType::WebSearch),
];
for (prefix_str, provider) in prefixes {
// Plugin provider prefixes - mapped to Plugin(type_id)
let plugin_prefixes: &[(&str, &str)] = &[
(":bm ", "bookmarks"),
(":bookmark ", "bookmarks"),
(":bookmarks ", "bookmarks"),
(":calc ", "calc"),
(":calculator ", "calc"),
(":clip ", "clipboard"),
(":clipboard ", "clipboard"),
(":emoji ", "emoji"),
(":emojis ", "emoji"),
(":file ", "filesearch"),
(":files ", "filesearch"),
(":find ", "filesearch"),
(":script ", "scripts"),
(":scripts ", "scripts"),
(":ssh ", "ssh"),
(":sys ", "system"),
(":system ", "system"),
(":power ", "system"),
(":uuctl ", "uuctl"),
(":systemd ", "uuctl"),
(":web ", "websearch"),
(":search ", "websearch"),
];
// Check core prefixes
for (prefix_str, provider) in core_prefixes {
if let Some(rest) = trimmed.strip_prefix(prefix_str) {
#[cfg(feature = "dev-logging")]
debug!("[Filter] parse_query({:?}) -> prefix={:?}, query={:?}", query, provider, rest);
return ParsedQuery {
prefix: Some(provider.clone()),
tag_filter: None,
query: rest.to_string(),
};
}
}
// Check plugin prefixes
for (prefix_str, type_id) in plugin_prefixes {
if let Some(rest) = trimmed.strip_prefix(prefix_str) {
let provider = ProviderType::Plugin(type_id.to_string());
#[cfg(feature = "dev-logging")]
debug!("[Filter] parse_query({:?}) -> prefix={:?}, query={:?}", query, provider, rest);
return ParsedQuery {
@@ -238,37 +260,54 @@ impl ProviderFilter {
}
}
// Handle prefix without trailing space (still typing)
let partial_prefixes = [
// Handle partial prefixes (still typing)
let partial_core: &[(&str, ProviderType)] = &[
(":app", ProviderType::Application),
(":apps", ProviderType::Application),
(":bm", ProviderType::Bookmarks),
(":bookmark", ProviderType::Bookmarks),
(":bookmarks", ProviderType::Bookmarks),
(":calc", ProviderType::Calculator),
(":calculator", ProviderType::Calculator),
(":clip", ProviderType::Clipboard),
(":clipboard", ProviderType::Clipboard),
(":cmd", ProviderType::Command),
(":command", ProviderType::Command),
(":emoji", ProviderType::Emoji),
(":emojis", ProviderType::Emoji),
(":file", ProviderType::Files),
(":files", ProviderType::Files),
(":find", ProviderType::Files),
(":script", ProviderType::Scripts),
(":scripts", ProviderType::Scripts),
(":ssh", ProviderType::Ssh),
(":sys", ProviderType::System),
(":system", ProviderType::System),
(":power", ProviderType::System),
(":uuctl", ProviderType::Uuctl),
(":web", ProviderType::WebSearch),
(":search", ProviderType::WebSearch),
];
for (prefix_str, provider) in partial_prefixes {
if trimmed == prefix_str {
let partial_plugin: &[(&str, &str)] = &[
(":bm", "bookmarks"),
(":bookmark", "bookmarks"),
(":bookmarks", "bookmarks"),
(":calc", "calc"),
(":calculator", "calc"),
(":clip", "clipboard"),
(":clipboard", "clipboard"),
(":emoji", "emoji"),
(":emojis", "emoji"),
(":file", "filesearch"),
(":files", "filesearch"),
(":find", "filesearch"),
(":script", "scripts"),
(":scripts", "scripts"),
(":ssh", "ssh"),
(":sys", "system"),
(":system", "system"),
(":power", "system"),
(":uuctl", "uuctl"),
(":systemd", "uuctl"),
(":web", "websearch"),
(":search", "websearch"),
];
for (prefix_str, provider) in partial_core {
if trimmed == *prefix_str {
#[cfg(feature = "dev-logging")]
debug!("[Filter] parse_query({:?}) -> partial prefix {:?}", query, provider);
return ParsedQuery {
prefix: Some(provider.clone()),
tag_filter: None,
query: String::new(),
};
}
}
for (prefix_str, type_id) in partial_plugin {
if trimmed == *prefix_str {
let provider = ProviderType::Plugin(type_id.to_string());
#[cfg(feature = "dev-logging")]
debug!("[Filter] parse_query({:?}) -> partial prefix {:?}", query, provider);
return ParsedQuery {
@@ -296,22 +335,9 @@ impl ProviderFilter {
let mut providers: Vec<_> = self.enabled.iter().cloned().collect();
providers.sort_by_key(|p| match p {
ProviderType::Application => 0,
ProviderType::Bookmarks => 1,
ProviderType::Calculator => 2,
ProviderType::Clipboard => 3,
ProviderType::Command => 4,
ProviderType::Dmenu => 5,
ProviderType::Emoji => 6,
ProviderType::Files => 7,
ProviderType::MediaPlayer => 8,
ProviderType::Pomodoro => 9,
ProviderType::Scripts => 10,
ProviderType::Ssh => 11,
ProviderType::System => 12,
ProviderType::Uuctl => 13,
ProviderType::Weather => 14,
ProviderType::WebSearch => 15,
ProviderType::Plugin(_) => 100, // Plugin providers sort last
ProviderType::Command => 1,
ProviderType::Dmenu => 2,
ProviderType::Plugin(_) => 100, // Plugin providers sort after core
});
providers
}
@@ -321,21 +347,8 @@ impl ProviderFilter {
if let Some(ref prefix) = self.active_prefix {
return match prefix {
ProviderType::Application => "Apps",
ProviderType::Bookmarks => "Bookmarks",
ProviderType::Calculator => "Calc",
ProviderType::Clipboard => "Clipboard",
ProviderType::Command => "Commands",
ProviderType::Dmenu => "dmenu",
ProviderType::Emoji => "Emoji",
ProviderType::Files => "Files",
ProviderType::MediaPlayer => "Media",
ProviderType::Pomodoro => "Pomodoro",
ProviderType::Scripts => "Scripts",
ProviderType::Ssh => "SSH",
ProviderType::System => "System",
ProviderType::Uuctl => "uuctl",
ProviderType::Weather => "Weather",
ProviderType::WebSearch => "Web",
ProviderType::Plugin(_) => "Plugin",
};
}
@@ -344,21 +357,8 @@ impl ProviderFilter {
if enabled.len() == 1 {
match &enabled[0] {
ProviderType::Application => "Apps",
ProviderType::Bookmarks => "Bookmarks",
ProviderType::Calculator => "Calc",
ProviderType::Clipboard => "Clipboard",
ProviderType::Command => "Commands",
ProviderType::Dmenu => "dmenu",
ProviderType::Emoji => "Emoji",
ProviderType::Files => "Files",
ProviderType::MediaPlayer => "Media",
ProviderType::Pomodoro => "Pomodoro",
ProviderType::Scripts => "Scripts",
ProviderType::Ssh => "SSH",
ProviderType::System => "System",
ProviderType::Uuctl => "uuctl",
ProviderType::Weather => "Weather",
ProviderType::WebSearch => "Web",
ProviderType::Plugin(_) => "Plugin",
}
} else {
@@ -392,6 +392,13 @@ mod tests {
assert_eq!(result.query, "");
}
#[test]
fn test_parse_query_plugin_prefix() {
let result = ProviderFilter::parse_query(":calc 5+3");
assert_eq!(result.prefix, Some(ProviderType::Plugin("calc".to_string())));
assert_eq!(result.query, "5+3");
}
#[test]
fn test_toggle_ensures_one_enabled() {
let mut filter = ProviderFilter::apps_only();

View File

@@ -44,27 +44,17 @@ pub struct LaunchItem {
/// Provider type identifier for filtering and badge display
///
/// Note: Plugin is a special case that stores a type_id string
/// for custom plugin-defined provider types.
/// Core types are built-in providers. All native plugins use Plugin(type_id).
/// This keeps the core app free of plugin-specific knowledge.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ProviderType {
/// Built-in: Desktop applications from XDG directories
Application,
Bookmarks,
Calculator,
Clipboard,
/// Built-in: Shell commands from PATH
Command,
/// Built-in: Pipe-based input (dmenu compatibility)
Dmenu,
Emoji,
Files,
MediaPlayer,
Pomodoro,
Scripts,
Ssh,
System,
Uuctl,
Weather,
WebSearch,
/// Plugin-defined provider type with custom type_id
/// Plugin-defined provider type with its type_id (e.g., "calc", "weather", "emoji")
Plugin(String),
}
@@ -73,27 +63,11 @@ impl std::str::FromStr for ProviderType {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
// Core built-in providers
"app" | "apps" | "application" | "applications" => Ok(ProviderType::Application),
"bookmark" | "bookmarks" | "bm" => Ok(ProviderType::Bookmarks),
"calc" | "calculator" => Ok(ProviderType::Calculator),
"clip" | "clipboard" => Ok(ProviderType::Clipboard),
"cmd" | "command" | "commands" => Ok(ProviderType::Command),
"dmenu" => Ok(ProviderType::Dmenu),
"emoji" | "emojis" => Ok(ProviderType::Emoji),
"file" | "files" | "find" | "filesearch" => Ok(ProviderType::Files),
"media" | "mpris" | "player" => Ok(ProviderType::MediaPlayer),
"pomo" | "pomodoro" | "timer" => Ok(ProviderType::Pomodoro),
"script" | "scripts" => Ok(ProviderType::Scripts),
"ssh" => Ok(ProviderType::Ssh),
"sys" | "system" | "power" => Ok(ProviderType::System),
"uuctl" | "systemd" => Ok(ProviderType::Uuctl),
"weather" => Ok(ProviderType::Weather),
"web" | "websearch" | "search" => Ok(ProviderType::WebSearch),
// Plugin types are prefixed with "plugin:" (e.g., "plugin:github-repos")
other if other.starts_with("plugin:") => {
Ok(ProviderType::Plugin(other[7..].to_string()))
}
// Unknown types become plugin types
// Everything else is a plugin
other => Ok(ProviderType::Plugin(other.to_string())),
}
}
@@ -103,21 +77,8 @@ impl std::fmt::Display for ProviderType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProviderType::Application => write!(f, "app"),
ProviderType::Bookmarks => write!(f, "bookmark"),
ProviderType::Calculator => write!(f, "calc"),
ProviderType::Clipboard => write!(f, "clip"),
ProviderType::Command => write!(f, "cmd"),
ProviderType::Dmenu => write!(f, "dmenu"),
ProviderType::Emoji => write!(f, "emoji"),
ProviderType::Files => write!(f, "file"),
ProviderType::MediaPlayer => write!(f, "media"),
ProviderType::Pomodoro => write!(f, "pomo"),
ProviderType::Scripts => write!(f, "script"),
ProviderType::Ssh => write!(f, "ssh"),
ProviderType::System => write!(f, "sys"),
ProviderType::Uuctl => write!(f, "uuctl"),
ProviderType::Weather => write!(f, "weather"),
ProviderType::WebSearch => write!(f, "web"),
ProviderType::Plugin(type_id) => write!(f, "{}", type_id),
}
}
@@ -146,19 +107,14 @@ pub struct ProviderManager {
matcher: SkimMatcherV2,
}
/// Known dynamic provider type IDs (need per-query evaluation)
const DYNAMIC_TYPE_IDS: &[&str] = &["calc", "websearch", "filesearch"];
/// Known widget provider type IDs (appear at top of results)
const WIDGET_TYPE_IDS: &[&str] = &["weather", "media", "pomodoro"];
impl ProviderManager {
/// Create a new ProviderManager with native plugins
///
/// Native plugins are loaded from /usr/lib/owlry/plugins/ and categorized into:
/// - Static providers (added to providers vec)
/// - Dynamic providers (queried per-keystroke: calculator, websearch, filesearch)
/// - Widget providers (shown at top: weather, media, pomodoro)
/// Native plugins are loaded from /usr/lib/owlry/plugins/ and categorized based on
/// their declared ProviderKind and ProviderPosition:
/// - Static providers with Normal position (added to providers vec)
/// - Dynamic providers (queried per-keystroke, declared via ProviderKind::Dynamic)
/// - Widget providers (shown at top, declared via ProviderPosition::Widget)
pub fn with_native_plugins(native_providers: Vec<NativeProvider>) -> Self {
let mut manager = Self {
providers: Vec::new(),
@@ -180,17 +136,20 @@ impl ProviderManager {
manager.providers.push(Box::new(ApplicationProvider::new()));
manager.providers.push(Box::new(CommandProvider::new()));
// Categorize native plugins
// Categorize native plugins based on their declared ProviderKind and ProviderPosition
for provider in native_providers {
let type_id = provider.type_id();
if DYNAMIC_TYPE_IDS.contains(&type_id) {
if provider.is_dynamic() {
// Dynamic providers declare ProviderKind::Dynamic
info!("Registered dynamic provider: {} ({})", provider.name(), type_id);
manager.dynamic_providers.push(provider);
} else if WIDGET_TYPE_IDS.contains(&type_id) {
} else if provider.is_widget() {
// Widgets declare ProviderPosition::Widget
info!("Registered widget provider: {} ({})", provider.name(), type_id);
manager.widget_providers.push(provider);
} else {
// Static providers with Normal position
info!("Registered static provider: {} ({})", provider.name(), type_id);
manager.providers.push(Box::new(provider));
}
@@ -393,19 +352,11 @@ impl ProviderManager {
// 1. No specific filter prefix is active
// 2. Query is empty (user hasn't started searching)
// This keeps widgets visible on launch but hides them during active search
// Widgets are always visible regardless of filter settings (they declare position via API)
if filter.active_prefix().is_none() && query.is_empty() {
// Widget priority scores based on type
// Widget priority comes from plugin-declared priority field
for provider in &self.widget_providers {
// Skip if this provider type is filtered out
if !filter.is_active(provider.provider_type()) {
continue;
}
let base_score = match provider.type_id() {
"weather" => 12000,
"pomodoro" => 11500,
"media" => 11000,
_ => 10500,
};
let base_score = provider.priority() as i64;
for (idx, item) in provider.items().iter().enumerate() {
results.push((item.clone(), base_score - idx as i64));
}
@@ -417,18 +368,14 @@ impl ProviderManager {
// 1. Their specific filter is active (e.g., :file prefix or Files tab selected), OR
// 2. No specific single-mode filter is active (showing all providers)
if !query.is_empty() {
for (provider_idx, provider) in self.dynamic_providers.iter().enumerate() {
for provider in &self.dynamic_providers {
// Skip if this provider type is explicitly filtered out
if !filter.is_active(provider.provider_type()) {
continue;
}
let dynamic_results = provider.query(query);
let base_score = match provider.type_id() {
"calc" => 10000,
"websearch" => 9000,
"filesearch" => 8000,
_ => 7000 - (provider_idx as i64 * 1000),
};
// Priority comes from plugin-declared priority field
let base_score = provider.priority() as i64;
for (idx, item) in dynamic_results.into_iter().enumerate() {
results.push((item, base_score - idx as i64));
}

View File

@@ -9,7 +9,7 @@
use std::sync::{Arc, RwLock};
use log::debug;
use owlry_plugin_api::{PluginItem as ApiPluginItem, ProviderHandle, ProviderInfo, ProviderKind};
use owlry_plugin_api::{PluginItem as ApiPluginItem, ProviderHandle, ProviderInfo, ProviderKind, ProviderPosition};
use super::{LaunchItem, Provider, ProviderType};
use crate::plugins::native_loader::NativePlugin;
@@ -43,13 +43,9 @@ impl NativeProvider {
}
/// Get the ProviderType for this native provider
/// Maps type_id string to the appropriate ProviderType variant
/// All native plugins return Plugin(type_id) - the core has no hardcoded plugin types
fn get_provider_type(&self) -> ProviderType {
// Parse type_id to get the proper ProviderType
// This uses the FromStr impl which maps strings like "clipboard" -> ProviderType::Clipboard
self.info.type_id.as_str().parse().unwrap_or_else(|_| {
ProviderType::Plugin(self.info.type_id.to_string())
})
ProviderType::Plugin(self.info.type_id.to_string())
}
/// Convert a plugin API item to a core LaunchItem
@@ -109,6 +105,17 @@ impl NativeProvider {
self.info.type_id.as_str()
}
/// Check if this is a widget provider (appears at top of results)
pub fn is_widget(&self) -> bool {
self.info.position == ProviderPosition::Widget
}
/// Get the provider's priority for result ordering
/// Higher values appear first in results
pub fn priority(&self) -> i32 {
self.info.priority
}
/// Execute an action command on the provider
/// Uses query with "!" prefix to trigger action handling in the plugin
pub fn execute_action(&self, action: &str) {

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#e0e0e0">
<path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
</svg>

Before

Width:  |  Height:  |  Size: 188 B

After

Width:  |  Height:  |  Size: 183 B

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
viewBox="0 0 30 30" fill="#e0e0e0" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path d="M3.89,17.6c0-0.99,0.31-1.88,0.93-2.65s1.41-1.27,2.38-1.49c0.26-1.17,0.85-2.14,1.78-2.88c0.93-0.75,2-1.12,3.22-1.12
c1.18,0,2.24,0.36,3.16,1.09c0.93,0.73,1.53,1.66,1.8,2.8h0.27c1.18,0,2.18,0.41,3.01,1.24s1.25,1.83,1.25,3
c0,1.18-0.42,2.18-1.25,3.01s-1.83,1.25-3.01,1.25H8.16c-0.58,0-1.13-0.11-1.65-0.34S5.52,21,5.14,20.62

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
viewBox="0 0 30 30" fill="#e0e0e0" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path d="M1.56,16.9c0,0.9,0.22,1.73,0.66,2.49s1.04,1.36,1.8,1.8c0.76,0.44,1.58,0.66,2.47,0.66h10.83c0.89,0,1.72-0.22,2.48-0.66
c0.76-0.44,1.37-1.04,1.81-1.8c0.44-0.76,0.67-1.59,0.67-2.49c0-0.66-0.14-1.33-0.42-2C22.62,13.98,23,12.87,23,11.6
c0-0.71-0.14-1.39-0.41-2.04c-0.27-0.65-0.65-1.2-1.12-1.67C21,7.42,20.45,7.04,19.8,6.77c-0.65-0.28-1.33-0.41-2.04-0.41

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
viewBox="0 0 30 30" fill="#e0e0e0" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path d="M4.37,14.62c0-0.24,0.08-0.45,0.25-0.62c0.17-0.16,0.38-0.24,0.6-0.24h2.04c0.23,0,0.42,0.08,0.58,0.25
c0.15,0.17,0.23,0.37,0.23,0.61S8,15.06,7.85,15.23c-0.15,0.17-0.35,0.25-0.58,0.25H5.23c-0.23,0-0.43-0.08-0.6-0.25
C4.46,15.06,4.37,14.86,4.37,14.62z M7.23,21.55c0-0.23,0.08-0.43,0.23-0.61l1.47-1.43c0.15-0.16,0.35-0.23,0.59-0.23

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
viewBox="0 0 30 30" fill="#e0e0e0" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path d="M2.62,21.05c0-0.24,0.08-0.45,0.25-0.61c0.17-0.16,0.38-0.24,0.63-0.24h18.67c0.25,0,0.45,0.08,0.61,0.24
c0.16,0.16,0.24,0.36,0.24,0.61c0,0.23-0.08,0.43-0.25,0.58c-0.17,0.16-0.37,0.23-0.6,0.23H3.5c-0.25,0-0.46-0.08-0.63-0.23
C2.7,21.47,2.62,21.28,2.62,21.05z M5.24,17.91c0-0.24,0.09-0.44,0.26-0.6c0.15-0.15,0.35-0.23,0.59-0.23h18.67

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
viewBox="0 0 30 30" fill="#e0e0e0" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path d="M7.91,14.48c0-0.96,0.19-1.87,0.56-2.75s0.88-1.63,1.51-2.26c0.63-0.63,1.39-1.14,2.27-1.52c0.88-0.38,1.8-0.57,2.75-0.57
h1.14c0.16,0.04,0.23,0.14,0.23,0.28l0.05,0.88c0.04,1.27,0.49,2.35,1.37,3.24c0.88,0.89,1.94,1.37,3.19,1.42l0.82,0.07
c0.16,0,0.24,0.08,0.24,0.23v0.98c0.01,1.28-0.3,2.47-0.93,3.56c-0.63,1.09-1.48,1.95-2.57,2.59c-1.08,0.63-2.27,0.95-3.55,0.95

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
viewBox="0 0 30 30" fill="#e0e0e0" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path d="M4.64,16.91c0-1.15,0.36-2.17,1.08-3.07c0.72-0.9,1.63-1.47,2.73-1.73c0.31-1.36,1.02-2.48,2.11-3.36s2.34-1.31,3.75-1.31
c1.38,0,2.6,0.43,3.68,1.28c1.08,0.85,1.78,1.95,2.1,3.29h0.32c0.89,0,1.72,0.22,2.48,0.65s1.37,1.03,1.81,1.78
c0.44,0.75,0.67,1.58,0.67,2.47c0,0.88-0.21,1.69-0.63,2.44c-0.42,0.75-1,1.35-1.73,1.8c-0.73,0.45-1.53,0.69-2.4,0.71

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
viewBox="0 0 30 30" fill="#e0e0e0" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path d="M4.64,16.95c0-1.16,0.35-2.18,1.06-3.08s1.62-1.48,2.74-1.76c0.31-1.36,1.01-2.48,2.1-3.36s2.34-1.31,3.75-1.31
c1.38,0,2.6,0.43,3.68,1.28c1.08,0.85,1.78,1.95,2.1,3.29h0.32c0.89,0,1.72,0.22,2.48,0.66c0.76,0.44,1.37,1.04,1.81,1.8
c0.44,0.76,0.67,1.59,0.67,2.48c0,1.32-0.46,2.47-1.39,3.42c-0.92,0.96-2.05,1.46-3.38,1.5c-0.13,0-0.2-0.06-0.2-0.17v-1.33

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
viewBox="0 0 30 30" fill="#e0e0e0" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path d="M9.91,19.56c0-0.85,0.2-1.64,0.59-2.38s0.94-1.35,1.65-1.84V5.42c0-0.8,0.27-1.48,0.82-2.03S14.2,2.55,15,2.55
c0.81,0,1.49,0.28,2.04,0.83c0.55,0.56,0.83,1.23,0.83,2.03v9.92c0.71,0.49,1.25,1.11,1.64,1.84s0.58,1.53,0.58,2.38
c0,0.92-0.23,1.78-0.68,2.56s-1.07,1.4-1.85,1.85s-1.63,0.68-2.56,0.68c-0.92,0-1.77-0.23-2.55-0.68s-1.4-1.07-1.86-1.85

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
viewBox="0 0 30 30" fill="#e0e0e0" style="enable-background:new 0 0 30 30;" xml:space="preserve">
<path d="M4.63,16.91c0,1.11,0.33,2.1,0.99,2.97s1.52,1.47,2.58,1.79l-0.66,1.68c-0.03,0.14,0.02,0.22,0.14,0.22h2.13l-0.98,4.3h0.28
l3.92-5.75c0.04-0.04,0.04-0.09,0.01-0.14c-0.03-0.05-0.08-0.07-0.15-0.07h-2.18l2.48-4.64c0.07-0.14,0.02-0.22-0.14-0.22h-2.94
c-0.09,0-0.17,0.05-0.23,0.15l-1.07,2.87c-0.71-0.18-1.3-0.57-1.77-1.16c-0.47-0.59-0.7-1.26-0.7-2.01c0-0.83,0.28-1.55,0.85-2.17

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -315,48 +315,54 @@ impl MainWindow {
}
/// Get display label for a provider tab
/// Core types have fixed labels; plugins derive labels from type_id
fn provider_tab_label(provider: &ProviderType) -> &'static str {
match provider {
ProviderType::Application => "Apps",
ProviderType::Bookmarks => "Bookmarks",
ProviderType::Calculator => "Calc",
ProviderType::Clipboard => "Clip",
ProviderType::Command => "Cmds",
ProviderType::Dmenu => "Dmenu",
ProviderType::Emoji => "Emoji",
ProviderType::Files => "Files",
ProviderType::MediaPlayer => "Media",
ProviderType::Pomodoro => "Pomo",
ProviderType::Scripts => "Scripts",
ProviderType::Ssh => "SSH",
ProviderType::System => "System",
ProviderType::Uuctl => "uuctl",
ProviderType::Weather => "Weather",
ProviderType::WebSearch => "Web",
ProviderType::Plugin(_) => "Plugin",
ProviderType::Plugin(type_id) => match type_id.as_str() {
"bookmarks" => "Bookmarks",
"calc" => "Calc",
"clipboard" => "Clip",
"emoji" => "Emoji",
"filesearch" => "Files",
"media" => "Media",
"pomodoro" => "Pomo",
"scripts" => "Scripts",
"ssh" => "SSH",
"system" => "System",
"uuctl" => "uuctl",
"weather" => "Weather",
"websearch" => "Web",
_ => "Plugin",
},
}
}
/// Get CSS class for a provider
/// Core types have fixed CSS classes; plugins derive from type_id
fn provider_css_class(provider: &ProviderType) -> &'static str {
match provider {
ProviderType::Application => "owlry-filter-app",
ProviderType::Bookmarks => "owlry-filter-bookmark",
ProviderType::Calculator => "owlry-filter-calc",
ProviderType::Clipboard => "owlry-filter-clip",
ProviderType::Command => "owlry-filter-cmd",
ProviderType::Dmenu => "owlry-filter-dmenu",
ProviderType::Emoji => "owlry-filter-emoji",
ProviderType::Files => "owlry-filter-file",
ProviderType::MediaPlayer => "owlry-filter-media",
ProviderType::Pomodoro => "owlry-filter-pomodoro",
ProviderType::Scripts => "owlry-filter-script",
ProviderType::Ssh => "owlry-filter-ssh",
ProviderType::System => "owlry-filter-sys",
ProviderType::Uuctl => "owlry-filter-uuctl",
ProviderType::Weather => "owlry-filter-weather",
ProviderType::WebSearch => "owlry-filter-web",
ProviderType::Plugin(_) => "owlry-filter-plugin",
ProviderType::Plugin(type_id) => match type_id.as_str() {
"bookmarks" => "owlry-filter-bookmark",
"calc" => "owlry-filter-calc",
"clipboard" => "owlry-filter-clip",
"emoji" => "owlry-filter-emoji",
"filesearch" => "owlry-filter-file",
"media" => "owlry-filter-media",
"pomodoro" => "owlry-filter-pomodoro",
"scripts" => "owlry-filter-script",
"ssh" => "owlry-filter-ssh",
"system" => "owlry-filter-sys",
"uuctl" => "owlry-filter-uuctl",
"weather" => "owlry-filter-weather",
"websearch" => "owlry-filter-web",
_ => "owlry-filter-plugin",
},
}
}
@@ -366,22 +372,24 @@ impl MainWindow {
.iter()
.map(|p| match p {
ProviderType::Application => "applications",
ProviderType::Bookmarks => "bookmarks",
ProviderType::Calculator => "calculator",
ProviderType::Clipboard => "clipboard",
ProviderType::Command => "commands",
ProviderType::Dmenu => "options",
ProviderType::Emoji => "emoji",
ProviderType::Files => "files",
ProviderType::MediaPlayer => "media",
ProviderType::Pomodoro => "pomodoro",
ProviderType::Scripts => "scripts",
ProviderType::Ssh => "SSH hosts",
ProviderType::System => "system",
ProviderType::Uuctl => "uuctl units",
ProviderType::Weather => "weather",
ProviderType::WebSearch => "web",
ProviderType::Plugin(_) => "plugins",
ProviderType::Plugin(type_id) => match type_id.as_str() {
"bookmarks" => "bookmarks",
"calc" => "calculator",
"clipboard" => "clipboard",
"emoji" => "emoji",
"filesearch" => "files",
"media" => "media",
"pomodoro" => "pomodoro",
"scripts" => "scripts",
"ssh" => "SSH hosts",
"system" => "system",
"uuctl" => "uuctl units",
"weather" => "weather",
"websearch" => "web",
_ => "plugins",
},
})
.collect();
@@ -612,22 +620,24 @@ impl MainWindow {
if let Some(ref prefix) = parsed.prefix {
let prefix_name = match prefix {
ProviderType::Application => "applications",
ProviderType::Bookmarks => "bookmarks",
ProviderType::Calculator => "calculator",
ProviderType::Clipboard => "clipboard",
ProviderType::Command => "commands",
ProviderType::Dmenu => "options",
ProviderType::Emoji => "emoji",
ProviderType::Files => "files",
ProviderType::MediaPlayer => "media",
ProviderType::Pomodoro => "pomodoro",
ProviderType::Scripts => "scripts",
ProviderType::Ssh => "SSH hosts",
ProviderType::System => "system",
ProviderType::Uuctl => "uuctl units",
ProviderType::Weather => "weather",
ProviderType::WebSearch => "web",
ProviderType::Plugin(_) => "plugins",
ProviderType::Plugin(type_id) => match type_id.as_str() {
"bookmarks" => "bookmarks",
"calc" => "calculator",
"clipboard" => "clipboard",
"emoji" => "emoji",
"filesearch" => "files",
"media" => "media",
"pomodoro" => "pomodoro",
"scripts" => "scripts",
"ssh" => "SSH hosts",
"system" => "system",
"uuctl" => "uuctl units",
"weather" => "weather",
"websearch" => "web",
_ => "plugins",
},
};
search_entry_for_change
.set_placeholder_text(Some(&format!("Search {}...", prefix_name)));

View File

@@ -56,6 +56,10 @@ impl ResultRow {
let img = Image::from_resource(icon_path);
img.set_pixel_size(32);
img.add_css_class("owlry-result-icon");
// SVG icons from resources should be treated as symbolic for color inheritance
if icon_path.ends_with(".svg") {
img.add_css_class("owlry-symbolic-icon");
}
img.upcast()
} else if icon_path.starts_with('/') {
// Absolute file path
@@ -68,27 +72,19 @@ impl ResultRow {
let img = Image::from_icon_name(icon_path);
img.set_pixel_size(32);
img.add_css_class("owlry-result-icon");
// Add symbolic class for icons ending with "-symbolic"
if icon_path.ends_with("-symbolic") {
img.add_css_class("owlry-symbolic-icon");
}
img.upcast()
}
} else {
// Default icon based on provider type (using symbolic icons for theme color support)
// Default icon based on provider type (only core types, plugins should provide icons)
let default_icon = match &item.provider {
crate::providers::ProviderType::Application => "application-x-executable-symbolic",
crate::providers::ProviderType::Bookmarks => "user-bookmarks-symbolic",
crate::providers::ProviderType::Calculator => "accessories-calculator-symbolic",
crate::providers::ProviderType::Clipboard => "edit-paste-symbolic",
crate::providers::ProviderType::Command => "utilities-terminal-symbolic",
crate::providers::ProviderType::Dmenu => "view-list-symbolic",
crate::providers::ProviderType::Emoji => "face-smile-symbolic",
crate::providers::ProviderType::Files => "folder-symbolic",
crate::providers::ProviderType::Scripts => "application-x-executable-symbolic",
crate::providers::ProviderType::Ssh => "network-server-symbolic",
crate::providers::ProviderType::System => "system-shutdown-symbolic",
crate::providers::ProviderType::Uuctl => "system-run-symbolic",
crate::providers::ProviderType::WebSearch => "web-browser-symbolic",
crate::providers::ProviderType::Weather => "weather-clear-symbolic",
crate::providers::ProviderType::MediaPlayer => "media-playback-start-symbolic",
crate::providers::ProviderType::Pomodoro => "alarm-symbolic",
// Plugins should provide their own icon; fallback to generic addon icon
crate::providers::ProviderType::Plugin(_) => "application-x-addon-symbolic",
};
let img = Image::from_icon_name(default_icon);

View File

@@ -49,9 +49,8 @@ font_size = 14
border_radius = 12
# Theme name - loads ~/.config/owlry/themes/{name}.css
# Built-in: owl, catppuccin-mocha, dracula, gruvbox-dark, nord,
# one-dark, rose-pine, solarized-dark, tokyo-night
# Or leave unset for GTK default
# Built-in: owl
# Or leave unset/empty for GTK default
# theme = "owl"
# Color overrides (applied on top of theme)
@@ -81,10 +80,12 @@ disabled = []
# disabled = ["weather", "media"] # Disable widget plugins
# ═══════════════════════════════════════════════════════════════════════
# CORE PROVIDERS
# PROVIDERS
# ═══════════════════════════════════════════════════════════════════════
#
# These are built into the core binary, not plugins.
# Enable/disable providers and configure their settings.
# Core providers (applications, commands) are built into the binary.
# Plugin providers require their .so to be installed.
[providers]
# Core providers (always available)
@@ -96,36 +97,25 @@ commands = true # Executables from $PATH
frecency = true
frecency_weight = 0.3 # 0.0 = disabled, 1.0 = strong boost
# ═══════════════════════════════════════════════════════════════════════
# PLUGIN SETTINGS
# ═══════════════════════════════════════════════════════════════════════
#
# Settings for specific plugins. Only applies if the plugin is installed.
# ─────────────────────────────────────────────────────────────────────────
# 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)
# Web Search plugin
[providers.websearch]
search_engine = "duckduckgo"
# ─────────────────────────────────────────────────────────────────────────
# Plugin settings
# ─────────────────────────────────────────────────────────────────────────
# Web search engine
# Options: google, duckduckgo, bing, startpage, searxng, brave, ecosia
# Custom URL: "https://search.example.com/?q={query}"
# File Search plugin
[providers.filesearch]
max_results = 50
# search_paths = ["/home", "/etc"] # Custom paths (default: $HOME)
# Weather widget plugin
[providers.weather]
enabled = true
provider = "wttr.in" # wttr.in (default), openweathermap, open-meteo
location = "" # City name, "lat,lon", or empty for auto-detect
# api_key = "" # Required for OpenWeatherMap
# Pomodoro timer plugin
[providers.pomodoro]
enabled = true
work_mins = 25 # Work session duration
break_mins = 5 # Break duration
# Media controls plugin
[providers.media]
enabled = true
# Or custom URL: "https://search.example.com/?q={query}"
search_engine = "duckduckgo"

View File

@@ -63,6 +63,14 @@ install-local:
echo "Cleaning up stale files..."
# Remove runtime files that may have ended up in plugins dir (from old installs)
sudo rm -f /usr/lib/owlry/plugins/libowlry_lua.so /usr/lib/owlry/plugins/libowlry_rune.so
# Remove old short-named plugin files (from old AUR packages before naming standardization)
sudo rm -f /usr/lib/owlry/plugins/libbookmarks.so /usr/lib/owlry/plugins/libcalculator.so \
/usr/lib/owlry/plugins/libclipboard.so /usr/lib/owlry/plugins/libemoji.so \
/usr/lib/owlry/plugins/libfilesearch.so /usr/lib/owlry/plugins/libmedia.so \
/usr/lib/owlry/plugins/libpomodoro.so /usr/lib/owlry/plugins/libscripts.so \
/usr/lib/owlry/plugins/libssh.so /usr/lib/owlry/plugins/libsystem.so \
/usr/lib/owlry/plugins/libsystemd.so /usr/lib/owlry/plugins/libweather.so \
/usr/lib/owlry/plugins/libwebsearch.so
echo "Installing core binary..."
sudo install -Dm755 target/release/owlry /usr/bin/owlry