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}