1use std::{
10 collections::HashMap,
11 os::fd::{FromRawFd, OwnedFd},
12};
13
14use passfd::FdPassingExt;
15use pinnacle_api_defs::pinnacle::process::v1::{SetEnvRequest, SpawnRequest, WaitOnSpawnRequest};
16use tokio_stream::StreamExt;
17
18use crate::{client::Client, BlockOnTokio};
19
20pub fn set_env(key: impl ToString, value: impl ToString) {
22 Client::process()
23 .set_env(SetEnvRequest {
24 key: key.to_string(),
25 value: value.to_string(),
26 })
27 .block_on_tokio()
28 .unwrap();
29}
30
31pub struct Command {
33 cmd: Vec<String>,
34 envs: HashMap<String, String>,
35 shell_cmd: Vec<String>,
36 unique: bool,
37 once: bool,
38 pipe_stdin: bool,
39 pipe_stdout: bool,
40 pipe_stderr: bool,
41}
42
43#[derive(Debug)]
45pub struct Child {
46 pid: u32,
47 pub stdin: Option<tokio::process::ChildStdin>,
51 pub stdout: Option<tokio::process::ChildStdout>,
55 pub stderr: Option<tokio::process::ChildStderr>,
59}
60
61#[derive(Debug, Default)]
63pub struct ExitInfo {
64 pub exit_code: Option<i32>,
66 pub exit_msg: Option<String>,
68}
69
70impl Child {
71 pub fn wait(self) -> ExitInfo {
73 self.wait_async().block_on_tokio()
74 }
75
76 pub async fn wait_async(self) -> ExitInfo {
78 let mut exit_status = Client::process()
79 .wait_on_spawn(WaitOnSpawnRequest { pid: self.pid })
80 .await
81 .unwrap()
82 .into_inner();
83
84 let thing = exit_status.next().await;
85
86 let Some(Ok(response)) = thing else {
87 return Default::default();
88 };
89
90 ExitInfo {
91 exit_code: response.exit_code,
92 exit_msg: response.exit_msg,
93 }
94 }
95}
96
97impl Drop for Child {
98 fn drop(&mut self) {
99 let pid = self.pid;
100
101 tokio::spawn(async move {
103 Client::process()
104 .wait_on_spawn(WaitOnSpawnRequest { pid })
105 .await
106 .unwrap();
107 });
108 }
109}
110
111impl Command {
112 pub fn new(program: impl ToString) -> Self {
114 Self {
115 cmd: vec![program.to_string()],
116 envs: Default::default(),
117 shell_cmd: Vec::new(),
118 unique: false,
119 once: false,
120 pipe_stdin: false,
121 pipe_stdout: false,
122 pipe_stderr: false,
123 }
124 }
125
126 pub fn with_shell(
136 shell_args: impl IntoIterator<Item = impl ToString>,
137 command: impl ToString,
138 ) -> Self {
139 Self {
140 cmd: vec![command.to_string()],
141 envs: Default::default(),
142 shell_cmd: shell_args
143 .into_iter()
144 .map(|args| args.to_string())
145 .collect(),
146 unique: false,
147 once: false,
148 pipe_stdin: false,
149 pipe_stdout: false,
150 pipe_stderr: false,
151 }
152 }
153
154 pub fn arg(&mut self, arg: impl ToString) -> &mut Self {
156 self.cmd.push(arg.to_string());
157 self
158 }
159
160 pub fn args(&mut self, args: impl IntoIterator<Item = impl ToString>) -> &mut Self {
162 self.cmd.extend(args.into_iter().map(|arg| arg.to_string()));
163 self
164 }
165
166 pub fn env(&mut self, key: impl ToString, value: impl ToString) -> &mut Self {
168 self.envs.insert(key.to_string(), value.to_string());
169 self
170 }
171
172 pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
174 where
175 I: IntoIterator<Item = (K, V)>,
176 K: ToString,
177 V: ToString,
178 {
179 self.envs.extend(
180 vars.into_iter()
181 .map(|(k, v)| (k.to_string(), v.to_string())),
182 );
183 self
184 }
185
186 pub fn unique(&mut self) -> &mut Self {
188 self.unique = true;
189 self
190 }
191
192 pub fn once(&mut self) -> &mut Self {
194 self.once = true;
195 self
196 }
197
198 pub fn pipe_stdin(&mut self) -> &mut Self {
202 self.pipe_stdin = true;
203 self
204 }
205
206 pub fn pipe_stdout(&mut self) -> &mut Self {
210 self.pipe_stdout = true;
211 self
212 }
213
214 pub fn pipe_stderr(&mut self) -> &mut Self {
218 self.pipe_stderr = true;
219 self
220 }
221
222 pub fn spawn(&mut self) -> Option<Child> {
224 let data = Client::process()
225 .spawn(SpawnRequest {
226 cmd: self.cmd.clone(),
227 unique: self.unique,
228 once: self.once,
229 shell_cmd: self.shell_cmd.clone(),
230 envs: self.envs.clone(),
231 pipe_stdin: self.pipe_stdin,
232 pipe_stdout: self.pipe_stdout,
233 pipe_stderr: self.pipe_stderr,
234 })
235 .block_on_tokio()
236 .unwrap()
237 .into_inner()
238 .spawn_data?;
239
240 let pid = data.pid;
241 let fd_socket_path = data.fd_socket_path;
242
243 let mut stdin = None;
244 let mut stdout = None;
245 let mut stderr = None;
246
247 let stream = std::os::unix::net::UnixStream::connect(fd_socket_path)
248 .expect("this should be set up by the compositor");
249
250 if data.has_stdin {
251 let fd = stream.recv_fd().unwrap();
252 let child_stdin =
254 tokio::process::ChildStdin::from_std(std::process::ChildStdin::from(unsafe {
255 OwnedFd::from_raw_fd(fd)
256 }))
257 .unwrap();
258 stdin = Some(child_stdin);
259 }
260
261 if data.has_stdout {
262 let fd = stream.recv_fd().unwrap();
263 let child_stdout =
264 tokio::process::ChildStdout::from_std(std::process::ChildStdout::from(unsafe {
265 OwnedFd::from_raw_fd(fd)
266 }))
267 .unwrap();
268 stdout = Some(child_stdout);
269 }
270
271 if data.has_stderr {
272 let fd = stream.recv_fd().unwrap();
273 let child_stderr =
274 tokio::process::ChildStderr::from_std(std::process::ChildStderr::from(unsafe {
275 OwnedFd::from_raw_fd(fd)
276 }))
277 .unwrap();
278 stderr = Some(child_stderr);
279 }
280
281 Some(Child {
282 pid,
283 stdin,
284 stdout,
285 stderr,
286 })
287 }
288}