clean architecture refactor, performance, resilience, DX/UX architecture: - 13 crates with proper domain/application/infrastructure layers - domain crate: newtypes, ports (Plugin, AppLauncher), constants - kernel: pure orchestrator - shared UI state machine (k-launcher-ui-core) - merged plugin-api into domain as ports module - granular file structure (no monolithic lib.rs) - all tests extracted to tests/ directories features: - frecency boost in search results - empty query shows top frecent apps - append-only frecency log with configurable compaction - config-driven styling (all colors, sizes, debounce) - configurable terminal emulator, external plugin timeout - log rotation with max_log_files - loading indicator, descriptive placeholder text - graceful shutdown via iced::exit() + Plugin::shutdown() - --version flag, panic hook, signal handling (SIGINT/SIGTERM) - SpawnInTerminal in external plugin protocol performance: - ~1500 -> ~50 heap allocs per keystroke - reused Matcher, Pattern, char buffer across entries - Arc<str> for shared result fields - pre-filter before fuzzy matching - partial sort for top frecent IDs - cached lowercase names in entries resilience: - parking_lot (no mutex poisoning) - thiserror hierarchy (PluginError, ConfigError, AppError) - all silent error swallowing replaced with tracing::warn - config parse errors logged quality: - named constants (no magic strings/numbers) - named types (no anonymous tuples) - Rgba newtype with validation - domain newtype validation (debug_assert non-empty) - man page, LICENSE (MIT), PKGBUILD, example config - plugin development guide updated - make check (fmt + clippy + test), make dev (RUST_LOG=debug) style: format code for better readability in tests and function signatures fix: update build_entries function signature to ignore frecency parameter fix(review): bugs, arch violations, design smells P1 bugs: - unix_launcher: shell_split respects quoted args (was split_whitespace) - plugin-host: 5s timeout on external plugin search - ui: handle engine init panic, wire error state - ui-egui: read window config instead of always using defaults - plugin-url: use OpenPath action instead of SpawnProcess+xdg-open Architecture: - remove WindowConfig (mirror of WindowCfg); use WindowCfg directly - remove on_select closure from SearchResult (domain leakage) - remove LaunchAction::Custom; add Plugin::on_selected + SearchEngine::on_selected - apps: record frecency via on_selected instead of embedded closure Design smells: - frecency: extract decay_factor helper, write outside mutex - apps: remove cfg(test) cache_path hack; add new_for_test ctor - apps: stable ResultId using name+exec to prevent collision - files: stable ResultId using full path instead of index - plugin-host: remove k-launcher-os-bridge dep (WindowConfig gone) Update iced dependency in Cargo.toml to disable default features and add additional ones feat(app): enhance engine initialization with EngineHandle and update run function signature feat: production hardening (panic isolation, file logging, apps cache) - Kernel::search wraps each plugin in catch_unwind; panics are logged and return [] - init_logging() adds daily rolling file at ~/.local/share/k-launcher/logs/ - AppsPlugin caches entries to ~/.cache/k-launcher/apps.bin via bincode; stale-while-revalidate on subsequent launches - 57 tests pass refactor: remove client module and associated show command logic fix(app): format code for clarity in update function chore: update .gitignore and enhance README with compositor setup instructions chore(docs): remove unused screenshot file feature/prod-ready (#1) Reviewed-on: #1 fix(calc): remove ambiguous log alias, use ln/log2/log10 explicitly fix(calc): fix log/ln naming, cache math context, strengthen sin(pi) test feat(calc): add math functions (sqrt, sin, cos, etc.) and pi/e constants refactor(calc): rename preprocess, extend underscore test assertions feat(calc): strip underscore digit separators feat: update dependencies for improved compatibility and performance feat: add plugin-url for URL handling and open in browser functionality feat: add support for external plugins and enhance plugin management feat: add Makefile for build, run, and installation commands feat: add required features for k-launcher-egui and update dependencies feat: update README and add documentation for installation, configuration, usage, and plugin development feat: enhance configuration management and UI styling, remove unused theme module feat: add k-launcher-config crate for configuration management and integrate with existing components feat: add k-launcher-ui-egui crate for enhanced UI - Introduced a new crate `k-launcher-ui-egui` to provide a graphical user interface using eframe and egui. - Updated the workspace configuration in `Cargo.toml` to include the new crate. - Implemented the main application logic in `src/app.rs`, handling search functionality and user interactions. - Created a library entry point in `src/lib.rs` to expose the `run` function for launching the UI. - Modified the `k-launcher` crate to include a new binary target for the egui-based launcher. - Added a new main file `src/main_egui.rs` to initialize and run the egui UI with the existing kernel and launcher components. feat: implement OS bridge and enhance app launcher functionality feat: add FilesPlugin for file searching and integrate into KLauncher feat: implement frecency tracking for app usage and enhance search functionality feat: add CmdPlugin for executing terminal commands and update workspace configuration refactor: update dependencies and improve keyboard event handling in KLauncherApp refactor: simplify theme usage and enhance AppsPlugin structure feat: restructure k-launcher workspace and add core functionality - Updated Cargo.toml to include a new k-launcher crate and reorganized workspace members. - Introduced a README.md file detailing the project philosophy, architecture, and technical specifications. - Implemented a new Kernel struct in k-launcher-kernel for managing plugins and search functionality. - Created a Plugin trait for plugins to implement, allowing for asynchronous search operations. - Developed k-launcher-ui with an Iced-based UI for user interaction, including search input and result display. - Added AppsPlugin and CalcPlugin to handle application launching and basic calculations, respectively. - Established a theme module for UI styling, focusing on an Aero aesthetic. - Removed unnecessary main.rs files from plugin crates, streamlining the project structure. Initialize k-launcher project structure with multiple crates and basic configurations
5.6 KiB
Plugin Development
Plugins are queried concurrently — the kernel fans out every search to all enabled plugins and merges results by score.
There are two kinds of plugins:
- External plugins — executables that speak a JSON protocol over stdin/stdout. Any language, no compilation required. Recommended for community plugins.
- Built-in plugins — Rust crates compiled into the binary. For performance-critical or tightly integrated plugins.
External Plugins
An external plugin is any executable that:
- Reads a JSON object from stdin (one line per query)
- Writes a JSON array of results to stdout (one line per response)
Protocol
Input (one line, newline-terminated):
{"query": "firefox"}
Output (one line, newline-terminated):
[{"id":"app-firefox","title":"Firefox","score":80,"description":"Web Browser","action":{"type":"SpawnProcess","cmd":"firefox"}}]
The process is kept alive between queries — do not exit after each response.
Action types
"type" |
Extra fields | Behavior |
|---|---|---|
SpawnProcess |
"cmd" |
Launch process directly |
SpawnInTerminal |
"cmd" |
Run command in terminal emulator |
CopyToClipboard |
"text" |
Copy text to clipboard |
OpenPath |
"path" |
Open file/dir with xdg-open |
Optional result fields
| Field | Type | Description |
|---|---|---|
description |
string |
Secondary line shown below title |
icon |
string |
Icon path (future use) |
Enabling an external plugin
In ~/.config/k-launcher/config.toml:
[[plugins.external]]
name = "my-plugin"
path = "/usr/lib/k-launcher/plugins/my-plugin"
args = [] # optional
timeout_secs = 5 # optional, default 5
Multiple [[plugins.external]] blocks are supported.
Example: shell plugin
#!/usr/bin/env bash
# A plugin that greets the user.
while IFS= read -r line; do
query=$(echo "$line" | python3 -c "import sys,json; print(json.load(sys.stdin)['query'])")
if [[ "$query" == hello* ]]; then
echo '[{"id":"greet","title":"Hello, World!","score":80,"action":{"type":"CopyToClipboard","text":"Hello, World!"}}]'
else
echo '[]'
fi
done
Example: Python plugin
#!/usr/bin/env python3
import sys, json
for line in sys.stdin:
query = json.loads(line)["query"]
results = []
if query.startswith("hello"):
results.append({
"id": "greet",
"title": "Hello, World!",
"score": 80,
"action": {"type": "CopyToClipboard", "text": "Hello, World!"},
})
print(json.dumps(results), flush=True)
Built-in Plugins (compiled-in)
Built-in plugins implement the Plugin trait from k-launcher-domain as Rust crates compiled into the binary.
1. Create a new crate in the workspace
cargo new --lib crates/plugins/plugin-hello
Add it to the workspace root Cargo.toml:
[workspace]
members = [
# ...existing members...
"crates/plugins/plugin-hello",
]
2. Add dependencies
crates/plugins/plugin-hello/Cargo.toml:
[dependencies]
k-launcher-domain = { workspace = true }
async-trait = "0.1"
3. Implement the Plugin trait
crates/plugins/plugin-hello/src/lib.rs:
use std::sync::Arc;
use async_trait::async_trait;
use k_launcher_domain::{LaunchAction, Plugin, ResultId, ResultTitle, Score, SearchResult};
pub struct HelloPlugin;
impl HelloPlugin {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Plugin for HelloPlugin {
fn name(&self) -> &str {
"hello"
}
async fn search(&self, query: &str) -> Vec<SearchResult> {
if !query.starts_with("hello") {
return vec![];
}
vec![SearchResult {
id: ResultId::new("hello:world"),
title: ResultTitle::new("Hello, World!"),
description: Some(Arc::from("A greeting from the hello plugin")),
icon: None,
score: Score::new(80),
action: LaunchAction::CopyToClipboard("Hello, World!".to_string()),
}]
}
}
4. Wire up in main.rs
crates/k-launcher/src/main.rs — add alongside the existing plugins:
use plugin_hello::HelloPlugin;
// inside main():
plugins.push(Arc::new(HelloPlugin::new()));
Also add the dependency to crates/k-launcher/Cargo.toml:
[dependencies]
plugin-hello = { path = "../plugins/plugin-hello" }
Reference
SearchResult Fields
| Field | Type | Description |
|---|---|---|
id |
ResultId |
Unique stable ID (e.g. "apps:firefox") |
title |
ResultTitle |
Primary display text |
description |
Option<Arc<str>> |
Secondary line shown below title |
icon |
Option<Arc<str>> |
Icon name or path (currently unused in renderer) |
score |
Score(u32) |
Sort priority — higher wins |
action |
LaunchAction |
What happens on Enter |
LaunchAction Variants
| Variant | Behavior |
|---|---|
SpawnProcess(String) |
Launch a process directly (e.g. app exec string) |
SpawnInTerminal(String) |
Run command inside a terminal emulator |
OpenPath(String) |
Open a file or directory with xdg-open |
CopyToClipboard(String) |
Copy text to clipboard |
Scoring Guidance
| Score range | Match type |
|---|---|
| 100 | Exact match |
| 90–99 | Calc/command result (always relevant) |
| 80 | Prefix match |
| 70 | Abbreviation match |
| 60 | Substring match |
| 50 | Keyword / loose match |
The kernel sorts all results from all plugins by score descending and truncates to max_results (default: 8).