4 Commits

Author SHA1 Message Date
c9f41bccc9 feat: add architecture documentation for Clean Architecture overview
Some checks failed
CI / clippy (push) Failing after 11m41s
CI / fmt (push) Has been cancelled
CI / test (push) Failing after 5m16s
2026-07-24 14:05:30 +02:00
fb0d014ccd feat: add dev-egui target to Makefile for easier development with egui
Some checks failed
CI / clippy (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / test (push) Has been cancelled
2026-07-24 13:57:45 +02:00
0fc21ede97 chore: bump version to 0.2.1 and update related files
Some checks failed
CI / clippy (push) Has been cancelled
CI / fmt (push) Has been cancelled
CI / test (push) Has been cancelled
Release / build (push) Failing after 5m11s
2026-07-24 13:54:32 +02:00
fa5d38107a feat: add ghostty to known terminals list
Some checks failed
CI / test (push) Has been cancelled
CI / clippy (push) Has been cancelled
CI / fmt (push) Has been cancelled
2026-07-24 13:50:38 +02:00
24 changed files with 190 additions and 41 deletions

28
Cargo.lock generated
View File

@@ -2060,7 +2060,7 @@ dependencies = [
[[package]] [[package]]
name = "k-launcher" name = "k-launcher"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"ctrlc", "ctrlc",
"dirs", "dirs",
@@ -2084,7 +2084,7 @@ dependencies = [
[[package]] [[package]]
name = "k-launcher-config" name = "k-launcher-config"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"dirs", "dirs",
"k-launcher-domain", "k-launcher-domain",
@@ -2096,7 +2096,7 @@ dependencies = [
[[package]] [[package]]
name = "k-launcher-domain" name = "k-launcher-domain"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"serde", "serde",
@@ -2104,7 +2104,7 @@ dependencies = [
[[package]] [[package]]
name = "k-launcher-kernel" name = "k-launcher-kernel"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"futures", "futures",
@@ -2118,7 +2118,7 @@ dependencies = [
[[package]] [[package]]
name = "k-launcher-os-bridge" name = "k-launcher-os-bridge"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"k-launcher-domain", "k-launcher-domain",
"libc", "libc",
@@ -2127,7 +2127,7 @@ dependencies = [
[[package]] [[package]]
name = "k-launcher-plugin-host" name = "k-launcher-plugin-host"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"k-launcher-domain", "k-launcher-domain",
@@ -2140,7 +2140,7 @@ dependencies = [
[[package]] [[package]]
name = "k-launcher-ui" name = "k-launcher-ui"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"iced", "iced",
"k-launcher-config", "k-launcher-config",
@@ -2153,7 +2153,7 @@ dependencies = [
[[package]] [[package]]
name = "k-launcher-ui-core" name = "k-launcher-ui-core"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"k-launcher-config", "k-launcher-config",
"k-launcher-domain", "k-launcher-domain",
@@ -2162,7 +2162,7 @@ dependencies = [
[[package]] [[package]]
name = "k-launcher-ui-egui" name = "k-launcher-ui-egui"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"eframe", "eframe",
"egui", "egui",
@@ -3168,7 +3168,7 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]] [[package]]
name = "plugin-apps" name = "plugin-apps"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bincode", "bincode",
@@ -3186,7 +3186,7 @@ dependencies = [
[[package]] [[package]]
name = "plugin-calc" name = "plugin-calc"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"evalexpr", "evalexpr",
@@ -3196,7 +3196,7 @@ dependencies = [
[[package]] [[package]]
name = "plugin-cmd" name = "plugin-cmd"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"k-launcher-domain", "k-launcher-domain",
@@ -3205,7 +3205,7 @@ dependencies = [
[[package]] [[package]]
name = "plugin-files" name = "plugin-files"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"k-launcher-domain", "k-launcher-domain",
@@ -3214,7 +3214,7 @@ dependencies = [
[[package]] [[package]]
name = "plugin-url" name = "plugin-url"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",

View File

@@ -13,6 +13,9 @@ build-egui:
dev: dev:
RUST_LOG=debug cargo run RUST_LOG=debug cargo run
dev-egui:
RUST_LOG=debug cargo run -p k-launcher --features egui --bin k-launcher-egui
check: check:
cargo fmt --all -- --check cargo fmt --all -- --check
cargo clippy --workspace -- -D warnings cargo clippy --workspace -- -D warnings

View File

@@ -77,6 +77,29 @@ timeout_secs = 5
See [Plugin Development](docs/plugin-development.md) for the full protocol. See [Plugin Development](docs/plugin-development.md) for the full protocol.
## Architecture
Clean Architecture (Domain → Application → Infrastructure → Main):
```
domain — pure value types (ResultId, ResultTitle, Score, LaunchAction, SearchResult),
port traits (Plugin, AppLauncher), shared constants
kernel — application use case: Kernel orchestrator (fan-out search, sort, truncate)
config — TOML config loading with typed errors (ConfigError)
ui-core — framework-agnostic UI state machine (LauncherState, Action, Effect)
ui — iced 0.14 rendering adapter
ui-egui — egui rendering adapter (optional)
os-bridge — UnixAppLauncher (process spawning, terminal detection, clipboard)
plugin-host — ExternalPlugin (JSON stdin/stdout protocol with typed PluginError)
plugins/
plugin-apps — XDG .desktop search, frecency ranking, nucleo fuzzy matching, bincode cache
plugin-calc — evalexpr math evaluator (supports sqrt, sin, cos, ln, pi, e, etc.)
plugin-cmd — shell command runner (> prefix, launches in terminal)
plugin-files — filesystem browser (/ and ~/ paths)
plugin-url — URL detection (external binary, tests the external plugin protocol)
k-launcher — entry point: DI wiring, logging, signal handling
```
## Docs ## Docs
- [Installation](docs/install.md) - [Installation](docs/install.md)

81
architecture.mmd Normal file
View File

@@ -0,0 +1,81 @@
---
title: k-launcher — Clean Architecture
---
graph TB
subgraph Main["Main (Composition Root)"]
BIN["k-launcher<br/><i>DI wiring, logging,<br/>signal handling, --version</i>"]
BIN_EGUI["k-launcher-egui<br/><i>Optional egui binary</i>"]
end
subgraph Application["Application Layer"]
KERNEL["kernel<br/><i>Kernel orchestrator</i><br/>Fan-out search, sort by score,<br/>truncate to max_results,<br/>catch_unwind per plugin"]
end
subgraph Domain["Domain Layer (0 external deps)"]
direction TB
subgraph Types["Value Types"]
VT_ID["ResultId(String)"]
VT_TITLE["ResultTitle(String)"]
VT_SCORE["Score(u32)"]
VT_ACTION["LaunchAction<br/><i>SpawnProcess, SpawnInTerminal,<br/>OpenPath, CopyToClipboard</i>"]
VT_RESULT["SearchResult<br/><i>id, title, description,<br/>icon, score, action</i>"]
end
subgraph Ports["Port Traits"]
P_PLUGIN["Plugin<br/><i>name, search, on_selected,<br/>shutdown</i>"]
P_LAUNCHER["AppLauncher<br/><i>execute(LaunchAction)</i>"]
end
CONSTANTS["constants<br/><i>APP_NAME, APP_TITLE,<br/>CONFIG_FILENAME, LOG_*,<br/>FRECENCY_SNAPSHOT_FILENAME</i>"]
end
subgraph Infrastructure["Infrastructure Layer"]
direction TB
subgraph UI["UI"]
UI_CORE["ui-core<br/><i>LauncherState, Action, Effect</i><br/>Framework-agnostic state machine"]
UI_ICED["ui (iced)<br/><i>style, view, update</i><br/>Rendering adapter"]
UI_EGUI["ui-egui<br/><i>style, input, render</i><br/>Rendering adapter"]
end
subgraph Plugins["Plugins (implement Plugin trait)"]
PL_APPS["plugin-apps<br/><i>XDG .desktop search</i><br/>Frecency, nucleo fuzzy,<br/>bincode cache"]
PL_CALC["plugin-calc<br/><i>evalexpr math</i><br/>sqrt, sin, cos, ln, pi, e"]
PL_CMD["plugin-cmd<br/><i>Shell commands</i><br/>> prefix → terminal"]
PL_FILES["plugin-files<br/><i>Filesystem browser</i><br/>/ and ~/ paths"]
PL_URL["plugin-url<br/><i>URL detection</i><br/>External binary (JSON protocol)"]
end
subgraph Platform["Platform"]
OS_BRIDGE["os-bridge<br/><i>UnixAppLauncher</i><br/>shell, terminal, spawn"]
PLUGIN_HOST["plugin-host<br/><i>ExternalPlugin</i><br/>JSON stdin/stdout protocol"]
CONFIG["config<br/><i>TOML loading</i><br/>ConfigError, try_load"]
end
end
%% Dependency arrows
BIN -->|"wires"| Application
BIN -->|"wires"| Infrastructure
BIN_EGUI -->|"wires"| Application
BIN_EGUI -->|"wires"| Infrastructure
Application -->|"depends on"| Domain
UI_ICED -->|"delegates to"| UI_CORE
UI_EGUI -->|"delegates to"| UI_CORE
UI_CORE -->|"uses"| KERNEL
Plugins -.->|"implements"| P_PLUGIN
OS_BRIDGE -.->|"implements"| P_LAUNCHER
PLUGIN_HOST -.->|"implements"| P_PLUGIN
KERNEL -->|"fan-out"| P_PLUGIN
%% Key data flows
UI_CORE ===|"Action → Effect"| KERNEL
PL_APPS ===|"frecency log"| CONSTANTS
classDef domain fill:#1a1a2e,stroke:#e94560,color:#fff
classDef app fill:#16213e,stroke:#0f3460,color:#fff
classDef infra fill:#0f3460,stroke:#533483,color:#fff
classDef binary fill:#533483,stroke:#e94560,color:#fff
class Domain domain
class Application app
class Infrastructure infra
class Main binary

View File

@@ -3,7 +3,7 @@
# Copy this file as PKGBUILD to your AUR repo # Copy this file as PKGBUILD to your AUR repo
pkgname=k-launcher pkgname=k-launcher
pkgver=0.2.0 pkgver=0.2.1
pkgrel=1 pkgrel=1
pkgdesc='Wayland command palette launcher with fuzzy search, frecency, and plugin support' pkgdesc='Wayland command palette launcher with fuzzy search, frecency, and plugin support'
arch=('x86_64') arch=('x86_64')

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-launcher-config" name = "k-launcher-config"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[lib] [lib]

View File

@@ -93,7 +93,10 @@ pub struct AppearanceCfg {
pub row_radius: f32, pub row_radius: f32,
pub placeholder: String, pub placeholder: String,
pub selected_row_rgba: Rgba, pub selected_row_rgba: Rgba,
pub selected_text_rgba: Rgba,
pub selected_description_rgba: Rgba,
pub unselected_row_rgba: Rgba, pub unselected_row_rgba: Rgba,
pub text_rgba: Rgba,
pub description_rgba: Rgba, pub description_rgba: Rgba,
pub no_results_rgba: Rgba, pub no_results_rgba: Rgba,
pub error_rgba: Rgba, pub error_rgba: Rgba,
@@ -113,7 +116,10 @@ impl Default for AppearanceCfg {
row_radius: 4.0, row_radius: 4.0,
placeholder: "Search apps, type > for commands, = for math".to_string(), placeholder: "Search apps, type > for commands, = for math".to_string(),
selected_row_rgba: Rgba::new(229.0, 125.0, 33.0, 1.0), selected_row_rgba: Rgba::new(229.0, 125.0, 33.0, 1.0),
selected_text_rgba: Rgba::new(255.0, 255.0, 255.0, 1.0),
selected_description_rgba: Rgba::new(240.0, 240.0, 240.0, 0.9),
unselected_row_rgba: Rgba::new(255.0, 255.0, 255.0, 0.07), unselected_row_rgba: Rgba::new(255.0, 255.0, 255.0, 0.07),
text_rgba: Rgba::new(255.0, 255.0, 255.0, 1.0),
description_rgba: Rgba::new(210.0, 215.0, 230.0, 1.0), description_rgba: Rgba::new(210.0, 215.0, 230.0, 1.0),
no_results_rgba: Rgba::new(180.0, 180.0, 200.0, 0.5), no_results_rgba: Rgba::new(180.0, 180.0, 200.0, 0.5),
error_rgba: Rgba::new(255.0, 80.0, 80.0, 1.0), error_rgba: Rgba::new(255.0, 80.0, 80.0, 1.0),

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-launcher-domain" name = "k-launcher-domain"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-launcher-kernel" name = "k-launcher-kernel"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-launcher-os-bridge" name = "k-launcher-os-bridge"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@@ -9,6 +9,10 @@ struct KnownTerminal {
} }
const KNOWN_TERMINALS: &[KnownTerminal] = &[ const KNOWN_TERMINALS: &[KnownTerminal] = &[
KnownTerminal {
bin: "ghostty",
exec_flag: "-e",
},
KnownTerminal { KnownTerminal {
bin: "foot", bin: "foot",
exec_flag: "-e", exec_flag: "-e",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-launcher-plugin-host" name = "k-launcher-plugin-host"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[lib] [lib]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-launcher-ui-core" name = "k-launcher-ui-core"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-launcher-ui-egui" name = "k-launcher-ui-egui"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[lib] [lib]

View File

@@ -43,14 +43,25 @@ pub fn render_result_list(
} }
fn render_result_row(ui: &mut Ui, result: &SearchResult, is_selected: bool, cfg: &AppearanceCfg) { fn render_result_row(ui: &mut Ui, result: &SearchResult, is_selected: bool, cfg: &AppearanceCfg) {
let title_color = if is_selected {
to_color32(&cfg.selected_text_rgba)
} else {
to_color32(&cfg.text_rgba)
};
let desc_color = if is_selected {
to_color32(&cfg.selected_description_rgba)
} else {
to_color32(&cfg.description_rgba)
};
style::result_row_frame(is_selected, cfg).show(ui, |ui| { style::result_row_frame(is_selected, cfg).show(ui, |ui| {
ui.set_width(ui.available_width()); ui.set_width(ui.available_width());
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add_space(8.0); ui.add_space(8.0);
ui.vertical(|ui| { ui.vertical(|ui| {
ui.label(result.title.as_str()); ui.colored_label(title_color, result.title.as_str());
if let Some(desc) = &result.description { if let Some(desc) = &result.description {
ui.colored_label(to_color32(&cfg.description_rgba), desc.as_ref()); ui.colored_label(desc_color, desc.as_ref());
} }
}); });
}); });

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-launcher-ui" name = "k-launcher-ui"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[lib] [lib]

View File

@@ -113,9 +113,12 @@ fn result_row<'a>(
}; };
container( container(
row![result_icon(&result.icon, cfg), title_column(result, cfg)] row![
.spacing(style::CONTENT_SPACING) result_icon(&result.icon, cfg),
.align_y(iced::Center), title_column(result, is_selected, cfg)
]
.spacing(style::CONTENT_SPACING)
.align_y(iced::Center),
) )
.width(Length::Fill) .width(Length::Fill)
.padding(style::ROW_PADDING) .padding(style::ROW_PADDING)
@@ -138,17 +141,35 @@ fn result_icon<'a>(icon_path: &'a Option<Arc<str>>, cfg: &AppearanceCfg) -> Elem
} }
} }
fn title_column<'a>(result: &'a SearchResult, cfg: &AppearanceCfg) -> Element<'a, Message> { fn title_column<'a>(
result: &'a SearchResult,
is_selected: bool,
cfg: &AppearanceCfg,
) -> Element<'a, Message> {
let title_color = if is_selected {
style::rgba(&cfg.selected_text_rgba)
} else {
style::rgba(&cfg.text_rgba)
};
let desc_color = if is_selected {
style::rgba(&cfg.selected_description_rgba)
} else {
style::rgba(&cfg.description_rgba)
};
if let Some(desc) = &result.description { if let Some(desc) = &result.description {
column![ column![
text(result.title.as_str()).size(cfg.title_size), text(result.title.as_str())
text(desc.as_ref()) .size(cfg.title_size)
.size(cfg.desc_size) .color(title_color),
.color(style::rgba(&cfg.description_rgba)), text(desc.as_ref()).size(cfg.desc_size).color(desc_color),
] ]
.into() .into()
} else { } else {
text(result.title.as_str()).size(cfg.title_size).into() text(result.title.as_str())
.size(cfg.title_size)
.color(title_color)
.into()
} }
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "k-launcher" name = "k-launcher"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
default-run = "k-launcher" default-run = "k-launcher"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "plugin-apps" name = "plugin-apps"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[lib] [lib]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "plugin-calc" name = "plugin-calc"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[lib] [lib]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "plugin-cmd" name = "plugin-cmd"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[lib] [lib]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "plugin-files" name = "plugin-files"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[lib] [lib]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "plugin-url" name = "plugin-url"
version = "0.2.0" version = "0.2.1"
edition = "2024" edition = "2024"
[lib] [lib]

View File

@@ -1,4 +1,4 @@
.TH K\-LAUNCHER 1 "2026-07-24" "k-launcher 0.2.0" "User Commands" .TH K\-LAUNCHER 1 "2026-07-24" "k-launcher 0.2.1" "User Commands"
.SH NAME .SH NAME
k\-launcher \- Wayland command palette launcher k\-launcher \- Wayland command palette launcher
.SH SYNOPSIS .SH SYNOPSIS