mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-21 16:21:29 +02:00
feat(bar): improve path handling on apps widget
This commit improves path handling for commands and icons in the new Application widget by making use of PathExt::replace_env when loading the user-specified ApplicationsConfig. Crucially for scoop users, this means that user-agnostic references to scoop apps can now be made like this: ``` $Env:USERPROFILE/scoop/apps/zed-nightly/current/zed.exe ``` When attempting to look up an icon for a command, we now split the command on ".exe", and if this is a complete path to a file, we try to use it to extract an icon, otherwise we try to resolve a complete path using "which" before doing the same.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2998,6 +2998,7 @@ dependencies = [
|
|||||||
"sysinfo 0.34.2",
|
"sysinfo 0.34.2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"which",
|
||||||
"windows 0.61.1",
|
"windows 0.61.1",
|
||||||
"windows-core 0.61.0",
|
"windows-core 0.61.0",
|
||||||
"windows-icons 0.1.0 (git+https://github.com/LGUG2Z/windows-icons?rev=0c9d7ee1b807347c507d3a9862dd007b4d3f4354)",
|
"windows-icons 0.1.0 (git+https://github.com/LGUG2Z/windows-icons?rev=0c9d7ee1b807347c507d3a9862dd007b4d3f4354)",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ starship-battery = "0.10"
|
|||||||
sysinfo = { workspace = true }
|
sysinfo = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
|
which = { workspace = true }
|
||||||
windows = { workspace = true }
|
windows = { workspace = true }
|
||||||
windows-core = { workspace = true }
|
windows-core = { workspace = true }
|
||||||
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "0c9d7ee1b807347c507d3a9862dd007b4d3f4354" }
|
windows-icons = { git = "https://github.com/LGUG2Z/windows-icons", rev = "0c9d7ee1b807347c507d3a9862dd007b4d3f4354" }
|
||||||
|
|||||||
@@ -19,13 +19,16 @@ use eframe::egui::Ui;
|
|||||||
use eframe::egui::Vec2;
|
use eframe::egui::Vec2;
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use image::RgbaImage;
|
use image::RgbaImage;
|
||||||
|
use komorebi_client::PathExt;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tracing;
|
use tracing;
|
||||||
|
use which::which;
|
||||||
|
|
||||||
/// Minimum interval between consecutive application launches to prevent accidental spamming.
|
/// Minimum interval between consecutive application launches to prevent accidental spamming.
|
||||||
const MIN_LAUNCH_INTERVAL: Duration = Duration::from_millis(800);
|
const MIN_LAUNCH_INTERVAL: Duration = Duration::from_millis(800);
|
||||||
@@ -118,28 +121,41 @@ impl From<&ApplicationsConfig> for Applications {
|
|||||||
fn from(applications_config: &ApplicationsConfig) -> Self {
|
fn from(applications_config: &ApplicationsConfig) -> Self {
|
||||||
// Allow immediate launch by initializing last_launch in the past.
|
// Allow immediate launch by initializing last_launch in the past.
|
||||||
let last_launch = Instant::now() - 2 * MIN_LAUNCH_INTERVAL;
|
let last_launch = Instant::now() - 2 * MIN_LAUNCH_INTERVAL;
|
||||||
|
let mut applications_config = applications_config.clone();
|
||||||
let items = applications_config
|
let items = applications_config
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter_mut()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, app_config)| App {
|
.map(|(index, app_config)| {
|
||||||
enable: app_config.enable.unwrap_or(applications_config.enable),
|
app_config.command = app_config
|
||||||
name: app_config
|
.command
|
||||||
.name
|
.replace_env()
|
||||||
.is_empty()
|
.to_string_lossy()
|
||||||
.then(|| format!("App {}", index + 1))
|
.to_string();
|
||||||
.unwrap_or_else(|| app_config.name.clone()),
|
|
||||||
icon: Icon::try_from(app_config),
|
if let Some(icon) = &mut app_config.icon {
|
||||||
command: app_config.command.clone(),
|
*icon = icon.replace_env().to_string_lossy().to_string();
|
||||||
display: app_config
|
}
|
||||||
.display
|
|
||||||
.or(applications_config.display)
|
App {
|
||||||
.unwrap_or_default(),
|
enable: app_config.enable.unwrap_or(applications_config.enable),
|
||||||
show_command_on_hover: app_config
|
name: app_config
|
||||||
.show_command_on_hover
|
.name
|
||||||
.or(applications_config.show_command_on_hover)
|
.is_empty()
|
||||||
.unwrap_or(false),
|
.then(|| format!("App {}", index + 1))
|
||||||
last_launch,
|
.unwrap_or_else(|| app_config.name.clone()),
|
||||||
|
icon: Icon::try_from(app_config),
|
||||||
|
command: app_config.command.clone(),
|
||||||
|
display: app_config
|
||||||
|
.display
|
||||||
|
.or(applications_config.display)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
show_command_on_hover: app_config
|
||||||
|
.show_command_on_hover
|
||||||
|
.or(applications_config.show_command_on_hover)
|
||||||
|
.unwrap_or(false),
|
||||||
|
last_launch,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -258,7 +274,7 @@ impl Icon {
|
|||||||
pub fn try_from(config: &AppConfig) -> Option<Self> {
|
pub fn try_from(config: &AppConfig) -> Option<Self> {
|
||||||
if let Some(icon) = config.icon.as_deref().map(str::trim) {
|
if let Some(icon) = config.icon.as_deref().map(str::trim) {
|
||||||
if !icon.is_empty() {
|
if !icon.is_empty() {
|
||||||
let path = Path::new(icon);
|
let path = Path::new(&icon);
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
match image::open(path).as_ref().map(DynamicImage::to_rgba8) {
|
match image::open(path).as_ref().map(DynamicImage::to_rgba8) {
|
||||||
Ok(image) => return Some(Icon::Image(image)),
|
Ok(image) => return Some(Icon::Image(image)),
|
||||||
@@ -272,12 +288,19 @@ impl Icon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if Path::new(&config.command).is_file() {
|
let binary = PathBuf::from(config.command.split(".exe").next()?);
|
||||||
return windows_icons::get_icon_by_path(&config.command)
|
let path = if binary.is_file() {
|
||||||
.or_else(|| windows_icons_fallback::get_icon_by_path(&config.command))
|
Some(binary)
|
||||||
.map(Icon::Image);
|
} else {
|
||||||
|
which(binary).ok()
|
||||||
|
};
|
||||||
|
|
||||||
|
match path {
|
||||||
|
Some(path) => windows_icons::get_icon_by_path(&path.to_string_lossy())
|
||||||
|
.or_else(|| windows_icons_fallback::get_icon_by_path(&path.to_string_lossy()))
|
||||||
|
.map(Icon::Image),
|
||||||
|
None => None,
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders the icon in the given `Ui` context with the specified size.
|
/// Renders the icon in the given `Ui` context with the specified size.
|
||||||
|
|||||||
Reference in New Issue
Block a user