Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1aa92ee1e5 | |||
| 9532b3cfde | |||
| 551e5d74ae | |||
| 60eaffb2ab | |||
| 6d8d4a9f89 |
34
Cargo.lock
generated
34
Cargo.lock
generated
@@ -2373,7 +2373,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
@@ -2402,7 +2402,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-lua"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"chrono",
|
||||
@@ -2420,7 +2420,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-api"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"serde",
|
||||
@@ -2428,7 +2428,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-bookmarks"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
@@ -2440,7 +2440,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-calculator"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"meval",
|
||||
@@ -2449,7 +2449,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-clipboard"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"owlry-plugin-api",
|
||||
@@ -2457,7 +2457,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-emoji"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"owlry-plugin-api",
|
||||
@@ -2465,7 +2465,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-filesearch"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
@@ -2474,7 +2474,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-media"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"owlry-plugin-api",
|
||||
@@ -2482,7 +2482,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-pomodoro"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
@@ -2494,7 +2494,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-scripts"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
@@ -2503,7 +2503,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-ssh"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
@@ -2512,7 +2512,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-system"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"owlry-plugin-api",
|
||||
@@ -2520,7 +2520,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-systemd"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"owlry-plugin-api",
|
||||
@@ -2528,7 +2528,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-weather"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"dirs",
|
||||
@@ -2541,7 +2541,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-plugin-websearch"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"abi_stable",
|
||||
"owlry-plugin-api",
|
||||
@@ -2549,7 +2549,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "owlry-rune"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dirs",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-lua"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-api"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-bookmarks"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-calculator"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-clipboard"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-emoji"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-filesearch"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-media"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-pomodoro"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-scripts"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-ssh"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-system"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-systemd"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-weather"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-plugin-websearch"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry-rune"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition = "2024"
|
||||
rust-version = "1.90"
|
||||
description = "Rune scripting runtime for owlry plugins"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "owlry"
|
||||
version = "0.4.8"
|
||||
version = "0.4.9"
|
||||
edition = "2024"
|
||||
rust-version = "1.90"
|
||||
description = "A lightweight, owl-themed application launcher for Wayland"
|
||||
|
||||
@@ -99,23 +99,57 @@ pub fn frecency_file() -> Option<PathBuf> {
|
||||
// =============================================================================
|
||||
|
||||
/// System data directories for applications (XDG_DATA_DIRS)
|
||||
///
|
||||
/// Follows the XDG Base Directory Specification:
|
||||
/// - $XDG_DATA_HOME/applications (defaults to ~/.local/share/applications)
|
||||
/// - $XDG_DATA_DIRS/*/applications (defaults to /usr/local/share:/usr/share)
|
||||
/// - Additional Flatpak and Snap directories
|
||||
pub fn system_data_dirs() -> Vec<PathBuf> {
|
||||
let mut dirs = Vec::new();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
|
||||
// User data directory first
|
||||
// Helper to add unique directories
|
||||
let mut add_dir = |path: PathBuf| {
|
||||
if seen.insert(path.clone()) {
|
||||
dirs.push(path);
|
||||
}
|
||||
};
|
||||
|
||||
// 1. User data directory first (highest priority)
|
||||
if let Some(data) = data_home() {
|
||||
dirs.push(data.join("applications"));
|
||||
add_dir(data.join("applications"));
|
||||
}
|
||||
|
||||
// System directories
|
||||
dirs.push(PathBuf::from("/usr/share/applications"));
|
||||
dirs.push(PathBuf::from("/usr/local/share/applications"));
|
||||
// 2. XDG_DATA_DIRS - parse the environment variable
|
||||
// Default per spec: /usr/local/share:/usr/share
|
||||
let xdg_data_dirs = std::env::var("XDG_DATA_DIRS")
|
||||
.unwrap_or_else(|_| "/usr/local/share:/usr/share".to_string());
|
||||
|
||||
// Flatpak directories
|
||||
if let Some(data) = data_home() {
|
||||
dirs.push(data.join("flatpak/exports/share/applications"));
|
||||
for dir in xdg_data_dirs.split(':') {
|
||||
if !dir.is_empty() {
|
||||
add_dir(PathBuf::from(dir).join("applications"));
|
||||
}
|
||||
}
|
||||
dirs.push(PathBuf::from("/var/lib/flatpak/exports/share/applications"));
|
||||
|
||||
// 3. Always include standard system directories as fallback
|
||||
// Some environments set XDG_DATA_DIRS without including these
|
||||
add_dir(PathBuf::from("/usr/share/applications"));
|
||||
add_dir(PathBuf::from("/usr/local/share/applications"));
|
||||
|
||||
// 4. Flatpak directories (user and system)
|
||||
if let Some(data) = data_home() {
|
||||
add_dir(data.join("flatpak/exports/share/applications"));
|
||||
}
|
||||
add_dir(PathBuf::from("/var/lib/flatpak/exports/share/applications"));
|
||||
|
||||
// 5. Snap directories
|
||||
add_dir(PathBuf::from("/var/lib/snapd/desktop/applications"));
|
||||
|
||||
// 6. Nix directories (common on NixOS)
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
add_dir(home.join(".nix-profile/share/applications"));
|
||||
}
|
||||
add_dir(PathBuf::from("/run/current-system/sw/share/applications"));
|
||||
|
||||
dirs
|
||||
}
|
||||
|
||||
@@ -98,6 +98,15 @@ impl Provider for ApplicationProvider {
|
||||
// Empty locale list for default locale
|
||||
let locales: &[&str] = &[];
|
||||
|
||||
// Get current desktop environment(s) for OnlyShowIn/NotShowIn filtering
|
||||
// XDG_CURRENT_DESKTOP can be colon-separated (e.g., "ubuntu:GNOME")
|
||||
let current_desktops: Vec<String> = std::env::var("XDG_CURRENT_DESKTOP")
|
||||
.unwrap_or_default()
|
||||
.split(':')
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
for path in Iter::new(dirs.into_iter()) {
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
@@ -125,6 +134,24 @@ impl Provider for ApplicationProvider {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply OnlyShowIn/NotShowIn filters only if we know the current desktop
|
||||
// If XDG_CURRENT_DESKTOP is not set, show all apps (don't filter)
|
||||
if !current_desktops.is_empty() {
|
||||
// OnlyShowIn: if set, current desktop must be in the list
|
||||
if desktop_entry.only_show_in().is_some_and(|only| {
|
||||
!current_desktops.iter().any(|de| only.contains(&de.as_str()))
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// NotShowIn: if current desktop is in the list, skip
|
||||
if desktop_entry.not_show_in().is_some_and(|not| {
|
||||
current_desktops.iter().any(|de| not.contains(&de.as_str()))
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let name = match desktop_entry.name(locales) {
|
||||
Some(n) => n.to_string(),
|
||||
None => continue,
|
||||
@@ -135,12 +162,17 @@ impl Provider for ApplicationProvider {
|
||||
None => continue,
|
||||
};
|
||||
|
||||
// Extract categories as tags (lowercase for consistency)
|
||||
let tags: Vec<String> = desktop_entry
|
||||
// Extract categories and keywords as tags (lowercase for consistency)
|
||||
let mut tags: Vec<String> = desktop_entry
|
||||
.categories()
|
||||
.map(|cats| cats.into_iter().map(|s| s.to_lowercase()).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
// Add keywords for searchability (e.g., Nautilus has Name=Files but Keywords contains "nautilus")
|
||||
if let Some(keywords) = desktop_entry.keywords(locales) {
|
||||
tags.extend(keywords.into_iter().map(|s| s.to_lowercase()));
|
||||
}
|
||||
|
||||
let item = LaunchItem {
|
||||
id: path.to_string_lossy().to_string(),
|
||||
name,
|
||||
@@ -157,6 +189,13 @@ impl Provider for ApplicationProvider {
|
||||
|
||||
debug!("Found {} applications", self.items.len());
|
||||
|
||||
#[cfg(feature = "dev-logging")]
|
||||
debug!(
|
||||
"XDG_CURRENT_DESKTOP={:?}, scanned dirs count={}",
|
||||
current_desktops,
|
||||
Self::get_application_dirs().len()
|
||||
);
|
||||
|
||||
// Sort alphabetically by name
|
||||
self.items.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
|
||||
}
|
||||
@@ -210,4 +249,18 @@ mod tests {
|
||||
"bash -c 'echo %u'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clean_desktop_exec_preserves_env() {
|
||||
// env VAR=value pattern should be preserved
|
||||
assert_eq!(
|
||||
clean_desktop_exec_field("env GDK_BACKEND=x11 UBUNTU_MENUPROXY=0 audacity %F"),
|
||||
"env GDK_BACKEND=x11 UBUNTU_MENUPROXY=0 audacity"
|
||||
);
|
||||
// Multiple env vars
|
||||
assert_eq!(
|
||||
clean_desktop_exec_field("env FOO=bar BAZ=qux myapp %u"),
|
||||
"env FOO=bar BAZ=qux myapp"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1286,17 +1286,81 @@ impl MainWindow {
|
||||
info!("Launching: {} ({})", item.name, item.command);
|
||||
|
||||
#[cfg(feature = "dev-logging")]
|
||||
debug!("[UI] Launch details: terminal={}, provider={:?}", item.terminal, item.provider);
|
||||
debug!("[UI] Launch details: terminal={}, provider={:?}, id={}", item.terminal, item.provider, item.id);
|
||||
|
||||
let cmd = if item.terminal {
|
||||
let terminal = config.general.terminal_command.as_deref().unwrap_or("xterm");
|
||||
format!("{} -e {}", terminal, item.command)
|
||||
// Check if this is a desktop application (has .desktop file as ID)
|
||||
let is_desktop_app = matches!(item.provider, ProviderType::Application)
|
||||
&& item.id.ends_with(".desktop");
|
||||
|
||||
// Desktop files should be launched via proper launchers that implement the
|
||||
// freedesktop Desktop Entry spec (D-Bus activation, field codes, env vars, etc.)
|
||||
// We delegate to: uwsm (if configured), gio launch, or gtk-launch as fallback.
|
||||
//
|
||||
// Non-desktop items (commands, plugins) use sh -c for shell execution.
|
||||
let result = if is_desktop_app {
|
||||
Self::launch_desktop_file(&item.id, config)
|
||||
} else {
|
||||
item.command.clone()
|
||||
Self::launch_command(&item.command, item.terminal, config)
|
||||
};
|
||||
|
||||
// Detect if this is a shell command vs an application launch
|
||||
// Shell commands: playerctl, dbus-send, systemctl, journalctl, or anything with shell operators
|
||||
if let Err(e) = result {
|
||||
let msg = format!("Failed to launch '{}': {}", item.name, e);
|
||||
log::error!("{}", msg);
|
||||
crate::notify::notify("Launch failed", &msg);
|
||||
}
|
||||
}
|
||||
|
||||
/// Launch a .desktop file using gio (GLib's desktop entry launcher).
|
||||
///
|
||||
/// gio is always available as it's part of glib2, which is a hard dependency
|
||||
/// of GTK4. It handles D-Bus activation, field codes, Terminal flag, etc.
|
||||
/// per the freedesktop Desktop Entry specification.
|
||||
///
|
||||
/// Optionally wraps with a session manager (uwsm, hyprctl) for proper
|
||||
/// process tracking in Wayland compositors.
|
||||
fn launch_desktop_file(desktop_path: &str, config: &Config) -> std::io::Result<std::process::Child> {
|
||||
use std::path::Path;
|
||||
|
||||
// Check if desktop file exists
|
||||
if !Path::new(desktop_path).exists() {
|
||||
let msg = format!("Desktop file not found: {}", desktop_path);
|
||||
log::error!("{}", msg);
|
||||
crate::notify::notify("Launch failed", &msg);
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::NotFound, msg));
|
||||
}
|
||||
|
||||
match &config.general.launch_wrapper {
|
||||
// With wrapper: wrapper manages the process, gio handles the .desktop file
|
||||
Some(wrapper) if !wrapper.is_empty() => {
|
||||
info!("Launching via {} + gio launch: {}", wrapper, desktop_path);
|
||||
let mut parts: Vec<&str> = wrapper.split_whitespace().collect();
|
||||
let cmd = parts.remove(0);
|
||||
Command::new(cmd)
|
||||
.args(&parts)
|
||||
.args(["gio", "launch"])
|
||||
.arg(desktop_path)
|
||||
.spawn()
|
||||
}
|
||||
// No wrapper: use gio directly
|
||||
_ => {
|
||||
info!("Launching via gio launch: {}", desktop_path);
|
||||
Command::new("gio")
|
||||
.args(["launch", desktop_path])
|
||||
.spawn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Launch a shell command (for non-desktop items like PATH commands, plugins, etc.)
|
||||
fn launch_command(command: &str, terminal: bool, config: &Config) -> std::io::Result<std::process::Child> {
|
||||
let cmd = if terminal {
|
||||
let terminal_cmd = config.general.terminal_command.as_deref().unwrap_or("xterm");
|
||||
format!("{} -e {}", terminal_cmd, command)
|
||||
} else {
|
||||
command.to_string()
|
||||
};
|
||||
|
||||
// Detect shell commands that shouldn't use a wrapper
|
||||
let is_shell_command = cmd.starts_with("playerctl ")
|
||||
|| cmd.starts_with("dbus-send ")
|
||||
|| cmd.starts_with("systemctl ")
|
||||
@@ -1307,28 +1371,19 @@ impl MainWindow {
|
||||
|| cmd.contains(" > ")
|
||||
|| cmd.contains(" < ");
|
||||
|
||||
// Use launch wrapper if configured (uwsm, hyprctl, etc.)
|
||||
// But skip wrapper for shell commands - they need sh -c
|
||||
let result = match &config.general.launch_wrapper {
|
||||
match &config.general.launch_wrapper {
|
||||
Some(wrapper) if !wrapper.is_empty() && !is_shell_command => {
|
||||
info!("Using launch wrapper: {}", wrapper);
|
||||
// Split wrapper into command and args (e.g., "uwsm app --" -> ["uwsm", "app", "--"])
|
||||
let mut wrapper_parts: Vec<&str> = wrapper.split_whitespace().collect();
|
||||
if wrapper_parts.is_empty() {
|
||||
Command::new("sh").arg("-c").arg(&cmd).spawn()
|
||||
} else {
|
||||
let wrapper_cmd = wrapper_parts.remove(0);
|
||||
Command::new(wrapper_cmd)
|
||||
.args(&wrapper_parts)
|
||||
.arg(&cmd)
|
||||
.spawn()
|
||||
}
|
||||
info!("Launching command via {}: {}", wrapper, cmd);
|
||||
let mut parts: Vec<&str> = wrapper.split_whitespace().collect();
|
||||
let wrapper_cmd = parts.remove(0);
|
||||
Command::new(wrapper_cmd)
|
||||
.args(&parts)
|
||||
.args(["sh", "-c", &cmd])
|
||||
.spawn()
|
||||
}
|
||||
_ => {
|
||||
Command::new("sh").arg("-c").arg(&cmd).spawn()
|
||||
}
|
||||
_ => Command::new("sh").arg("-c").arg(&cmd).spawn(),
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
log::error!("Failed to launch '{}': {}", item.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user