Files
k-launcher/crates/k-launcher-plugin-host/src/plugin.rs
Gabriel Kaszewski 051d19d878
Some checks failed
CI / test (push) Failing after 5m16s
CI / fmt (push) Has been cancelled
CI / clippy (push) Has been cancelled
Release / build (push) Failing after 5m31s
v0.2.0
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
2026-07-24 13:42:14 +02:00

138 lines
4.3 KiB
Rust

use std::sync::Arc;
use async_trait::async_trait;
use k_launcher_domain::{LaunchAction, Plugin, ResultId, ResultTitle, Score, SearchResult};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
use tokio::process::{ChildStdin, ChildStdout, Command};
use tokio::sync::Mutex;
use crate::error::PluginError;
use crate::protocol::{ExternalAction, ExternalResult, Query};
struct ProcessIo {
stdin: BufWriter<ChildStdin>,
stdout: BufReader<ChildStdout>,
}
async fn do_search(io: &mut ProcessIo, query: &str) -> Result<Vec<ExternalResult>, PluginError> {
let line = serde_json::to_string(&Query {
query: query.to_string(),
})
.map_err(|e| PluginError::Protocol(e.to_string()))?;
io.stdin
.write_all(line.as_bytes())
.await
.map_err(|e| PluginError::ProcessError(e.to_string()))?;
io.stdin
.write_all(b"\n")
.await
.map_err(|e| PluginError::ProcessError(e.to_string()))?;
io.stdin
.flush()
.await
.map_err(|e| PluginError::ProcessError(e.to_string()))?;
let mut response = String::new();
io.stdout
.read_line(&mut response)
.await
.map_err(|e| PluginError::ProcessError(e.to_string()))?;
serde_json::from_str(&response).map_err(|e| PluginError::Protocol(e.to_string()))
}
pub struct ExternalPlugin {
name: String,
path: String,
args: Vec<String>,
timeout_secs: u64,
inner: Mutex<Option<ProcessIo>>,
}
impl ExternalPlugin {
pub fn new(
name: impl Into<String>,
path: impl Into<String>,
args: Vec<String>,
timeout_secs: u64,
) -> Self {
Self {
name: name.into(),
path: path.into(),
args,
timeout_secs,
inner: Mutex::new(None),
}
}
async fn spawn(&self) -> std::io::Result<ProcessIo> {
let mut child = Command::new(&self.path)
.args(&self.args)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()?;
let stdin = BufWriter::new(child.stdin.take().unwrap());
let stdout = BufReader::new(child.stdout.take().unwrap());
Ok(ProcessIo { stdin, stdout })
}
}
#[async_trait]
impl Plugin for ExternalPlugin {
fn name(&self) -> &str {
&self.name
}
async fn search(&self, query: &str) -> Vec<SearchResult> {
let mut guard = self.inner.lock().await;
if guard.is_none() {
match self.spawn().await {
Ok(io) => *guard = Some(io),
Err(e) => {
tracing::warn!("failed to spawn plugin {}: {e}", self.name);
return vec![];
}
}
}
let result = match guard.as_mut() {
Some(io) => tokio::time::timeout(
std::time::Duration::from_secs(self.timeout_secs),
do_search(io, query),
)
.await
.unwrap_or(Err(PluginError::Timeout {
timeout_secs: self.timeout_secs,
})),
None => unreachable!(),
};
match result {
Ok(results) => results
.into_iter()
.map(|r| SearchResult {
id: ResultId::new(r.id),
title: ResultTitle::new(r.title),
description: r.description.map(Arc::from),
icon: r.icon.map(Arc::from),
score: Score::new(r.score),
action: match r.action {
ExternalAction::SpawnProcess { cmd } => LaunchAction::SpawnProcess(cmd),
ExternalAction::SpawnInTerminal { cmd } => {
LaunchAction::SpawnInTerminal(cmd)
}
ExternalAction::CopyToClipboard { text } => {
LaunchAction::CopyToClipboard(text)
}
ExternalAction::OpenPath { path } => LaunchAction::OpenPath(path),
},
})
.collect(),
Err(e) => {
tracing::warn!("plugin {} error: {e}", self.name);
*guard = None;
vec![]
}
}
}
}