Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35a0f580c3 | |||
| 7ed36c58c2 | |||
| 7cccd3b512 | |||
| 9f6d0c5935 | |||
| 026a232e0c | |||
| 1557119448 | |||
| b814d07382 | |||
| 0dead603ec | |||
| c1eb5ae2eb | |||
| 07847c76d8 | |||
| 2dfce67f3b | |||
| b1198f4600 | |||
| e6776b803c | |||
| 6e2d60466b | |||
| 8c1cf88474 | |||
| ecaaae39e3 | |||
| 96e9b09a31 | |||
| e053f7d5d5 | |||
| b1f11c076b | |||
| 2d7fb33f30 | |||
| 3b1ff03ff8 | |||
| e1fb63d6c4 | |||
| 33e2f9cb5e | |||
| 6b21602a07 | |||
| 4516865c21 | |||
| 4fbc7fc4c9 |
717
Cargo.lock
generated
46
README.md
@@ -32,10 +32,10 @@ yay -S owlry
|
||||
yay -S owlry-plugin-calculator owlry-plugin-weather
|
||||
|
||||
# Or install bundles:
|
||||
yay -S owlry-essentials # calculator, system, ssh, scripts, bookmarks
|
||||
yay -S owlry-widgets # weather, media, pomodoro
|
||||
yay -S owlry-tools # clipboard, emoji, websearch, filesearch, systemd
|
||||
yay -S owlry-full # everything
|
||||
yay -S owlry-meta-essentials # calculator, system, ssh, scripts, bookmarks
|
||||
yay -S owlry-meta-widgets # weather, media, pomodoro
|
||||
yay -S owlry-meta-tools # clipboard, emoji, websearch, filesearch, systemd
|
||||
yay -S owlry-meta-full # everything
|
||||
|
||||
# For custom Lua/Rune plugins
|
||||
yay -S owlry-lua # Lua 5.4 runtime
|
||||
@@ -53,7 +53,7 @@ yay -S owlry-rune # Rune runtime
|
||||
| `owlry-plugin-clipboard` | History via cliphist |
|
||||
| `owlry-plugin-emoji` | 400+ searchable emoji |
|
||||
| `owlry-plugin-scripts` | User scripts |
|
||||
| `owlry-plugin-bookmarks` | Chrome, Brave, Edge bookmarks |
|
||||
| `owlry-plugin-bookmarks` | Firefox, Chrome, Brave, Edge bookmarks |
|
||||
| `owlry-plugin-websearch` | Web search (`? query`) |
|
||||
| `owlry-plugin-filesearch` | File search (`/ filename`) |
|
||||
| `owlry-plugin-systemd` | User services with actions |
|
||||
@@ -158,6 +158,21 @@ Owlry follows the [XDG Base Directory Specification](https://specifications.free
|
||||
| `~/.local/share/owlry/scripts/` | User scripts |
|
||||
| `~/.local/share/owlry/frecency.json` | Usage history |
|
||||
|
||||
System locations:
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `/usr/lib/owlry/plugins/*.so` | Installed native plugins |
|
||||
| `/usr/lib/owlry/runtimes/*.so` | Lua/Rune script runtimes |
|
||||
| `/usr/share/doc/owlry/config.example.toml` | Example configuration |
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Copy example config
|
||||
mkdir -p ~/.config/owlry
|
||||
cp /usr/share/doc/owlry/config.example.toml ~/.config/owlry/config.toml
|
||||
```
|
||||
|
||||
### Example Configuration
|
||||
|
||||
```toml
|
||||
@@ -169,8 +184,8 @@ tabs = ["app", "cmd", "uuctl"]
|
||||
# launch_wrapper = "uwsm app --" # Auto-detected
|
||||
|
||||
[appearance]
|
||||
width = 700
|
||||
height = 500
|
||||
width = 850
|
||||
height = 650
|
||||
font_size = 14
|
||||
border_radius = 12
|
||||
# theme = "owl" # Or: catppuccin-mocha, nord, dracula, etc.
|
||||
@@ -178,17 +193,18 @@ border_radius = 12
|
||||
[plugins]
|
||||
disabled = [] # Plugin IDs to disable, e.g., ["emoji", "pomodoro"]
|
||||
|
||||
# Per-plugin configuration (new in 0.4.0)
|
||||
[plugins.weather]
|
||||
provider = "wttr.in" # or: openweathermap, open-meteo
|
||||
location = "Berlin" # city name or "lat,lon"
|
||||
# api_key = "..." # Required for OpenWeatherMap
|
||||
[providers]
|
||||
applications = true # .desktop files
|
||||
commands = true # PATH executables
|
||||
frecency = true # Boost frequently used items
|
||||
frecency_weight = 0.3 # 0.0-1.0
|
||||
|
||||
[plugins.pomodoro]
|
||||
work_mins = 25 # Work session duration
|
||||
break_mins = 5 # Break duration
|
||||
# Web search engine: google, duckduckgo, bing, startpage, brave, ecosia
|
||||
search_engine = "duckduckgo"
|
||||
```
|
||||
|
||||
See `/usr/share/doc/owlry/config.example.toml` for all options with documentation.
|
||||
|
||||
## Plugin System
|
||||
|
||||
Owlry uses a modular plugin architecture. Plugins are loaded from:
|
||||
|
||||
91
ROADMAP.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Owlry Roadmap
|
||||
|
||||
Feature ideas and future development plans for Owlry.
|
||||
|
||||
## High Value, Low Effort
|
||||
|
||||
### Plugin hot-reload
|
||||
Detect `.so` file changes in `/usr/lib/owlry/plugins/` and reload without restarting the launcher. The loader infrastructure already exists.
|
||||
|
||||
### Frecency pruning
|
||||
Add `max_entries` and `max_age_days` config options. Prune old entries on startup to prevent `frecency.json` from growing unbounded.
|
||||
|
||||
### `:recent` prefix
|
||||
Show last N launched items. Data already exists in frecency.json — just needs a provider to surface it.
|
||||
|
||||
### Clipboard images
|
||||
`cliphist` supports images. Extend the clipboard plugin to show image thumbnails in results.
|
||||
|
||||
---
|
||||
|
||||
## Medium Effort, High Value
|
||||
|
||||
### Actions on any result
|
||||
Generalize the submenu system beyond systemd. Every result type gets contextual actions:
|
||||
|
||||
| Provider | Actions |
|
||||
|----------|---------|
|
||||
| Applications | Open, Open in terminal, Show .desktop location |
|
||||
| Files | Open, Open folder, Copy path, Delete |
|
||||
| SSH | Connect, Copy hostname, Edit config |
|
||||
| Bookmarks | Open, Copy URL, Open incognito |
|
||||
| Clipboard | Paste, Delete from history |
|
||||
|
||||
This is the difference between a launcher and a command palette.
|
||||
|
||||
### Plugin settings UI
|
||||
A `:settings` provider that lists installed plugins and their configurable options. Edit values inline, writes to `config.toml`.
|
||||
|
||||
### Result action capture
|
||||
Calculator shows `= 5+3 → 8`. Allow pressing Tab or Ctrl+C to copy the result to clipboard instead of "launching" it. Useful for calculator, file paths, URLs.
|
||||
|
||||
---
|
||||
|
||||
## Bigger Bets
|
||||
|
||||
### Window switcher with live thumbnails
|
||||
A `windows` plugin using Wayland screencopy to show live thumbnails of open windows. Hyprland and Sway expose window lists via IPC. Could replace Alt+Tab.
|
||||
|
||||
### Cross-device bookmark sync
|
||||
Firefox and Chrome sync bookmarks across devices. Parse sync metadata to show "recently added on other devices" or "bookmarks from phone".
|
||||
|
||||
### Natural language commands
|
||||
Parse simple natural language into system commands:
|
||||
|
||||
```
|
||||
"shutdown in 30 minutes" → systemd-run --user --on-active=30m systemctl poweroff
|
||||
"remind me in 1 hour" → notify-send scheduled via at/systemd timer
|
||||
"volume 50%" → wpctl set-volume @DEFAULT_AUDIO_SINK@ 0.5
|
||||
```
|
||||
|
||||
Local pattern matching, no AI/cloud required.
|
||||
|
||||
### Plugin marketplace
|
||||
A curated registry of third-party Lua/Rune plugins with one-command install:
|
||||
|
||||
```bash
|
||||
owlry plugin install github-notifications
|
||||
owlry plugin install todoist
|
||||
owlry plugin install spotify-controls
|
||||
```
|
||||
|
||||
The script runtimes make this viable without recompiling.
|
||||
|
||||
---
|
||||
|
||||
## Technical Debt
|
||||
|
||||
### Replace meval with evalexpr
|
||||
`meval` depends on `nom v1.2.4` which will be rejected by future Rust versions. Migrate calculator plugin and Lua runtime to `evalexpr` v13+.
|
||||
|
||||
### Plugin API backwards compatibility
|
||||
When `API_VERSION` increments, provide a compatibility shim so v3 plugins work with v4 core. Prevents ecosystem fragmentation.
|
||||
|
||||
### Per-plugin configuration
|
||||
Current flat `[providers]` config doesn't scale. Design a `[plugins.weather]`, `[plugins.pomodoro]` structure that plugins can declare and the core validates.
|
||||
|
||||
---
|
||||
|
||||
## Priority
|
||||
|
||||
If we had to pick one: **Actions on any result**. It transforms every provider from "search and launch" to "search and do anything". The ROI is massive.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-lua"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-api"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-bookmarks"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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 {
|
||||
]),
|
||||
};
|
||||
|
||||
state.process_chrome_folder(&folder);
|
||||
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]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-calculator"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-clipboard"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-emoji"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-filesearch"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-media"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-pomodoro"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-scripts"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-ssh"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-system"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-systemd"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-weather"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-websearch"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-rune"
|
||||
version = "0.2.1"
|
||||
version = "0.4.6"
|
||||
edition = "2024"
|
||||
rust-version = "1.90"
|
||||
description = "Rune scripting runtime for owlry plugins"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry"
|
||||
version = "0.4.1"
|
||||
version = "0.4.6"
|
||||
edition = "2024"
|
||||
rust-version = "1.90"
|
||||
description = "A lightweight, owl-themed application launcher for Wayland"
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -271,14 +271,16 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_lua_runtime_not_installed() {
|
||||
// In test environment, runtime shouldn't be installed
|
||||
assert!(!lua_runtime_available());
|
||||
fn test_lua_runtime_check_doesnt_panic() {
|
||||
// Just verify the function runs without panicking
|
||||
// Result depends on whether runtime is installed
|
||||
let _available = lua_runtime_available();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rune_runtime_not_installed() {
|
||||
// In test environment, runtime shouldn't be installed
|
||||
assert!(!rune_runtime_available());
|
||||
fn test_rune_runtime_check_doesnt_panic() {
|
||||
// Just verify the function runs without panicking
|
||||
// Result depends on whether runtime is installed
|
||||
let _available = rune_runtime_available();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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)));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -143,9 +143,10 @@ chmod +x ~/.local/share/owlry/scripts/backup.sh
|
||||
**Prefix:** `:bm`
|
||||
**Package:** `owlry-plugin-bookmarks`
|
||||
|
||||
Browser bookmarks from Chromium-based browsers.
|
||||
Browser bookmarks from Firefox and Chromium-based browsers.
|
||||
|
||||
**Supported browsers:**
|
||||
- Firefox (reads places.sqlite)
|
||||
- Google Chrome
|
||||
- Brave
|
||||
- Microsoft Edge
|
||||
@@ -236,13 +237,7 @@ Current weather displayed at the top of results.
|
||||
- OpenWeatherMap (requires API key)
|
||||
- Open-Meteo (no API key required)
|
||||
|
||||
**Configuration:**
|
||||
```toml
|
||||
[plugins.weather]
|
||||
provider = "wttr.in" # or: openweathermap, open-meteo
|
||||
location = "London" # city name or "lat,lon" (empty for auto-detect)
|
||||
# api_key = "..." # Required for OpenWeatherMap
|
||||
```
|
||||
**Note:** Weather configuration is currently embedded in the plugin. Future versions will support runtime configuration.
|
||||
|
||||
**Features:**
|
||||
- Temperature, condition, humidity, wind speed
|
||||
@@ -274,13 +269,6 @@ MPRIS media player controls.
|
||||
|
||||
Pomodoro timer with work/break cycles.
|
||||
|
||||
**Configuration:**
|
||||
```toml
|
||||
[plugins.pomodoro]
|
||||
work_mins = 25 # Work session duration (default: 25)
|
||||
break_mins = 5 # Break duration (default: 5)
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Configurable work session duration
|
||||
- Configurable break duration
|
||||
@@ -301,17 +289,17 @@ For convenience, plugins are available in bundle meta-packages:
|
||||
|
||||
| Bundle | Plugins |
|
||||
|--------|---------|
|
||||
| `owlry-essentials` | calculator, system, ssh, scripts, bookmarks |
|
||||
| `owlry-widgets` | weather, media, pomodoro |
|
||||
| `owlry-tools` | clipboard, emoji, websearch, filesearch, systemd |
|
||||
| `owlry-full` | All of the above |
|
||||
| `owlry-meta-essentials` | calculator, system, ssh, scripts, bookmarks |
|
||||
| `owlry-meta-widgets` | weather, media, pomodoro |
|
||||
| `owlry-meta-tools` | clipboard, emoji, websearch, filesearch, systemd |
|
||||
| `owlry-meta-full` | All of the above |
|
||||
|
||||
```bash
|
||||
# Install everything
|
||||
yay -S owlry-full
|
||||
yay -S owlry-meta-full
|
||||
|
||||
# Or pick a bundle
|
||||
yay -S owlry-essentials owlry-widgets
|
||||
yay -S owlry-meta-essentials owlry-meta-widgets
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -23,7 +23,7 @@ Edit `Cargo.toml`:
|
||||
[package]
|
||||
name = "owlry-plugin-myplugin"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
@@ -38,7 +38,7 @@ Edit `src/lib.rs`:
|
||||
use abi_stable::std_types::{ROption, RStr, RString, RVec};
|
||||
use owlry_plugin_api::{
|
||||
owlry_plugin, PluginInfo, PluginItem, ProviderHandle, ProviderInfo,
|
||||
ProviderKind, API_VERSION,
|
||||
ProviderKind, ProviderPosition, API_VERSION,
|
||||
};
|
||||
|
||||
extern "C" fn plugin_info() -> PluginInfo {
|
||||
@@ -59,6 +59,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
|
||||
icon: RString::from("application-x-executable"),
|
||||
provider_type: ProviderKind::Static,
|
||||
type_id: RString::from("myplugin"),
|
||||
position: ProviderPosition::Normal,
|
||||
priority: 0, // Use frecency-based ordering
|
||||
}].into()
|
||||
}
|
||||
|
||||
@@ -198,12 +200,19 @@ pub struct ProviderInfo {
|
||||
pub icon: RString, // Default icon name
|
||||
pub provider_type: ProviderKind, // Static or Dynamic
|
||||
pub type_id: RString, // Short ID for badges
|
||||
pub position: ProviderPosition, // Normal or Widget
|
||||
pub priority: i32, // Result ordering (higher = first)
|
||||
}
|
||||
|
||||
pub enum ProviderKind {
|
||||
Static, // Items loaded at startup via refresh()
|
||||
Dynamic, // Items computed per-query via query()
|
||||
}
|
||||
|
||||
pub enum ProviderPosition {
|
||||
Normal, // Standard results (sorted by score/frecency)
|
||||
Widget, // Displayed at top when query is empty
|
||||
}
|
||||
```
|
||||
|
||||
### PluginItem
|
||||
|
||||
36
justfile
@@ -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
|
||||
@@ -160,7 +168,7 @@ bump-plugins new_version:
|
||||
bump-meta new_version:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
for pkg in owlry-essentials owlry-tools owlry-widgets owlry-full; do
|
||||
for pkg in owlry-meta-essentials owlry-meta-tools owlry-meta-widgets owlry-meta-full; do
|
||||
file="aur/$pkg/PKGBUILD"
|
||||
old=$(grep '^pkgver=' "$file" | sed 's/pkgver=//')
|
||||
if [ "$old" != "{{new_version}}" ]; then
|
||||
@@ -295,12 +303,11 @@ aur-update-pkg pkg:
|
||||
fi
|
||||
|
||||
url="https://somegit.dev/Owlibou/owlry"
|
||||
core_ver="{{version}}"
|
||||
|
||||
# Determine crate version
|
||||
# Determine crate version (unified versioning: all crates share same version)
|
||||
case "{{pkg}}" in
|
||||
owlry-essentials|owlry-tools|owlry-widgets|owlry-full)
|
||||
# Meta-packages have no crate, keep current version
|
||||
owlry-meta-essentials|owlry-meta-tools|owlry-meta-widgets|owlry-meta-full)
|
||||
# Meta-packages use static versioning (1.0.0), only bump pkgrel for dep changes
|
||||
crate_ver=$(grep '^pkgver=' "$aur_dir/PKGBUILD" | sed 's/pkgver=//')
|
||||
;;
|
||||
*)
|
||||
@@ -322,16 +329,8 @@ aur-update-pkg pkg:
|
||||
sed -i "s/^pkgver=.*/pkgver=$crate_ver/" PKGBUILD
|
||||
sed -i 's/^pkgrel=.*/pkgrel=1/' PKGBUILD
|
||||
|
||||
# Update _srcver for plugins/runtimes (they download from core version tag)
|
||||
if grep -q "^_srcver=" PKGBUILD; then
|
||||
echo " _srcver=$core_ver"
|
||||
sed -i "s/^_srcver=.*/_srcver=$core_ver/" PKGBUILD
|
||||
# Update checksum using core version
|
||||
echo "Updating checksums (from v$core_ver)..."
|
||||
b2sum=$(curl -sL "$url/archive/v$core_ver.tar.gz" | b2sum | cut -d' ' -f1)
|
||||
sed -i "s/^b2sums=.*/b2sums=('$b2sum')/" PKGBUILD
|
||||
elif grep -q "^source=" PKGBUILD; then
|
||||
# Core package uses pkgver for source
|
||||
# Update checksums (unified versioning: all packages use same version)
|
||||
if grep -q "^source=" PKGBUILD; then
|
||||
echo "Updating checksums..."
|
||||
b2sum=$(curl -sL "$url/archive/v$crate_ver.tar.gz" | b2sum | cut -d' ' -f1)
|
||||
sed -i "s/^b2sums=.*/b2sums=('$b2sum')/" PKGBUILD
|
||||
@@ -404,7 +403,7 @@ aur-publish-plugins:
|
||||
aur-publish-meta:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
for pkg in owlry-essentials owlry-tools owlry-widgets owlry-full; do
|
||||
for pkg in owlry-meta-essentials owlry-meta-tools owlry-meta-widgets owlry-meta-full; do
|
||||
echo "=== Publishing $pkg ==="
|
||||
just aur-publish-pkg "$pkg"
|
||||
done
|
||||
@@ -446,9 +445,8 @@ aur-update-all:
|
||||
just aur-update-pkg owlry-rune
|
||||
echo ""
|
||||
echo "=== Updating meta-packages ==="
|
||||
for pkg in owlry-essentials owlry-tools owlry-widgets owlry-full; do
|
||||
for pkg in owlry-meta-essentials owlry-meta-tools owlry-meta-widgets owlry-meta-full; do
|
||||
echo "--- $pkg ---"
|
||||
# Use subshell to avoid cd issues
|
||||
(cd "aur/$pkg" && makepkg --printsrcinfo > .SRCINFO)
|
||||
done
|
||||
echo ""
|
||||
@@ -473,7 +471,7 @@ aur-publish-all:
|
||||
just aur-publish-pkg owlry-rune
|
||||
echo ""
|
||||
echo "=== Publishing meta-packages ==="
|
||||
for pkg in owlry-essentials owlry-tools owlry-widgets owlry-full; do
|
||||
for pkg in owlry-meta-essentials owlry-meta-tools owlry-meta-widgets owlry-meta-full; do
|
||||
echo "--- $pkg ---"
|
||||
just aur-publish-pkg "$pkg"
|
||||
done
|
||||
|
||||