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