use super::*;
#[cfg(target_os = "macos")]
pub(crate) fn open_with_default(path: &std::path::Path) -> std::io::Result<()> {
Command::new("open").arg(path).stdout(Stdio::null()).stderr(Stdio::null()).spawn()?;
Ok(())
}
#[cfg(target_os = "windows")]
pub(crate) fn open_with_default(path: &std::path::Path) -> std::io::Result<()> {
use std::ffi::{OsStr, c_void};
use std::os::windows::ffi::OsStrExt;
#[link(name = "shell32")]
unsafe extern "system" {
fn ShellExecuteW(hwnd: *mut c_void, operation: *const u16, file: *const u16, parameters: *const u16, directory: *const u16, show: i32) -> *mut c_void;
}
const SW_SHOWNORMAL: i32 = 1;
let wide = |text: &OsStr| text.encode_wide().chain(std::iter::once(0)).collect::<Vec<u16>>();
let operation = wide(OsStr::new("open"));
let file = wide(path.as_os_str());
let result = unsafe { ShellExecuteW(std::ptr::null_mut(), operation.as_ptr(), file.as_ptr(), std::ptr::null(), std::ptr::null(), SW_SHOWNORMAL) };
if result as usize > 32 { Ok(()) } else { Err(std::io::Error::last_os_error()) }
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
pub(crate) fn open_with_default(path: &std::path::Path) -> std::io::Result<()> {
use std::os::unix::process::CommandExt;
Command::new("xdg-open").arg(path).stdout(Stdio::null()).stderr(Stdio::null()).process_group(0).spawn()?;
Ok(())
}
pub(super) fn open_terminal(app_state: &mut AppState) {
let dir = &app_state.panel_real_dir(app_state.is_left_active);
let command = app_state.options.terminal.trim();
let result = if command.is_empty() { spawn_detached_terminal(dir) } else { spawn_configured_terminal(command, dir) };
if let Err(e) = result {
app_state.display_error(t!("Cannot open terminal: {e}", e));
}
}
fn spawn_configured_terminal(command: &str, dir: &std::path::Path) -> std::io::Result<()> {
let mut parts = command.split_whitespace().map(|part| crate::utils::substitute(part, dir.as_os_str()));
let program = parts.next().ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidInput, t!("Empty terminal command")))?;
let mut process = Command::new(program);
process.args(parts).current_dir(dir).stdout(Stdio::null()).stderr(Stdio::null());
detach(&mut process);
process.spawn()?;
Ok(())
}
#[cfg(unix)]
pub(crate) fn detach(process: &mut Command) {
use std::os::unix::process::CommandExt;
process.process_group(0);
}
#[cfg(windows)]
pub(crate) fn detach(process: &mut Command) {
use std::os::windows::process::CommandExt;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
process.creation_flags(CREATE_NEW_PROCESS_GROUP);
}
#[cfg(not(any(unix, windows)))]
pub(crate) fn detach(_process: &mut Command) {}
#[cfg(target_os = "macos")]
fn spawn_detached_terminal(dir: &std::path::Path) -> std::io::Result<()> {
use std::os::unix::process::CommandExt;
Command::new("open").arg("-a").arg("Terminal").arg(dir).stdout(Stdio::null()).stderr(Stdio::null()).process_group(0).spawn()?;
Ok(())
}
#[cfg(windows)]
fn spawn_detached_terminal(dir: &std::path::Path) -> std::io::Result<()> {
use std::os::windows::process::CommandExt;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
Command::new("cmd")
.args(["/C", "start", "cmd"])
.current_dir(dir)
.stdout(Stdio::null())
.stderr(Stdio::null())
.creation_flags(CREATE_NEW_PROCESS_GROUP)
.spawn()?;
Ok(())
}
#[cfg(not(any(target_os = "macos", windows)))]
fn spawn_detached_terminal(dir: &std::path::Path) -> std::io::Result<()> {
use std::os::unix::process::CommandExt;
if let Ok(term) = std::env::var("TERMINAL") {
Command::new(&term).current_dir(dir).stdout(Stdio::null()).stderr(Stdio::null()).process_group(0).spawn()?;
return Ok(());
}
let emulators = ["xdg-terminal-emulator", "alacritty", "kitty", "foot", "gnome-terminal", "konsole", "xfce4-terminal", "xterm"];
for emu in &emulators {
if Command::new(emu).current_dir(dir).stdout(Stdio::null()).stderr(Stdio::null()).process_group(0).spawn().is_ok() {
return Ok(());
}
}
Err(std::io::Error::new(std::io::ErrorKind::NotFound, t!("No terminal emulator found. Set $TERMINAL.")))
}
pub(super) fn run_user_menu_entry(app_state: &mut AppState) {
let Some(Dialog::UserMenu(menu)) = app_state.dialog.take() else {
return;
};
let Some(entry) = menu.entries.get(menu.index).cloned() else {
return;
};
let is_left = app_state.is_left_active;
let (needs_file, needs_files) = crate::app::uses_entries(&entry.command);
if (needs_file || needs_files) && app_state.refuse_in_archive(is_left) {
return;
}
let files: Vec<std::path::PathBuf> = chosen_items(app_state).into_iter().map(|(path, _)| path).collect();
let (children, state, dir) = if is_left {
(&app_state.children_left, &app_state.state_left, &app_state.dir_left)
} else {
(&app_state.children_right, &app_state.state_right, &app_state.dir_right)
};
let file = state.selected().and_then(|index| children.get(index)).filter(|item| !item.is_parent()).map(|item| item.path_in(dir));
if (needs_file && file.is_none()) || (needs_files && files.is_empty()) {
app_state.display_error(t!("Nothing chosen for {label}", label = entry.label));
return;
}
let values = crate::app::MenuValues {
file,
files,
dir: app_state.panel_real_dir(is_left),
other: app_state.panel_real_dir(!is_left),
};
app_state.user_command = Some(crate::app::UserCommand {
process: crate::app::shell_command(&entry.command, &values),
label: entry.label,
pause: true,
});
}