use rmux_core::PaneId;
use rmux_os::process;
use rmux_proto::{
ForegroundFieldSource, ForegroundSourcesDto, ForegroundStateDto, PaneTarget, RmuxError,
};
use crate::format_runtime::pane_path_from_osc7;
use crate::pane_terminals::HandlerState;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ForegroundProbeSeed {
pane_id: PaneId,
generation: u64,
root_pid: Option<u32>,
foreground_pid: Option<u32>,
runtime_name: Option<String>,
shell_path: Option<String>,
shell_name: Option<String>,
profile_cwd: Option<String>,
osc7_path: Option<String>,
env_pwd: Option<String>,
env_home: Option<String>,
env_userprofile: Option<String>,
}
impl ForegroundProbeSeed {
pub(crate) const fn pane_id(&self) -> PaneId {
self.pane_id
}
pub(crate) const fn generation(&self) -> u64 {
self.generation
}
}
pub(crate) fn capture_foreground_probe_seed(
state: &HandlerState,
target: &PaneTarget,
) -> Result<ForegroundProbeSeed, RmuxError> {
let pane_id = state
.sessions
.session(target.session_name())
.and_then(|session| session.window_at(target.window_index()))
.and_then(|window| window.pane(target.pane_index()))
.map(|pane| pane.id())
.ok_or_else(|| {
RmuxError::invalid_target(target.to_string(), "pane index does not exist in session")
})?;
let generation = state.pane_output_generation_for_target(target, pane_id);
let root_pid = state
.pane_pid_in_window(
target.session_name(),
target.window_index(),
target.pane_index(),
)
.ok();
let foreground_pid = foreground_pid_for_target(state, target, root_pid);
let runtime_name = state
.pane_runtime_window_name_in_window(
target.session_name(),
target.window_index(),
target.pane_index(),
)
.ok()
.flatten();
let profile = state
.pane_profile_in_window(
target.session_name(),
target.window_index(),
target.pane_index(),
)
.ok();
let shell_path = profile
.as_ref()
.map(|profile| profile.shell().to_string_lossy().into_owned());
let shell_name = profile.as_ref().and_then(|profile| {
profile
.shell()
.file_name()
.and_then(|name| name.to_str())
.map(str::to_owned)
});
let profile_cwd = profile
.as_ref()
.map(|profile| profile.cwd().to_string_lossy().into_owned());
let osc7_path = state
.pane_screen_state(target.session_name(), pane_id)
.and_then(|screen| pane_path_from_osc7(&screen.path));
let env_pwd = state
.environment
.resolve(Some(target.session_name()), "PWD")
.map(str::to_owned);
let env_home = state
.environment
.resolve(Some(target.session_name()), "HOME")
.map(str::to_owned);
let env_userprofile = state
.environment
.resolve(Some(target.session_name()), "USERPROFILE")
.map(str::to_owned);
Ok(ForegroundProbeSeed {
pane_id,
generation,
root_pid,
foreground_pid,
runtime_name,
shell_path,
shell_name,
profile_cwd,
osc7_path,
env_pwd,
env_home,
env_userprofile,
})
}
pub(crate) fn probe_foreground(seed: &ForegroundProbeSeed) -> ForegroundStateDto {
let mut sources = ForegroundSourcesDto::default();
let pid = foreground_pid(seed);
sources.pid = pid.map(|(_, source)| source);
let command = foreground_command(seed);
sources.command = command.as_ref().map(|(_, source)| *source);
let cwd = foreground_cwd(seed);
sources.cwd = cwd.as_ref().map(|(_, source)| *source);
let exe = foreground_exe(seed);
sources.exe = exe.as_ref().map(|(_, source)| *source);
ForegroundStateDto {
pid: pid.map(|(pid, _)| pid),
command: command.map(|(command, _)| command),
cwd: cwd.map(|(cwd, _)| cwd),
exe: exe.map(|(exe, _)| exe),
sources,
}
}
#[cfg(unix)]
fn foreground_pid_for_target(
state: &HandlerState,
target: &PaneTarget,
root_pid: Option<u32>,
) -> Option<u32> {
state
.pane_master_fd(
target.session_name(),
target.window_index(),
target.pane_index(),
)
.ok()
.and_then(process::unix::foreground_pid)
.or(root_pid)
}
#[cfg(windows)]
fn foreground_pid_for_target(
_state: &HandlerState,
_target: &PaneTarget,
root_pid: Option<u32>,
) -> Option<u32> {
root_pid
}
#[cfg(unix)]
fn foreground_pid(seed: &ForegroundProbeSeed) -> Option<(u32, ForegroundFieldSource)> {
seed.foreground_pid
.map(|pid| (pid, ForegroundFieldSource::Process))
}
#[cfg(windows)]
fn foreground_pid(seed: &ForegroundProbeSeed) -> Option<(u32, ForegroundFieldSource)> {
seed.root_pid
.map(|pid| (pid, ForegroundFieldSource::RootProcess))
}
#[cfg(unix)]
fn foreground_command(seed: &ForegroundProbeSeed) -> Option<(String, ForegroundFieldSource)> {
let foreground_name = seed.foreground_pid.and_then(process::command_name);
match (
foreground_name,
seed.runtime_name.clone(),
seed.shell_name.clone(),
) {
(Some(foreground), Some(runtime), Some(shell))
if foreground == shell && runtime != shell =>
{
Some((runtime, ForegroundFieldSource::RuntimeName))
}
(Some(foreground), _, _) => Some((foreground, ForegroundFieldSource::Process)),
(None, Some(runtime), _) => Some((runtime, ForegroundFieldSource::RuntimeName)),
(None, None, Some(shell)) => Some((shell, ForegroundFieldSource::Profile)),
(None, None, None) => None,
}
}
#[cfg(windows)]
fn foreground_command(seed: &ForegroundProbeSeed) -> Option<(String, ForegroundFieldSource)> {
seed.runtime_name
.clone()
.map(|name| (name, ForegroundFieldSource::RuntimeName))
.or_else(|| {
seed.root_pid
.and_then(process::command_name)
.map(|name| (name, ForegroundFieldSource::RootProcess))
})
.or_else(|| {
seed.shell_name
.clone()
.map(|name| (name, ForegroundFieldSource::Profile))
})
}
#[cfg(unix)]
fn foreground_exe(seed: &ForegroundProbeSeed) -> Option<(String, ForegroundFieldSource)> {
seed.foreground_pid
.and_then(process::executable_path)
.map(|path| (path, ForegroundFieldSource::Process))
.or_else(|| {
seed.shell_path
.clone()
.map(|path| (path, ForegroundFieldSource::Profile))
})
}
#[cfg(windows)]
fn foreground_exe(seed: &ForegroundProbeSeed) -> Option<(String, ForegroundFieldSource)> {
seed.root_pid
.and_then(process::executable_path)
.map(|path| (path, ForegroundFieldSource::RootProcess))
.or_else(|| {
seed.shell_path
.clone()
.map(|path| (path, ForegroundFieldSource::Profile))
})
}
#[cfg(unix)]
fn foreground_cwd(seed: &ForegroundProbeSeed) -> Option<(String, ForegroundFieldSource)> {
seed.foreground_pid
.and_then(process::current_path)
.map(|path| (path, ForegroundFieldSource::Process))
.or_else(|| {
seed.osc7_path
.clone()
.map(|path| (path, ForegroundFieldSource::Osc7))
})
.or_else(|| {
seed.profile_cwd
.clone()
.map(|path| (path, ForegroundFieldSource::Profile))
})
.or_else(|| {
seed.env_pwd
.clone()
.map(|path| (path, ForegroundFieldSource::Environment))
})
.or_else(|| {
seed.env_home
.clone()
.map(|path| (path, ForegroundFieldSource::Environment))
})
}
#[cfg(windows)]
fn foreground_cwd(seed: &ForegroundProbeSeed) -> Option<(String, ForegroundFieldSource)> {
seed.osc7_path
.clone()
.map(|path| (path, ForegroundFieldSource::Osc7))
.or_else(|| {
seed.root_pid
.and_then(process::current_path)
.map(|path| (path, ForegroundFieldSource::RootProcess))
})
.or_else(|| {
seed.profile_cwd
.clone()
.map(|path| (path, ForegroundFieldSource::Profile))
})
.or_else(|| {
seed.env_pwd
.clone()
.map(|path| (path, ForegroundFieldSource::Environment))
})
.or_else(|| {
seed.env_userprofile
.clone()
.map(|path| (path, ForegroundFieldSource::Environment))
})
}
#[cfg(test)]
mod tests {
use super::*;
fn seed_with_profile_shell(shell_path: &str) -> ForegroundProbeSeed {
ForegroundProbeSeed {
pane_id: PaneId::new(1),
generation: 1,
root_pid: None,
foreground_pid: None,
runtime_name: None,
shell_path: Some(shell_path.to_owned()),
shell_name: Some("pwsh.exe".to_owned()),
profile_cwd: None,
osc7_path: None,
env_pwd: None,
env_home: None,
env_userprofile: None,
}
}
#[test]
fn probe_foreground_falls_back_to_profile_executable_path() {
let state = probe_foreground(&seed_with_profile_shell(
"C:/Program Files/PowerShell/7/pwsh.exe",
));
assert_eq!(
state.exe.as_deref(),
Some("C:/Program Files/PowerShell/7/pwsh.exe")
);
assert_eq!(state.sources.exe, Some(ForegroundFieldSource::Profile));
}
}