hyprswitch cache images to improve startup times
This commit is contained in:
parent
beb9b9d1c3
commit
d9ca8e62d8
3 changed files with 102 additions and 41 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2907,12 +2907,14 @@ dependencies = [
|
||||||
name = "worf-hyprswitch"
|
name = "worf-hyprswitch"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"dirs 6.0.0",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"freedesktop-icons",
|
"freedesktop-icons",
|
||||||
"hyprland",
|
"hyprland",
|
||||||
"log",
|
"log",
|
||||||
"rayon",
|
"rayon",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
|
"toml",
|
||||||
"worf",
|
"worf",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,10 @@ edition = "2024"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
worf = {path = "../../worf"}
|
worf = {path = "../../worf"}
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
log = "0.4.27"
|
|
||||||
hyprland = "0.4.0-beta.2"
|
hyprland = "0.4.0-beta.2"
|
||||||
sysinfo = "0.34.2"
|
sysinfo = "0.34.2"
|
||||||
freedesktop-icons = "0.4.0"
|
freedesktop-icons = "0.4.0"
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
|
toml = "0.8.22"
|
||||||
|
log = "0.4.27"
|
||||||
|
dirs = "6.0.0"
|
||||||
|
|
|
@ -1,35 +1,44 @@
|
||||||
use std::{env, sync::Arc};
|
|
||||||
|
|
||||||
use hyprland::{
|
use hyprland::{
|
||||||
dispatch::{DispatchType, WindowIdentifier},
|
dispatch::{DispatchType, WindowIdentifier},
|
||||||
prelude::HyprData,
|
prelude::HyprData,
|
||||||
shared::Address,
|
shared::Address,
|
||||||
};
|
};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::{env, fs, sync::Arc, thread};
|
||||||
use sysinfo::{Pid, System};
|
use sysinfo::{Pid, System};
|
||||||
use worf::{
|
use worf::{
|
||||||
|
Error,
|
||||||
config::{self, Config},
|
config::{self, Config},
|
||||||
|
desktop,
|
||||||
desktop::EntryType,
|
desktop::EntryType,
|
||||||
gui::{self, ItemProvider, MenuItem},
|
gui::{self, ItemProvider, MenuItem},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Window {
|
||||||
|
process: String,
|
||||||
|
address: Address,
|
||||||
|
icon: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct WindowProvider {
|
struct WindowProvider {
|
||||||
windows: Vec<MenuItem<String>>,
|
windows: Vec<MenuItem<Window>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowProvider {
|
impl WindowProvider {
|
||||||
fn new(cfg: &Config) -> Result<Self, String> {
|
fn new(cfg: &Config, cache: &HashMap<String, String>) -> Result<Self, String> {
|
||||||
let clients = hyprland::data::Clients::get().map_err(|e| e.to_string())?;
|
let clients = hyprland::data::Clients::get().map_err(|e| e.to_string())?;
|
||||||
let clients: Vec<_> = clients.iter().cloned().collect();
|
let clients: Vec<_> = clients.iter().cloned().collect();
|
||||||
|
|
||||||
let desktop_files = Arc::new(worf::desktop::find_desktop_files());
|
let desktop_files = Arc::new(desktop::find_desktop_files());
|
||||||
|
|
||||||
let mut sys = System::new_all();
|
let mut sys = System::new_all();
|
||||||
sys.refresh_all();
|
sys.refresh_all();
|
||||||
let sys = Arc::new(sys);
|
let sys = Arc::new(sys);
|
||||||
|
|
||||||
let menu_items: Vec<MenuItem<String>> = clients
|
let menu_items: Vec<MenuItem<_>> = clients
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.filter_map(|c| {
|
.filter_map(|c| {
|
||||||
let sys = Arc::clone(&sys);
|
let sys = Arc::clone(&sys);
|
||||||
|
@ -40,40 +49,51 @@ impl WindowProvider {
|
||||||
.map(|x| x.name().to_string_lossy().into_owned());
|
.map(|x| x.name().to_string_lossy().into_owned());
|
||||||
|
|
||||||
process_name.map(|process_name| {
|
process_name.map(|process_name| {
|
||||||
let icon = freedesktop_icons::lookup(&process_name)
|
let icon = cache.get(&process_name).cloned().or_else(|| {
|
||||||
.with_size(cfg.image_size())
|
freedesktop_icons::lookup(&process_name)
|
||||||
.with_scale(1)
|
.with_size(cfg.image_size())
|
||||||
.find()
|
.with_scale(1)
|
||||||
.map(|icon| icon.to_string_lossy().to_string())
|
.find()
|
||||||
.or_else(|| {
|
.map(|icon| icon.to_string_lossy().to_string())
|
||||||
desktop_files
|
.or_else(|| {
|
||||||
.iter()
|
desktop_files
|
||||||
.find_map(|d| match &d.entry.entry_type {
|
.iter()
|
||||||
EntryType::Application(app) => {
|
.find_map(|d| match &d.entry.entry_type {
|
||||||
if app.startup_wm_class.as_ref().is_some_and(|wm_class| {
|
EntryType::Application(app) => {
|
||||||
*wm_class.to_lowercase()
|
if app.startup_wm_class.as_ref().is_some_and(
|
||||||
== c.initial_class.to_lowercase()
|
|wm_class| {
|
||||||
}) {
|
*wm_class.to_lowercase()
|
||||||
d.entry.icon.as_ref().map(|icon| icon.content.clone())
|
== c.initial_class.to_lowercase()
|
||||||
} else {
|
},
|
||||||
None
|
) {
|
||||||
|
d.entry
|
||||||
|
.icon
|
||||||
|
.as_ref()
|
||||||
|
.map(|icon| icon.content.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
_ => None,
|
||||||
_ => None,
|
})
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
MenuItem::new(
|
MenuItem::new(
|
||||||
format!(
|
format!(
|
||||||
"[{}] \t {} \t {}",
|
"[{}] \t {} \t {}",
|
||||||
c.workspace.name, c.initial_class, c.title
|
c.workspace.name, c.initial_class, c.title
|
||||||
),
|
),
|
||||||
icon,
|
icon.clone(),
|
||||||
None,
|
None,
|
||||||
vec![].into_iter().collect(),
|
vec![].into_iter().collect(),
|
||||||
None,
|
None,
|
||||||
0.0,
|
0.0,
|
||||||
Some(c.address.to_string()),
|
Some(Window {
|
||||||
|
process: process_name,
|
||||||
|
address: c.address.clone(),
|
||||||
|
icon,
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -85,16 +105,33 @@ impl WindowProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ItemProvider<String> for WindowProvider {
|
impl ItemProvider<Window> for WindowProvider {
|
||||||
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<String>>) {
|
fn get_elements(&mut self, _: Option<&str>) -> (bool, Vec<MenuItem<Window>>) {
|
||||||
(false, self.windows.clone())
|
(false, self.windows.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sub_elements(&mut self, _: &MenuItem<String>) -> (bool, Option<Vec<MenuItem<String>>>) {
|
fn get_sub_elements(&mut self, _: &MenuItem<Window>) -> (bool, Option<Vec<MenuItem<Window>>>) {
|
||||||
(false, None)
|
(false, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_icon_cache(cache_path: &String) -> Result<HashMap<String, String>, Error> {
|
||||||
|
let toml_content =
|
||||||
|
fs::read_to_string(cache_path).map_err(|e| Error::UpdateCacheError(format!("{e}")))?;
|
||||||
|
let cache: HashMap<String, String> = toml::from_str(&toml_content)
|
||||||
|
.map_err(|_| Error::ParsingError("failed to parse cache".to_owned()))?;
|
||||||
|
Ok(cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_path() -> Result<String, Error> {
|
||||||
|
let path = dirs::cache_dir()
|
||||||
|
.map(|x| x.join("worf-hyprswitch"))
|
||||||
|
.ok_or_else(|| Error::UpdateCacheError("cannot read cache file".to_owned()))?;
|
||||||
|
|
||||||
|
desktop::create_file_if_not_exists(&path)?;
|
||||||
|
Ok(path.to_string_lossy().into_owned())
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), String> {
|
fn main() -> Result<(), String> {
|
||||||
env_logger::Builder::new()
|
env_logger::Builder::new()
|
||||||
.parse_filters(&env::var("RUST_LOG").unwrap_or_else(|_| "error".to_owned()))
|
.parse_filters(&env::var("RUST_LOG").unwrap_or_else(|_| "error".to_owned()))
|
||||||
|
@ -104,15 +141,35 @@ fn main() -> Result<(), String> {
|
||||||
let args = config::parse_args();
|
let args = config::parse_args();
|
||||||
let config = config::load_config(Some(&args)).unwrap_or(args);
|
let config = config::load_config(Some(&args)).unwrap_or(args);
|
||||||
|
|
||||||
let provider = WindowProvider::new(&config)?;
|
let cache_path = cache_path().map_err(|err| err.to_string())?;
|
||||||
|
let mut cache = load_icon_cache(&cache_path).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let provider = WindowProvider::new(&config, &cache)?;
|
||||||
|
let windows = provider.windows.clone();
|
||||||
|
let update_cache = thread::spawn(move || {
|
||||||
|
windows.iter().for_each(|item| {
|
||||||
|
if let Some(window) = &item.data {
|
||||||
|
if let Some(icon) = &window.icon {
|
||||||
|
cache.insert(window.process.clone(), icon.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let updated_toml = toml::to_string(&cache);
|
||||||
|
match updated_toml {
|
||||||
|
Ok(toml) => {
|
||||||
|
fs::write(cache_path, toml).map_err(|e| Error::UpdateCacheError(e.to_string()))
|
||||||
|
}
|
||||||
|
Err(e) => Err(Error::UpdateCacheError(e.to_string())),
|
||||||
|
}
|
||||||
|
});
|
||||||
let result = gui::show(config, provider, false, None, None).map_err(|e| e.to_string())?;
|
let result = gui::show(config, provider, false, None, None).map_err(|e| e.to_string())?;
|
||||||
if let Some(window_id) = result.menu.data {
|
|
||||||
Ok(
|
if let Some(window) = result.menu.data {
|
||||||
hyprland::dispatch::Dispatch::call(DispatchType::FocusWindow(
|
hyprland::dispatch::Dispatch::call(DispatchType::FocusWindow(WindowIdentifier::Address(
|
||||||
WindowIdentifier::Address(Address::new(window_id)),
|
window.address,
|
||||||
))
|
)))
|
||||||
.map_err(|e| e.to_string())?,
|
.map_err(|e| e.to_string())?;
|
||||||
)
|
Ok(update_cache.join().unwrap().map_err(|e| e.to_string())?)
|
||||||
} else {
|
} else {
|
||||||
Err("No window data found".to_owned())
|
Err("No window data found".to_owned())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue