feat: add k-launcher-config crate for configuration management and integrate with existing components

This commit is contained in:
2026-03-15 18:20:15 +01:00
parent bc7c896519
commit 3098a4be7c
15 changed files with 494 additions and 46 deletions

188
Cargo.lock generated
View File

@@ -846,6 +846,27 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "dispatch" name = "dispatch"
version = "0.2.0" version = "0.2.0"
@@ -1438,6 +1459,17 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.4" version = "0.3.4"
@@ -2184,6 +2216,7 @@ name = "k-launcher"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"iced", "iced",
"k-launcher-config",
"k-launcher-kernel", "k-launcher-kernel",
"k-launcher-os-bridge", "k-launcher-os-bridge",
"k-launcher-ui", "k-launcher-ui",
@@ -2195,6 +2228,15 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "k-launcher-config"
version = "0.1.0"
dependencies = [
"dirs",
"serde",
"toml",
]
[[package]] [[package]]
name = "k-launcher-kernel" name = "k-launcher-kernel"
version = "0.1.0" version = "0.1.0"
@@ -2209,6 +2251,7 @@ dependencies = [
name = "k-launcher-os-bridge" name = "k-launcher-os-bridge"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"k-launcher-config",
"k-launcher-kernel", "k-launcher-kernel",
"libc", "libc",
] ]
@@ -2218,6 +2261,7 @@ name = "k-launcher-ui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"iced", "iced",
"k-launcher-config",
"k-launcher-kernel", "k-launcher-kernel",
"k-launcher-os-bridge", "k-launcher-os-bridge",
"tokio", "tokio",
@@ -2229,6 +2273,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"eframe", "eframe",
"egui", "egui",
"k-launcher-config",
"k-launcher-kernel", "k-launcher-kernel",
"k-launcher-os-bridge", "k-launcher-os-bridge",
"tokio", "tokio",
@@ -3082,6 +3127,12 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "orbclient" name = "orbclient"
version = "0.3.51" version = "0.3.51"
@@ -3372,7 +3423,7 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [ dependencies = [
"toml_edit", "toml_edit 0.25.4+spec-1.1.0",
] ]
[[package]] [[package]]
@@ -3612,6 +3663,17 @@ dependencies = [
"bitflags 2.11.0", "bitflags 2.11.0",
] ]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.17",
"libredox",
"thiserror 1.0.69",
]
[[package]] [[package]]
name = "renderdoc-sys" name = "renderdoc-sys"
version = "1.1.0" version = "1.1.0"
@@ -3821,6 +3883,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@@ -4288,6 +4359,27 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime 0.6.11",
"toml_edit 0.22.27",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "1.0.0+spec-1.1.0" version = "1.0.0+spec-1.1.0"
@@ -4297,6 +4389,20 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime 0.6.11",
"toml_write",
"winnow",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.25.4+spec-1.1.0" version = "0.25.4+spec-1.1.0"
@@ -4304,7 +4410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"toml_datetime", "toml_datetime 1.0.0+spec-1.1.0",
"toml_parser", "toml_parser",
"winnow", "winnow",
] ]
@@ -4318,6 +4424,12 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.44" version = "0.1.44"
@@ -4534,6 +4646,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasip2" name = "wasip2"
version = "1.0.2+wasi-0.2.9" version = "1.0.2+wasi-0.2.9"
@@ -5309,6 +5427,15 @@ dependencies = [
"windows-targets 0.42.2", "windows-targets 0.42.2",
] ]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"
@@ -5351,6 +5478,21 @@ dependencies = [
"windows_x86_64_msvc 0.42.2", "windows_x86_64_msvc 0.42.2",
] ]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.52.6"
@@ -5382,6 +5524,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.6" version = "0.52.6"
@@ -5394,6 +5542,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
@@ -5406,6 +5560,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
@@ -5424,6 +5584,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
@@ -5436,6 +5602,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
@@ -5448,6 +5620,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
@@ -5460,6 +5638,12 @@ version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"

View File

@@ -1,6 +1,7 @@
[workspace] [workspace]
members = [ members = [
"crates/k-launcher", "crates/k-launcher",
"crates/k-launcher-config",
"crates/k-launcher-kernel", "crates/k-launcher-kernel",
"crates/k-launcher-os-bridge", "crates/k-launcher-os-bridge",
"crates/k-launcher-ui", "crates/k-launcher-ui",
@@ -14,8 +15,10 @@ resolver = "2"
[workspace.dependencies] [workspace.dependencies]
async-trait = "0.1" async-trait = "0.1"
dirs = "5.0"
futures = "0.3" futures = "0.3"
iced = { version = "0.14", features = ["image", "svg", "tokio", "tiny-skia"] } iced = { version = "0.14", features = ["image", "svg", "tokio", "tiny-skia"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.35", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.35", features = ["rt-multi-thread", "macros"] }
toml = "0.8"
tracing = "0.1" tracing = "0.1"

View File

@@ -0,0 +1,13 @@
[package]
name = "k-launcher-config"
version = "0.1.0"
edition = "2024"
[lib]
name = "k_launcher_config"
path = "src/lib.rs"
[dependencies]
dirs = { workspace = true }
serde = { workspace = true }
toml = { workspace = true }

View File

@@ -0,0 +1,193 @@
use serde::Deserialize;
// RGBA: [r, g, b, a] where r/g/b are 0255 as f32, a is 0.01.0
pub type Rgba = [f32; 4];
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct Config {
pub window: WindowCfg,
pub appearance: AppearanceCfg,
pub search: SearchCfg,
pub plugins: PluginsCfg,
}
impl Default for Config {
fn default() -> Self {
Self {
window: WindowCfg::default(),
appearance: AppearanceCfg::default(),
search: SearchCfg::default(),
plugins: PluginsCfg::default(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct WindowCfg {
pub width: f32,
pub height: f32,
pub decorations: bool,
pub transparent: bool,
pub resizable: bool,
}
impl Default for WindowCfg {
fn default() -> Self {
Self {
width: 600.0,
height: 400.0,
decorations: false,
transparent: true,
resizable: false,
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct AppearanceCfg {
pub background_rgba: Rgba,
pub border_rgba: Rgba,
pub border_width: f32,
pub border_radius: f32,
pub search_font_size: f32,
pub title_size: f32,
pub desc_size: f32,
pub row_radius: f32,
pub placeholder: String,
}
impl Default for AppearanceCfg {
fn default() -> Self {
Self {
background_rgba: [20.0, 20.0, 30.0, 0.9],
border_rgba: [0.0, 183.0, 235.0, 1.0],
border_width: 1.0,
border_radius: 8.0,
search_font_size: 18.0,
title_size: 15.0,
desc_size: 12.0,
row_radius: 4.0,
placeholder: "Search...".to_string(),
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct SearchCfg {
pub max_results: usize,
}
impl Default for SearchCfg {
fn default() -> Self {
Self { max_results: 8 }
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct PluginsCfg {
pub calc: bool,
pub cmd: bool,
pub files: bool,
pub apps: bool,
}
impl Default for PluginsCfg {
fn default() -> Self {
Self {
calc: true,
cmd: true,
files: true,
apps: true,
}
}
}
pub fn load() -> Config {
let path = dirs::config_dir()
.map(|d| d.join("k-launcher").join("config.toml"));
let Some(path) = path else {
return Config::default();
};
let Ok(content) = std::fs::read_to_string(&path) else {
return Config::default();
};
toml::from_str(&content).unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_has_sane_values() {
let cfg = Config::default();
assert_eq!(cfg.search.max_results, 8);
assert_eq!(cfg.window.width, 600.0);
assert_eq!(cfg.window.height, 400.0);
assert!(!cfg.window.decorations);
assert!(cfg.window.transparent);
assert!(!cfg.window.resizable);
assert!(cfg.plugins.calc);
assert!(cfg.plugins.apps);
assert_eq!(cfg.appearance.search_font_size, 18.0);
assert_eq!(cfg.appearance.placeholder, "Search...");
}
#[test]
fn parse_partial_toml_uses_defaults() {
let toml = "[search]\nmax_results = 5\n";
let cfg: Config = toml::from_str(toml).unwrap();
assert_eq!(cfg.search.max_results, 5);
assert_eq!(cfg.window.width, 600.0);
assert_eq!(cfg.appearance.search_font_size, 18.0);
assert!(cfg.plugins.apps);
}
#[test]
fn parse_full_toml_roundtrip() {
let toml = r#"
[window]
width = 800.0
height = 500.0
decorations = true
transparent = false
resizable = true
[appearance]
background_rgba = [10.0, 10.0, 20.0, 0.8]
border_rgba = [100.0, 200.0, 255.0, 1.0]
border_width = 2.0
border_radius = 12.0
search_font_size = 20.0
title_size = 16.0
desc_size = 13.0
row_radius = 6.0
placeholder = "Type here..."
[search]
max_results = 12
[plugins]
calc = false
cmd = true
files = false
apps = true
"#;
let cfg: Config = toml::from_str(toml).unwrap();
assert_eq!(cfg.window.width, 800.0);
assert_eq!(cfg.window.height, 500.0);
assert!(cfg.window.decorations);
assert!(!cfg.window.transparent);
assert_eq!(cfg.appearance.background_rgba, [10.0, 10.0, 20.0, 0.8]);
assert_eq!(cfg.appearance.search_font_size, 20.0);
assert_eq!(cfg.appearance.placeholder, "Type here...");
assert_eq!(cfg.search.max_results, 12);
assert!(!cfg.plugins.calc);
assert!(!cfg.plugins.files);
}
}

View File

@@ -101,11 +101,12 @@ pub trait SearchEngine: Send + Sync {
pub struct Kernel { pub struct Kernel {
plugins: Vec<Arc<dyn Plugin>>, plugins: Vec<Arc<dyn Plugin>>,
max_results: usize,
} }
impl Kernel { impl Kernel {
pub fn new(plugins: Vec<Arc<dyn Plugin>>) -> Self { pub fn new(plugins: Vec<Arc<dyn Plugin>>, max_results: usize) -> Self {
Self { plugins } Self { plugins, max_results }
} }
pub async fn search(&self, query: &str) -> Vec<SearchResult> { pub async fn search(&self, query: &str) -> Vec<SearchResult> {
@@ -113,7 +114,7 @@ impl Kernel {
let nested: Vec<Vec<SearchResult>> = join_all(futures).await; let nested: Vec<Vec<SearchResult>> = join_all(futures).await;
let mut flat: Vec<SearchResult> = nested.into_iter().flatten().collect(); let mut flat: Vec<SearchResult> = nested.into_iter().flatten().collect();
flat.sort_by(|a, b| b.score.cmp(&a.score)); flat.sort_by(|a, b| b.score.cmp(&a.score));
flat.truncate(8); flat.truncate(self.max_results);
flat flat
} }
} }
@@ -181,7 +182,7 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn empty_kernel_returns_empty() { async fn empty_kernel_returns_empty() {
let k = Kernel::new(vec![]); let k = Kernel::new(vec![], 8);
assert!(k.search("x").await.is_empty()); assert!(k.search("x").await.is_empty());
} }
@@ -192,10 +193,26 @@ mod tests {
("higher", 10), ("higher", 10),
("middle", 7), ("middle", 7),
])); ]));
let k = Kernel::new(vec![plugin]); let k = Kernel::new(vec![plugin], 8);
let results = k.search("q").await; let results = k.search("q").await;
assert_eq!(results[0].score.value(), 10); assert_eq!(results[0].score.value(), 10);
assert_eq!(results[1].score.value(), 7); assert_eq!(results[1].score.value(), 7);
assert_eq!(results[2].score.value(), 5); assert_eq!(results[2].score.value(), 5);
} }
#[tokio::test]
async fn kernel_truncates_at_max_results() {
let plugin = Arc::new(MockPlugin::returns(vec![
("a", 10),
("b", 9),
("c", 8),
("d", 7),
("e", 6),
]));
let k = Kernel::new(vec![plugin], 3);
let results = k.search("q").await;
assert_eq!(results.len(), 3);
assert_eq!(results[0].score.value(), 10);
assert_eq!(results[2].score.value(), 8);
}
} }

View File

@@ -4,5 +4,6 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
k-launcher-config = { path = "../k-launcher-config" }
k-launcher-kernel = { path = "../k-launcher-kernel" } k-launcher-kernel = { path = "../k-launcher-kernel" }
libc = "0.2" libc = "0.2"

View File

@@ -11,13 +11,13 @@ pub struct WindowConfig {
} }
impl WindowConfig { impl WindowConfig {
pub fn launcher() -> Self { pub fn from_cfg(w: &k_launcher_config::WindowCfg) -> Self {
Self { Self {
width: 600.0, width: w.width,
height: 400.0, height: w.height,
decorations: false, decorations: w.decorations,
transparent: true, transparent: w.transparent,
resizable: false, resizable: w.resizable,
} }
} }
} }

View File

@@ -10,6 +10,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
eframe = { version = "0.31", default-features = false, features = ["default_fonts", "wayland", "x11", "glow"] } eframe = { version = "0.31", default-features = false, features = ["default_fonts", "wayland", "x11", "glow"] }
egui = "0.31" egui = "0.31"
k-launcher-config = { path = "../k-launcher-config" }
k-launcher-kernel = { path = "../k-launcher-kernel" } k-launcher-kernel = { path = "../k-launcher-kernel" }
k-launcher-os-bridge = { path = "../k-launcher-os-bridge" } k-launcher-os-bridge = { path = "../k-launcher-os-bridge" }
tokio = { workspace = true } tokio = { workspace = true }

View File

@@ -160,7 +160,7 @@ pub fn run(
engine: Arc<dyn SearchEngine>, engine: Arc<dyn SearchEngine>,
launcher: Arc<dyn AppLauncher>, launcher: Arc<dyn AppLauncher>,
) -> Result<(), eframe::Error> { ) -> Result<(), eframe::Error> {
let wc = WindowConfig::launcher(); let wc = WindowConfig::from_cfg(&k_launcher_config::WindowCfg::default());
let rt = tokio::runtime::Runtime::new().expect("tokio runtime"); let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
let handle = rt.handle().clone(); let handle = rt.handle().clone();

View File

@@ -9,6 +9,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
iced = { workspace = true } iced = { workspace = true }
k-launcher-config = { path = "../k-launcher-config" }
k-launcher-kernel = { path = "../k-launcher-kernel" } k-launcher-kernel = { path = "../k-launcher-kernel" }
k-launcher-os-bridge = { path = "../k-launcher-os-bridge" } k-launcher-os-bridge = { path = "../k-launcher-os-bridge" }
tokio = { workspace = true } tokio = { workspace = true }

View File

@@ -8,30 +8,39 @@ use iced::{
window, window,
}; };
use k_launcher_config::AppearanceCfg;
use k_launcher_kernel::{AppLauncher, SearchEngine, SearchResult}; use k_launcher_kernel::{AppLauncher, SearchEngine, SearchResult};
use k_launcher_os_bridge::WindowConfig; use k_launcher_os_bridge::WindowConfig;
use crate::theme;
static INPUT_ID: std::sync::LazyLock<iced::widget::Id> = static INPUT_ID: std::sync::LazyLock<iced::widget::Id> =
std::sync::LazyLock::new(|| iced::widget::Id::new("search")); std::sync::LazyLock::new(|| iced::widget::Id::new("search"));
fn rgba(c: &[f32; 4]) -> Color {
Color::from_rgba8(c[0] as u8, c[1] as u8, c[2] as u8, c[3])
}
pub struct KLauncherApp { pub struct KLauncherApp {
engine: Arc<dyn SearchEngine>, engine: Arc<dyn SearchEngine>,
launcher: Arc<dyn AppLauncher>, launcher: Arc<dyn AppLauncher>,
query: String, query: String,
results: Arc<Vec<SearchResult>>, results: Arc<Vec<SearchResult>>,
selected: usize, selected: usize,
cfg: AppearanceCfg,
} }
impl KLauncherApp { impl KLauncherApp {
fn new(engine: Arc<dyn SearchEngine>, launcher: Arc<dyn AppLauncher>) -> Self { fn new(
engine: Arc<dyn SearchEngine>,
launcher: Arc<dyn AppLauncher>,
cfg: AppearanceCfg,
) -> Self {
Self { Self {
engine, engine,
launcher, launcher,
query: String::new(), query: String::new(),
results: Arc::new(vec![]), results: Arc::new(vec![]),
selected: 0, selected: 0,
cfg,
} }
} }
} }
@@ -96,13 +105,18 @@ fn update(state: &mut KLauncherApp, message: Message) -> Task<Message> {
} }
fn view(state: &KLauncherApp) -> Element<'_, Message> { fn view(state: &KLauncherApp) -> Element<'_, Message> {
let colors = &*theme::AERO; let cfg = &state.cfg;
let border_color = rgba(&cfg.border_rgba);
let search_bar = text_input("Search...", &state.query) let search_bar = text_input(&cfg.placeholder, &state.query)
.id(INPUT_ID.clone()) .id(INPUT_ID.clone())
.on_input(Message::QueryChanged) .on_input(Message::QueryChanged)
.padding(12) .padding(12)
.size(18); .size(cfg.search_font_size);
let row_radius: f32 = cfg.row_radius;
let title_size: f32 = cfg.title_size;
let desc_size: f32 = cfg.desc_size;
let result_rows: Vec<Element<'_, Message>> = state let result_rows: Vec<Element<'_, Message>> = state
.results .results
@@ -111,7 +125,7 @@ fn view(state: &KLauncherApp) -> Element<'_, Message> {
.map(|(i, result)| { .map(|(i, result)| {
let is_selected = i == state.selected; let is_selected = i == state.selected;
let bg_color = if is_selected { let bg_color = if is_selected {
colors.border_cyan border_color
} else { } else {
Color::from_rgba8(255, 255, 255, 0.07) Color::from_rgba8(255, 255, 255, 0.07)
}; };
@@ -124,12 +138,12 @@ fn view(state: &KLauncherApp) -> Element<'_, Message> {
}; };
let title_col: Element<'_, Message> = if let Some(desc) = &result.description { let title_col: Element<'_, Message> = if let Some(desc) = &result.description {
column![ column![
text(result.title.as_str()).size(15), text(result.title.as_str()).size(title_size),
text(desc).size(12).color(Color::from_rgba8(210, 215, 230, 1.0)), text(desc).size(desc_size).color(Color::from_rgba8(210, 215, 230, 1.0)),
] ]
.into() .into()
} else { } else {
text(result.title.as_str()).size(15).into() text(result.title.as_str()).size(title_size).into()
}; };
container( container(
row![icon_el, title_col] row![icon_el, title_col]
@@ -143,7 +157,7 @@ fn view(state: &KLauncherApp) -> Element<'_, Message> {
border: Border { border: Border {
color: Color::TRANSPARENT, color: Color::TRANSPARENT,
width: 0.0, width: 0.0,
radius: 4.0.into(), radius: row_radius.into(),
}, },
..Default::default() ..Default::default()
}) })
@@ -155,7 +169,7 @@ fn view(state: &KLauncherApp) -> Element<'_, Message> {
scrollable( scrollable(
container( container(
text("No results") text("No results")
.size(15) .size(title_size)
.color(Color::from_rgba8(180, 180, 200, 0.5)), .color(Color::from_rgba8(180, 180, 200, 0.5)),
) )
.width(Length::Fill) .width(Length::Fill)
@@ -173,17 +187,19 @@ fn view(state: &KLauncherApp) -> Element<'_, Message> {
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill); .height(Length::Fill);
let bg_color = rgba(&cfg.background_rgba);
let border_width = cfg.border_width;
let border_radius = cfg.border_radius;
container(content) container(content)
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.style(|_theme| container::Style { .style(move |_theme| container::Style {
background: Some(iced::Background::Color(Color::from_rgba8( background: Some(iced::Background::Color(bg_color)),
20, 20, 30, 0.9,
))),
border: Border { border: Border {
color: theme::AERO.border_cyan, color: border_color,
width: 1.0, width: border_width,
radius: 8.0.into(), radius: border_radius.into(),
}, },
..Default::default() ..Default::default()
}) })
@@ -197,11 +213,16 @@ fn subscription(_state: &KLauncherApp) -> Subscription<Message> {
}) })
} }
pub fn run(engine: Arc<dyn SearchEngine>, launcher: Arc<dyn AppLauncher>) -> iced::Result { pub fn run(
let wc = WindowConfig::launcher(); engine: Arc<dyn SearchEngine>,
launcher: Arc<dyn AppLauncher>,
window_cfg: &k_launcher_config::WindowCfg,
appearance_cfg: AppearanceCfg,
) -> iced::Result {
let wc = WindowConfig::from_cfg(window_cfg);
iced::application( iced::application(
move || { move || {
let app = KLauncherApp::new(engine.clone(), launcher.clone()); let app = KLauncherApp::new(engine.clone(), launcher.clone(), appearance_cfg.clone());
let focus = iced::widget::operation::focus(INPUT_ID.clone()); let focus = iced::widget::operation::focus(INPUT_ID.clone());
(app, focus) (app, focus)
}, },

View File

@@ -3,8 +3,14 @@ pub mod theme;
use std::sync::Arc; use std::sync::Arc;
use k_launcher_config::{AppearanceCfg, WindowCfg};
use k_launcher_kernel::{AppLauncher, SearchEngine}; use k_launcher_kernel::{AppLauncher, SearchEngine};
pub fn run(engine: Arc<dyn SearchEngine>, launcher: Arc<dyn AppLauncher>) -> iced::Result { pub fn run(
app::run(engine, launcher) engine: Arc<dyn SearchEngine>,
launcher: Arc<dyn AppLauncher>,
window_cfg: &WindowCfg,
appearance_cfg: AppearanceCfg,
) -> iced::Result {
app::run(engine, launcher, window_cfg, appearance_cfg)
} }

View File

@@ -14,6 +14,7 @@ path = "src/main_egui.rs"
[dependencies] [dependencies]
iced = { workspace = true } iced = { workspace = true }
k-launcher-config = { path = "../k-launcher-config" }
k-launcher-kernel = { path = "../k-launcher-kernel" } k-launcher-kernel = { path = "../k-launcher-kernel" }
k-launcher-os-bridge = { path = "../k-launcher-os-bridge" } k-launcher-os-bridge = { path = "../k-launcher-os-bridge" }
k-launcher-ui = { path = "../k-launcher-ui" } k-launcher-ui = { path = "../k-launcher-ui" }

View File

@@ -10,13 +10,20 @@ use plugin_cmd::CmdPlugin;
use plugin_files::FilesPlugin; use plugin_files::FilesPlugin;
fn main() -> iced::Result { fn main() -> iced::Result {
let cfg = k_launcher_config::load();
let launcher = Arc::new(UnixAppLauncher::new()); let launcher = Arc::new(UnixAppLauncher::new());
let frecency = FrecencyStore::load(); let frecency = FrecencyStore::load();
let kernel: Arc<dyn k_launcher_kernel::SearchEngine> = Arc::new(Kernel::new(vec![
Arc::new(CmdPlugin::new()), let mut plugins: Vec<Arc<dyn k_launcher_kernel::Plugin>> = vec![];
Arc::new(CalcPlugin::new()), if cfg.plugins.cmd { plugins.push(Arc::new(CmdPlugin::new())); }
Arc::new(FilesPlugin::new()), if cfg.plugins.calc { plugins.push(Arc::new(CalcPlugin::new())); }
Arc::new(AppsPlugin::new(FsDesktopEntrySource::new(), frecency)), if cfg.plugins.files { plugins.push(Arc::new(FilesPlugin::new())); }
])); if cfg.plugins.apps {
k_launcher_ui::run(kernel, launcher) plugins.push(Arc::new(AppsPlugin::new(FsDesktopEntrySource::new(), frecency)));
}
let kernel: Arc<dyn k_launcher_kernel::SearchEngine> =
Arc::new(Kernel::new(plugins, cfg.search.max_results));
k_launcher_ui::run(kernel, launcher, &cfg.window, cfg.appearance)
} }

View File

@@ -17,7 +17,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Arc::new(CalcPlugin::new()), Arc::new(CalcPlugin::new()),
Arc::new(FilesPlugin::new()), Arc::new(FilesPlugin::new()),
Arc::new(AppsPlugin::new(FsDesktopEntrySource::new(), frecency)), Arc::new(AppsPlugin::new(FsDesktopEntrySource::new(), frecency)),
])); ], 8));
k_launcher_ui_egui::run(kernel, launcher)?; k_launcher_ui_egui::run(kernel, launcher)?;
Ok(()) Ok(())
} }