From ae7d3f712379e27e76e79b6b54ceaecd70674676 Mon Sep 17 00:00:00 2001 From: Javier Feliz Date: Sat, 6 Sep 2025 14:37:29 -0400 Subject: [PATCH] Getting to work on caching --- .gitignore | 1 + Cargo.lock | 152 +++++++++++++++++++- tmp.txt | 84 ----------- waycast-core/Cargo.toml | 6 + waycast-core/src/cache/errors.rs | 50 +++++++ waycast-core/src/cache/mod.rs | 236 +++++++++++++++++++++++++++++++ waycast-core/src/lib.rs | 2 + waycast-gtk/Cargo.toml | 5 +- waycast-gtk/src/main.rs | 106 ++++++++------ 9 files changed, 511 insertions(+), 131 deletions(-) delete mode 100644 tmp.txt create mode 100644 waycast-core/src/cache/errors.rs create mode 100644 waycast-core/src/cache/mod.rs diff --git a/.gitignore b/.gitignore index d0daf30..3e533d9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ # direnv cache (should not be committed) .direnv/ +waycast_cache diff --git a/Cargo.lock b/Cargo.lock index 6314728..ca87e3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,35 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + [[package]] name = "bitflags" version = "2.9.3" @@ -110,6 +139,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "field-offset" version = "0.3.6" @@ -249,7 +294,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.4+wasi-0.2.4", ] [[package]] @@ -552,6 +609,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "log" version = "0.4.27" @@ -589,7 +652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] @@ -602,6 +665,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "option-ext" version = "0.2.0" @@ -677,13 +746,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "redb" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fefa3e5ff4a369819c3d6df4195873d6f9abad109f13c0d505dbe119cfabb10" +dependencies = [ + "libc", +] + [[package]] name = "redox_users" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom", + "getrandom 0.2.16", "libredox", "thiserror", ] @@ -703,6 +787,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.60.2", +] + [[package]] name = "same-file" version = "1.0.6" @@ -789,6 +886,19 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +[[package]] +name = "tempfile" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.60.2", +] + [[package]] name = "thiserror" version = "2.0.16" @@ -875,12 +985,24 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + [[package]] name = "walkdir" version = "2.5.0" @@ -897,18 +1019,36 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.4+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "waycast-core" version = "0.1.0" +dependencies = [ + "bincode 1.3.3", + "redb", + "serde", + "tempfile", +] [[package]] name = "waycast-gtk" version = "0.1.0" dependencies = [ + "bincode 2.0.1", "gio", "glib", "gtk4", "gtk4-layer-shell", + "redb", + "serde", "waycast-core", "waycast-plugins", ] @@ -1106,6 +1246,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" + [[package]] name = "xml-rs" version = "0.8.27" diff --git a/tmp.txt b/tmp.txt deleted file mode 100644 index de302d5..0000000 --- a/tmp.txt +++ /dev/null @@ -1,84 +0,0 @@ -cargo run -p waycast-gtk -/home/javi/projects/homelab-ansible -/home/javi/projects/gla-old -/home/javi/projects/filebrowser-agent -/home/javi/projects/ttrpg-cms -/home/javi/projects/desktop-setup -/home/javi/projects/ravensofravnica.stream -/home/javi/projects/nix -/home/javi/projects/demos -/home/javi/projects/resumeforge.app -/home/javi/projects/videos -/home/javi/projects/authentikate.dev -/home/javi/projects/blogimage.app -/home/javi/projects/arch-hyprland-setup -/home/javi/projects/laravel-dockerized -/home/javi/projects/alex-nix-config -/home/javi/projects/thegrind-tools -/home/javi/projects/imgsrc-app -/home/javi/projects/scripthost -/home/javi/projects/foundryvtt-docker -/home/javi/projects/launchlet.app -/home/javi/projects/yourfluence.dev -/home/javi/projects/thegrind.dev -/home/javi/projects/opnsensebackup -/home/javi/projects/project-picker -/home/javi/projects/thegrind.dev-old-jigsaw -/home/javi/projects/og-cli -/home/javi/projects/do-flow -/home/javi/projects/carti-python -/home/javi/projects/rack-planner -/home/javi/projects/ember-db -/home/javi/projects/local-ai -/home/javi/projects/zap -/home/javi/projects/flowtodo -/home/javi/projects/temp -/home/javi/projects/blog-backup -/home/javi/projects/javierfeliz.com -/home/javi/projects/thc-demo -/home/javi/projects/crawler-linkedin -/home/javi/projects/vikunja-planka-transfer -/home/javi/projects/desktop-backup -/home/javi/projects/authentikate -/home/javi/projects/scratchfl.com -/home/javi/projects/cs-trade-bot -/home/javi/projects/leetcode -/home/javi/projects/screenshotter -/home/javi/projects/docker-compose-builder -/home/javi/projects/javif89 -/home/javi/projects/papibot -/home/javi/projects/crypt -/home/javi/projects/analysis -/home/javi/projects/go-starter-kit -/home/javi/projects/vue3-learning -/home/javi/projects/homelab-docker -/home/javi/projects/sitemapper -/home/javi/projects/dotnet-deployment-ansible -/home/javi/projects/planka -/home/javi/projects/waycast-cpp -/home/javi/projects/hubspot-assessment -/home/javi/projects/go-doit -/home/javi/projects/alex-demo -/home/javi/projects/docker-laravel-base -/home/javi/projects/staticforge -/home/javi/projects/jigsaw -/home/javi/projects/theresaleconcierge -/home/javi/projects/nico-stuff -/home/javi/projects/zold-javierfeliz.com -/home/javi/projects/ansible-starter-kit -/home/javi/projects/oidctester -/home/javi/projects/waycast -/home/javi/projects/ubuntu-setup -/home/javi/projects/caddy-test -/home/javi/projects/dotfiles -/home/javi/projects/bazzitouchedhisconfig.dev -/home/javi/projects/cs2-server-manager -/home/javi/projects/noconfig -/home/javi/projects/gamelineanalytics.com -/home/javi/projects/thegrind-de -/home/javi/projects/data-parser -/home/javi/projects/test-dotnet-blazor-project -/home/javi/projects/homies -/home/javi/projects/ogimage-click -/home/javi/projects/ansible-on-prem -Projects plugin: Found 82 projects diff --git a/waycast-core/Cargo.toml b/waycast-core/Cargo.toml index 99bf684..a2f1a35 100644 --- a/waycast-core/Cargo.toml +++ b/waycast-core/Cargo.toml @@ -4,4 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] +bincode = "1.3" +redb = "3.0.1" +serde = { version = "1.0.219", features = ["derive"] } + +[dev-dependencies] +tempfile = "3.21.0" # No dependencies for core traits and launcher logic \ No newline at end of file diff --git a/waycast-core/src/cache/errors.rs b/waycast-core/src/cache/errors.rs new file mode 100644 index 0000000..2845869 --- /dev/null +++ b/waycast-core/src/cache/errors.rs @@ -0,0 +1,50 @@ +use redb::{CommitError, DatabaseError, StorageError, TableError, TransactionError}; + +#[derive(Debug)] +pub enum CacheError { + DatabaseError(String), + SerializationError(String), + Other(String), +} + +impl From for CacheError { + fn from(err: DatabaseError) -> Self { + CacheError::DatabaseError(err.to_string()) + } +} + +impl From for CacheError { + fn from(err: TransactionError) -> Self { + CacheError::DatabaseError(err.to_string()) + } +} + +impl From for CacheError { + fn from(err: TableError) -> Self { + CacheError::DatabaseError(err.to_string()) + } +} + +impl From for CacheError { + fn from(err: StorageError) -> Self { + CacheError::DatabaseError(err.to_string()) + } +} + +impl From for CacheError { + fn from(err: CommitError) -> Self { + CacheError::DatabaseError(err.to_string()) + } +} + +impl std::fmt::Display for CacheError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CacheError::DatabaseError(e) => write!(f, "Database error: {}", e), + CacheError::SerializationError(e) => write!(f, "Serialization error: {}", e), + CacheError::Other(msg) => write!(f, "Cache error: {}", msg), + } + } +} + +impl std::error::Error for CacheError {} diff --git a/waycast-core/src/cache/mod.rs b/waycast-core/src/cache/mod.rs new file mode 100644 index 0000000..177438f --- /dev/null +++ b/waycast-core/src/cache/mod.rs @@ -0,0 +1,236 @@ +pub mod errors; +use redb::{Database, ReadableDatabase, ReadableTable, TableDefinition}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::path::Path; +use std::time::{Duration, SystemTime}; + +use crate::cache::errors::CacheError; + +const CACHE_TABLE: TableDefinition<&str, &[u8]> = TableDefinition::new("cache"); + +#[derive(Serialize, Deserialize)] +struct CacheEntry { + data: T, + expires_at: Option, +} + +pub struct Cache { + db: Database, +} + +pub fn new>(db_path: P) -> Result { + let db = Database::create(db_path)?; + + // Initialize the table if it doesn't exist + let write_txn = db.begin_write()?; + { + let _ = write_txn.open_table(CACHE_TABLE)?; + } + write_txn.commit()?; + + Ok(Cache { db }) +} + +impl Cache { + /// Cache a value with an optional TTL. If TTL is None, the value never expires. + pub fn remember_with_ttl( + &self, + key: &str, + ttl: Option, + compute: impl FnOnce() -> T, + ) -> Result + where + T: Serialize + DeserializeOwned + Clone, + { + // Try to get from cache first + if let Some(entry) = self.get_cached_entry::(key)? { + // Check if entry has expired + if let Some(expires_at) = entry.expires_at { + if SystemTime::now() < expires_at { + println!("Cache hit"); + return Ok(entry.data); + } + // Entry has expired, continue to recompute + } else { + // No expiration, return cached data + return Ok(entry.data); + } + } + + println!("Cache miss"); + + // Not in cache or expired, compute the value + let data = compute(); + let expires_at = ttl.map(|duration| SystemTime::now() + duration); + let entry = CacheEntry { + data: data.clone(), + expires_at, + }; + + // Store in cache + self.store_entry(key, &entry)?; + + Ok(data) + } + + /// Cache a value with no expiration + pub fn remember(&self, key: &str, compute: impl FnOnce() -> T) -> Result + where + T: Serialize + DeserializeOwned + Clone, + { + self.remember_with_ttl(key, None, compute) + } + + /// Get a cached value if it exists and hasn't expired + pub fn get(&self, key: &str) -> Result, CacheError> + where + T: Serialize + DeserializeOwned, + { + if let Some(entry) = self.get_cached_entry::(key)? { + // Check if entry has expired + if let Some(expires_at) = entry.expires_at { + if SystemTime::now() < expires_at { + return Ok(Some(entry.data)); + } + // Entry has expired, remove it and return None + self.forget(key)?; + return Ok(None); + } else { + // No expiration, return cached data + return Ok(Some(entry.data)); + } + } + Ok(None) + } + + /// Store a value in the cache with optional TTL + pub fn put(&self, key: &str, value: T, ttl: Option) -> Result<(), CacheError> + where + T: Serialize, + { + let expires_at = ttl.map(|duration| SystemTime::now() + duration); + let entry = CacheEntry { + data: value, + expires_at, + }; + self.store_entry(key, &entry) + } + + /// Remove a key from the cache + pub fn forget(&self, key: &str) -> Result<(), CacheError> { + let write_txn = self.db.begin_write()?; + { + let mut table = write_txn.open_table(CACHE_TABLE)?; + table.remove(key)?; + } + write_txn.commit()?; + Ok(()) + } + + /// Clear all cached entries + pub fn clear(&self) -> Result<(), CacheError> { + let write_txn = self.db.begin_write()?; + { + let mut table = write_txn.open_table(CACHE_TABLE)?; + // Remove all entries + let keys: Vec = { + let mut keys = Vec::new(); + let mut iter = table.iter()?; + while let Some(Ok((key, _))) = iter.next() { + keys.push(key.value().to_string()); + } + keys + }; + + for key in keys { + table.remove(key.as_str())?; + } + } + write_txn.commit()?; + Ok(()) + } + + fn get_cached_entry(&self, key: &str) -> Result>, CacheError> + where + T: DeserializeOwned, + { + let read_txn = self.db.begin_read()?; + let table = read_txn.open_table(CACHE_TABLE)?; + + if let Some(cached_bytes) = table.get(key)? { + match bincode::deserialize::>(cached_bytes.value()) { + Ok(entry) => Ok(Some(entry)), + Err(_) => { + // Failed to deserialize, probably corrupted or wrong format + Ok(None) + } + } + } else { + Ok(None) + } + } + + fn store_entry(&self, key: &str, entry: &CacheEntry) -> Result<(), CacheError> + where + T: Serialize, + { + let write_txn = self.db.begin_write()?; + { + let mut table = write_txn.open_table(CACHE_TABLE)?; + let serialized = bincode::serialize(entry) + .map_err(|e| CacheError::SerializationError(e.to_string()))?; + table.insert(key, serialized.as_slice())?; + } + write_txn.commit()?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + use tempfile::NamedTempFile; + + #[test] + fn test_cache_remember() { + let temp_file = NamedTempFile::new().unwrap(); + let cache = Cache::new(temp_file.path()).unwrap(); + + let result = cache + .remember("test_key", || "computed_value".to_string()) + .unwrap(); + assert_eq!(result, "computed_value"); + + // Second call should return cached value + let result2 = cache + .remember("test_key", || "different_value".to_string()) + .unwrap(); + assert_eq!(result2, "computed_value"); + } + + #[test] + fn test_cache_ttl() { + let temp_file = NamedTempFile::new().unwrap(); + let cache = Cache::new(temp_file.path()).unwrap(); + + // Cache with very short TTL + let result = cache + .remember_with_ttl("ttl_key", Some(Duration::from_millis(1)), || { + "cached_value".to_string() + }) + .unwrap(); + assert_eq!(result, "cached_value"); + + // Wait for expiration + std::thread::sleep(Duration::from_millis(10)); + + // Should recompute after expiration + let result2 = cache + .remember_with_ttl("ttl_key", Some(Duration::from_millis(1)), || { + "new_value".to_string() + }) + .unwrap(); + assert_eq!(result2, "new_value"); + } +} diff --git a/waycast-core/src/lib.rs b/waycast-core/src/lib.rs index 2f24e5b..8400240 100644 --- a/waycast-core/src/lib.rs +++ b/waycast-core/src/lib.rs @@ -1,6 +1,8 @@ use std::collections::HashMap; use std::sync::Arc; +pub mod cache; + #[derive(Debug)] pub enum LaunchError { CouldNotLaunch(String), diff --git a/waycast-gtk/Cargo.toml b/waycast-gtk/Cargo.toml index e0c6c32..9e4cc3d 100644 --- a/waycast-gtk/Cargo.toml +++ b/waycast-gtk/Cargo.toml @@ -13,4 +13,7 @@ waycast-plugins = { path = "../waycast-plugins" } gio = "0.21.1" glib = "0.21.1" gtk = { version = "0.10.0", package = "gtk4" } -gtk4-layer-shell = "0.6.1" \ No newline at end of file +gtk4-layer-shell = "0.6.1" +redb = "3.0.1" +serde = "1.0.219" +bincode = "2.0.1" diff --git a/waycast-gtk/src/main.rs b/waycast-gtk/src/main.rs index 2cbbca0..6326fab 100644 --- a/waycast-gtk/src/main.rs +++ b/waycast-gtk/src/main.rs @@ -1,50 +1,70 @@ -mod ui; -mod util; +use std::time::Duration; -use gtk::prelude::*; -use gtk::Application; -use ui::gtk::GtkLauncherUI; -use waycast_core::WaycastLauncher; +use waycast_core::cache::Cache; fn main() { - let app = Application::builder() - .application_id("dev.thegrind.waycast") - .build(); + let cache: Cache; + if let Ok(db) = waycast_core::cache::new("waycast_cache") { + cache = db; + } else { + panic!("Failed to open database"); + } - app.connect_activate(|app| { - let mut file_search_plugin = waycast_plugins::file_search::new(); - - if let Err(e) = file_search_plugin.add_search_path("/home/javi/working-files/DJ Music/") { - eprintln!("{}", e) - } - - let mut project_plugin = waycast_plugins::projects::new(); - if let Err(e) = project_plugin.add_search_path("/home/javi/projects") { - eprintln!("{}", e) - } - - // Create the core launcher - let launcher = WaycastLauncher::new() - .add_plugin(Box::new(waycast_plugins::drun::new())) - .add_plugin(Box::new(file_search_plugin)) - .add_plugin(Box::new(project_plugin)) - .init(); - - // Create and show the GTK UI - let ui = GtkLauncherUI::new(app, launcher); - - // Apply built-in default styles - if let Err(e) = ui.apply_default_css() { - eprintln!("Warning: Could not apply default styles: {}", e); - } - - // Optionally apply user CSS overrides - // if let Err(_) = ui.apply_css("waycast.css") { - // // Silently ignore if user hasn't provided custom CSS - // } - - ui.show(); + let result = cache.remember_with_ttl("test_key", Some(Duration::from_secs(10)), || { + String::from("my cool string value") }); - app.run(); + if let Ok(val) = result { + println!("{}", val); + } } +// mod ui; +// mod util; + +// use gtk::prelude::*; +// use gtk::Application; +// use ui::gtk::GtkLauncherUI; +// use waycast_core::WaycastLauncher; + +// fn main() { +// let app = Application::builder() +// .application_id("dev.thegrind.waycast") +// .build(); + +// app.connect_activate(|app| { +// let mut file_search_plugin = waycast_plugins::file_search::new(); + +// if let Err(e) = file_search_plugin.add_search_path("/home/javi/working-files/DJ Music/") { +// eprintln!("{}", e) +// } + +// let mut project_plugin = waycast_plugins::projects::new(); +// if let Err(e) = project_plugin.add_search_path("/home/javi/projects") { +// eprintln!("{}", e) +// } + +// // Create the core launcher +// let launcher = WaycastLauncher::new() +// .add_plugin(Box::new(waycast_plugins::drun::new())) +// .add_plugin(Box::new(file_search_plugin)) +// .add_plugin(Box::new(project_plugin)) +// .init(); + +// // Create and show the GTK UI +// let ui = GtkLauncherUI::new(app, launcher); + +// // Apply built-in default styles +// if let Err(e) = ui.apply_default_css() { +// eprintln!("Warning: Could not apply default styles: {}", e); +// } + +// // Optionally apply user CSS overrides +// // if let Err(_) = ui.apply_css("waycast.css") { +// // // Silently ignore if user hasn't provided custom CSS +// // } + +// ui.show(); +// }); + +// app.run(); +// }