pty_info.rs

  1use alacritty_terminal::tty::Pty;
  2#[cfg(target_os = "windows")]
  3use std::num::NonZeroU32;
  4#[cfg(unix)]
  5use std::os::fd::AsRawFd;
  6use std::path::PathBuf;
  7
  8#[cfg(target_os = "windows")]
  9use windows::Win32::{Foundation::HANDLE, System::Threading::GetProcessId};
 10
 11use sysinfo::{Pid, Process, ProcessRefreshKind, RefreshKind, System, UpdateKind};
 12
 13pub struct ProcessIdGetter {
 14    handle: i32,
 15    fallback_pid: u32,
 16}
 17
 18impl ProcessIdGetter {
 19    pub fn fallback_pid(&self) -> Pid {
 20        Pid::from_u32(self.fallback_pid)
 21    }
 22}
 23
 24#[cfg(unix)]
 25impl ProcessIdGetter {
 26    fn new(pty: &Pty) -> ProcessIdGetter {
 27        ProcessIdGetter {
 28            handle: pty.file().as_raw_fd(),
 29            fallback_pid: pty.child().id(),
 30        }
 31    }
 32
 33    fn pid(&self) -> Option<Pid> {
 34        let pid = unsafe { libc::tcgetpgrp(self.handle) };
 35        if pid < 0 {
 36            return Some(Pid::from_u32(self.fallback_pid));
 37        }
 38        Some(Pid::from_u32(pid as u32))
 39    }
 40}
 41
 42#[cfg(windows)]
 43impl ProcessIdGetter {
 44    fn new(pty: &Pty) -> ProcessIdGetter {
 45        let child = pty.child_watcher();
 46        let handle = child.raw_handle();
 47        let fallback_pid = child.pid().unwrap_or_else(|| unsafe {
 48            NonZeroU32::new_unchecked(GetProcessId(HANDLE(handle as _)))
 49        });
 50
 51        ProcessIdGetter {
 52            handle: handle as i32,
 53            fallback_pid: u32::from(fallback_pid),
 54        }
 55    }
 56
 57    fn pid(&self) -> Option<Pid> {
 58        let pid = unsafe { GetProcessId(HANDLE(self.handle as _)) };
 59        // the GetProcessId may fail and returns zero, which will lead to a stack overflow issue
 60        if pid == 0 {
 61            // in the builder process, there is a small chance, almost negligible,
 62            // that this value could be zero, which means child_watcher returns None,
 63            // GetProcessId returns 0.
 64            if self.fallback_pid == 0 {
 65                return None;
 66            }
 67            return Some(Pid::from_u32(self.fallback_pid));
 68        }
 69        Some(Pid::from_u32(pid))
 70    }
 71}
 72
 73#[derive(Clone, Debug)]
 74pub struct ProcessInfo {
 75    pub name: String,
 76    pub cwd: PathBuf,
 77    pub argv: Vec<String>,
 78}
 79
 80/// Fetches Zed-relevant Pseudo-Terminal (PTY) process information
 81pub struct PtyProcessInfo {
 82    system: System,
 83    refresh_kind: ProcessRefreshKind,
 84    pid_getter: ProcessIdGetter,
 85    pub current: Option<ProcessInfo>,
 86}
 87
 88impl PtyProcessInfo {
 89    pub fn new(pty: &Pty) -> PtyProcessInfo {
 90        let process_refresh_kind = ProcessRefreshKind::nothing()
 91            .with_cmd(UpdateKind::Always)
 92            .with_cwd(UpdateKind::Always)
 93            .with_exe(UpdateKind::Always);
 94        let refresh_kind = RefreshKind::nothing().with_processes(process_refresh_kind);
 95        let system = System::new_with_specifics(refresh_kind);
 96
 97        PtyProcessInfo {
 98            system,
 99            refresh_kind: process_refresh_kind,
100            pid_getter: ProcessIdGetter::new(pty),
101            current: None,
102        }
103    }
104
105    pub fn pid_getter(&self) -> &ProcessIdGetter {
106        &self.pid_getter
107    }
108
109    fn refresh(&mut self) -> Option<&Process> {
110        let pid = self.pid_getter.pid()?;
111        if self.system.refresh_processes_specifics(
112            sysinfo::ProcessesToUpdate::Some(&[pid]),
113            true,
114            self.refresh_kind,
115        ) == 1
116        {
117            self.system.process(pid)
118        } else {
119            None
120        }
121    }
122
123    fn get_child(&self) -> Option<&Process> {
124        let pid = self.pid_getter.fallback_pid();
125        self.system.process(pid)
126    }
127
128    pub(crate) fn kill_current_process(&mut self) -> bool {
129        self.refresh().is_some_and(|process| process.kill())
130    }
131
132    pub(crate) fn kill_child_process(&mut self) -> bool {
133        self.get_child().is_some_and(|process| process.kill())
134    }
135
136    fn load(&mut self) -> Option<ProcessInfo> {
137        let process = self.refresh()?;
138        let cwd = process.cwd().map_or(PathBuf::new(), |p| p.to_owned());
139
140        let info = ProcessInfo {
141            name: process.name().to_str()?.to_owned(),
142            cwd,
143            argv: process
144                .cmd()
145                .iter()
146                .filter_map(|s| s.to_str().map(ToOwned::to_owned))
147                .collect(),
148        };
149        self.current = Some(info.clone());
150        Some(info)
151    }
152
153    /// Updates the cached process info, returns whether the Zed-relevant info has changed
154    pub fn has_changed(&mut self) -> bool {
155        let current = self.load();
156        let has_changed = match (self.current.as_ref(), current.as_ref()) {
157            (None, None) => false,
158            (Some(prev), Some(now)) => prev.cwd != now.cwd || prev.name != now.name,
159            _ => true,
160        };
161        if has_changed {
162            self.current = current;
163        }
164        has_changed
165    }
166
167    pub fn pid(&self) -> Option<Pid> {
168        self.pid_getter.pid()
169    }
170}