File search on the way

This commit is contained in:
Javier Feliz 2025-09-04 18:00:05 -04:00
parent 53e45fa232
commit 47dd0c752a
7 changed files with 242 additions and 10 deletions

98
Cargo.lock generated
View File

@ -89,6 +89,27 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
[[package]]
name = "directories"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.60.2",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -583,6 +604,16 @@ version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libredox"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [
"bitflags",
"libc",
]
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.13" version = "0.4.13"
@ -658,6 +689,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "pango" name = "pango"
version = "0.21.1" version = "0.21.1"
@ -727,6 +764,17 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "redox_users"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]] [[package]]
name = "relm4" name = "relm4"
version = "0.10.0" version = "0.10.0"
@ -787,6 +835,15 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@ -879,6 +936,26 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
[[package]]
name = "thiserror"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.47.1" version = "1.47.1"
@ -990,6 +1067,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.1+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"
@ -1057,6 +1144,7 @@ dependencies = [
name = "waycast" name = "waycast"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"directories",
"gio", "gio",
"glib", "glib",
"gtk4", "gtk4",
@ -1064,6 +1152,16 @@ dependencies = [
"relm4", "relm4",
"relm4-components", "relm4-components",
"tracker", "tracker",
"walkdir",
]
[[package]]
name = "winapi-util"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
dependencies = [
"windows-sys 0.60.2",
] ]
[[package]] [[package]]

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
directories = "6.0.0"
gio = "0.21.1" gio = "0.21.1"
glib = "0.21.1" glib = "0.21.1"
gtk = { version = "0.10.0", package = "gtk4" } gtk = { version = "0.10.0", package = "gtk4" }
@ -11,3 +12,4 @@ gtk4-layer-shell = "0.6.1"
relm4 = "0.10.0" relm4 = "0.10.0"
relm4-components = "0.10.0" relm4-components = "0.10.0"
tracker = "0.2.2" tracker = "0.2.2"
walkdir = "2.5.0"

View File

@ -15,6 +15,7 @@ pub trait LauncherListItem {
} }
pub trait LauncherPlugin { pub trait LauncherPlugin {
fn init(&self);
fn name(&self) -> String; fn name(&self) -> String;
fn priority(&self) -> i32; fn priority(&self) -> i32;
fn description(&self) -> Option<String>; fn description(&self) -> Option<String>;

View File

@ -1,8 +1,13 @@
use gtk::Application; use gtk::Application;
use gtk::prelude::*; use gtk::prelude::*;
use std::env;
use waycast::plugins; use waycast::plugins;
use waycast::ui::WaycastLauncher; use waycast::ui::WaycastLauncher;
// TODO: Add an init() function to the launcher plugin spec
// that will get called when loaded. That way plugins like
// file search can index the file system on init instead
// of on the fly
fn main() { fn main() {
let app = Application::builder() let app = Application::builder()
.application_id("dev.thegrind.waycast") .application_id("dev.thegrind.waycast")
@ -11,7 +16,7 @@ fn main() {
app.connect_activate(|app| { app.connect_activate(|app| {
let launcher = WaycastLauncher::new() let launcher = WaycastLauncher::new()
.add_plugin(plugins::drun::DrunPlugin {}) .add_plugin(plugins::drun::DrunPlugin {})
.add_plugin(plugins::file_search::FileSearchPlugin {}) .add_plugin(plugins::file_search::FileSearchPlugin::new())
.initialize(app); .initialize(app);
launcher.borrow().show(); launcher.borrow().show();

View File

@ -85,6 +85,10 @@ pub fn get_desktop_entries() -> Vec<DesktopEntry> {
pub struct DrunPlugin {} pub struct DrunPlugin {}
impl LauncherPlugin for DrunPlugin { impl LauncherPlugin for DrunPlugin {
fn init(&self) {
// TODO: Load apps into memory
// TODO: Find and cache Icons
}
fn name(&self) -> String { fn name(&self) -> String {
return String::from("drun"); return String::from("drun");
} }

View File

@ -1,28 +1,128 @@
use directories::UserDirs;
use gio::prelude::FileExt;
use glib::object::Cast;
use std::path::PathBuf;
use std::{cell::RefCell, env};
use walkdir::{DirEntry, WalkDir};
use crate::{LaunchError, LauncherListItem, LauncherPlugin}; use crate::{LaunchError, LauncherListItem, LauncherPlugin};
#[derive(Clone)]
struct FileEntry { struct FileEntry {
title: String, path: PathBuf,
} }
impl LauncherListItem for ExampleEntry { impl FileEntry {
fn from(entry: DirEntry) -> Self {
return FileEntry {
path: entry.into_path(),
};
}
}
impl LauncherListItem for FileEntry {
fn title(&self) -> String { fn title(&self) -> String {
return self.title.to_string(); return String::from(self.path.file_name().unwrap().to_string_lossy());
} }
fn description(&self) -> Option<String> { fn description(&self) -> Option<String> {
return None; return None;
} }
fn execute(&self) -> Result<(), LaunchError> { fn execute(&self) -> Result<(), LaunchError> {
println!("Sample item clicked: {}", self.title); let file_uri = gio::File::for_path(&self.path);
Ok(()) match gio::AppInfo::launch_default_for_uri(
file_uri.uri().as_str(),
None::<&gio::AppLaunchContext>,
) {
Err(_) => Err(LaunchError::CouldNotLaunch(
"Error opening file".to_string(),
)),
Ok(()) => Ok(()),
} }
}
fn icon(&self) -> String { fn icon(&self) -> String {
return String::from("vscode"); let (content_type, _) = gio::content_type_guess(Some(&self.path), None);
let icon = gio::content_type_get_icon(&content_type);
if let Some(themed_icon) = icon.downcast_ref::<gio::ThemedIcon>() {
if let Some(icon_name) = themed_icon.names().first() {
return icon_name.to_string();
}
}
String::from("text-x-generic")
} }
} }
pub struct FileSearchPlugin {} pub struct FileSearchPlugin {
search_paths: Vec<PathBuf>,
skip_dirs: Vec<String>,
// Running list of files in memory
files: RefCell<Vec<FileEntry>>,
}
impl FileSearchPlugin {
pub fn new() -> Self {
return FileSearchPlugin {
search_paths: Vec::new(),
skip_dirs: vec![
String::from("vendor"),
String::from("node_modules"),
String::from("cache"),
String::from("zig-cache"),
],
files: RefCell::new(Vec::new()),
};
}
}
fn skip_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.starts_with("."))
.unwrap_or(false)
}
fn skip_dir(entry: &DirEntry, dirs: &Vec<String>) -> bool {
entry
.file_name()
.to_str()
.map(|n| dirs.contains(&String::from(n)))
.unwrap_or(false)
}
impl LauncherPlugin for FileSearchPlugin { impl LauncherPlugin for FileSearchPlugin {
fn init(&self) {
// let home = env::home_dir().unwrap();
if let Some(ud) = UserDirs::new() {
let scan = [
ud.document_dir(),
ud.picture_dir(),
ud.audio_dir(),
ud.video_dir(),
];
for p in scan {
match p {
Some(path) => {
let walker = WalkDir::new(path).into_iter();
for entry in walker
.filter_entry(|e| !skip_hidden(e) && !skip_dir(e, &self.skip_dirs))
.filter_map(|e| e.ok())
{
if entry.path().is_file() {
self.files.borrow_mut().push(FileEntry::from(entry));
}
}
}
None => continue,
}
}
}
}
fn name(&self) -> String { fn name(&self) -> String {
return String::from("File search"); return String::from("File search");
} }
@ -48,6 +148,21 @@ impl LauncherPlugin for FileSearchPlugin {
} }
fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>> { fn filter(&self, query: &str) -> Vec<Box<dyn LauncherListItem>> {
self.default_list() if query.is_empty() {
return self.default_list();
}
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
let files = self.files.borrow();
for f in files.iter() {
if let Some(file_name) = f.path.file_name() {
let cmp = file_name.to_string_lossy().to_lowercase();
if cmp.contains(&query.to_lowercase()) {
entries.push(Box::new(f.clone()));
}
}
}
entries
} }
} }

View File

@ -10,6 +10,7 @@ use gtk4_layer_shell as layerShell;
use layerShell::LayerShell; use layerShell::LayerShell;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
mod launcher_builder; mod launcher_builder;
@ -155,6 +156,7 @@ impl WaycastLauncher {
plugins_by_prefix, plugins_by_prefix,
})); }));
model.borrow().init_plugins();
// Populate the list // Populate the list
model.borrow_mut().populate_list(); model.borrow_mut().populate_list();
@ -165,7 +167,6 @@ impl WaycastLauncher {
let model_clone = model.clone(); let model_clone = model.clone();
search_input.connect_changed(move |entry| { search_input.connect_changed(move |entry| {
let query = entry.text().to_string(); let query = entry.text().to_string();
println!("query: {query}");
model_clone.borrow_mut().filter_list(&query); model_clone.borrow_mut().filter_list(&query);
}); });
@ -263,6 +264,12 @@ impl WaycastLauncher {
model model
} }
fn init_plugins(&self) {
for plugin in &self.plugins {
plugin.init();
}
}
pub fn clear_list_ui(&self) { pub fn clear_list_ui(&self) {
while let Some(child) = self.list_box.first_child() { while let Some(child) = self.list_box.first_child() {
self.list_box.remove(&child); self.list_box.remove(&child);