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