1use alloc::string::String;
27use alloc::vec::Vec;
28
29use rt::sync::SpinLock;
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33#[non_exhaustive]
34pub enum Claim {
35 Primary,
37 Forwarded,
39 Alone,
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct Request {
46 pub cwd: String,
48 pub args: Vec<String>,
50}
51
52const MAGIC: &[u8; 4] = b"JFI1";
53const TOKEN_TAG: &[u8; 4] = b"XAT1";
54const MSG_MAX: usize = 1 << 20;
55#[cfg(any(windows, target_os = "linux"))]
56const QUEUE_MAX: usize = 64;
57
58static QUEUE: SpinLock<Vec<Request>> = SpinLock::new(Vec::new());
59
60impl Request {
61 pub fn paths(&self) -> Vec<String> {
65 let mut out = Vec::new();
66 for a in self.args.iter().filter(|a| !a.is_empty() && !a.starts_with('-')) {
67 if let Some(p) = crate::assoc::file_uri_path(a) {
68 out.push(p);
69 continue;
70 }
71 let b = a.as_bytes();
72 let absolute = a.contains("://")
73 || b[0] == b'/'
74 || b[0] == b'\\'
75 || (b.len() >= 2 && b[0].is_ascii_alphabetic() && b[1] == b':');
76 if absolute || self.cwd.is_empty() {
77 out.push(a.clone());
78 } else {
79 out.push(alloc::format!("{}/{}", self.cwd.trim_end_matches(['/', '\\']), a));
80 }
81 }
82 out
83 }
84}
85
86pub fn take() -> Vec<Request> {
88 core::mem::take(&mut *QUEUE.lock())
89}
90
91fn encode(cwd: &str, args: &[String], token: Option<&str>) -> Option<Vec<u8>> {
92 let mut body = Vec::new();
93 body.extend_from_slice(&(args.len() as u32 + 1).to_le_bytes());
94 for s in core::iter::once(cwd).chain(args.iter().map(|a| a.as_str())) {
95 body.extend_from_slice(&u32::try_from(s.len()).ok()?.to_le_bytes());
96 body.extend_from_slice(s.as_bytes());
97 }
98 if let Some(t) = token {
99 body.extend_from_slice(TOKEN_TAG);
100 body.extend_from_slice(&u32::try_from(t.len()).ok()?.to_le_bytes());
101 body.extend_from_slice(t.as_bytes());
102 }
103 if body.len() > MSG_MAX {
104 return None;
105 }
106 let mut out = Vec::with_capacity(body.len() + 8);
107 out.extend_from_slice(MAGIC);
108 out.extend_from_slice(&(body.len() as u32).to_le_bytes());
109 out.extend_from_slice(&body);
110 Some(out)
111}
112
113fn decode(body: &[u8]) -> Option<(Request, Option<String>)> {
115 let rd = |p: usize| -> Option<usize> { Some(u32::from_le_bytes(body.get(p..p + 4)?.try_into().ok()?) as usize) };
116 let count = rd(0)?;
117 let mut p = 4;
118 let mut items = Vec::new();
119 for _ in 0..count {
120 let n = rd(p)?;
121 let s = core::str::from_utf8(body.get(p + 4..p.checked_add(4 + n)?)?).ok()?;
122 items.push(String::from(s));
123 p += 4 + n;
124 }
125 let mut token = None;
126 if body.get(p..p + 4) == Some(&TOKEN_TAG[..]) {
127 let n = rd(p + 4)?;
128 token = Some(String::from(core::str::from_utf8(body.get(p + 8..p.checked_add(8 + n)?)?).ok()?));
129 p += 8 + n;
130 }
131 if p != body.len() || items.is_empty() {
132 return None;
133 }
134 let cwd = items.remove(0);
135 Some((Request { cwd, args: items }, token))
136}
137
138#[cfg(any(windows, target_os = "linux"))]
141mod native {
142 use super::*;
143 use crate::control_ipc::{InstConn, inst_connect, inst_listen};
144
145 const STEP_MS: u32 = 2;
146 static STOP: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
148 const LIMIT_MS: u64 = 3000;
149
150 fn deadline() -> u64 {
151 crate::time_us() + LIMIT_MS * 1000
152 }
153
154 fn write_all(c: &mut InstConn, mut data: &[u8], end: u64) -> bool {
155 while !data.is_empty() {
156 match c.write(data) {
157 Ok(Some(n)) if n > 0 => data = &data[n..],
158 Ok(Some(_)) | Err(_) => return false,
159 Ok(None) if crate::time_us() < end => crate::sleep_ms(STEP_MS),
160 Ok(None) => return false,
161 }
162 }
163 true
164 }
165
166 fn read_exact(c: &mut InstConn, buf: &mut [u8], end: u64) -> bool {
167 let mut got = 0;
168 while got < buf.len() {
169 match c.read(&mut buf[got..]) {
170 Ok(Some(n)) if n > 0 => got += n,
171 Ok(Some(_)) | Err(_) => return false,
172 Ok(None) if crate::time_us() < end => crate::sleep_ms(STEP_MS),
173 Ok(None) => return false,
174 }
175 }
176 true
177 }
178
179 fn serve_one(c: &mut InstConn) -> bool {
181 let end = deadline();
182 let mut head = [0u8; 8];
183 if !read_exact(c, &mut head, end) || &head[..4] != MAGIC {
184 return false;
185 }
186 let len = u32::from_le_bytes([head[4], head[5], head[6], head[7]]) as usize;
187 if len > MSG_MAX {
188 return false;
189 }
190 let mut body = alloc::vec![0u8; len];
191 if !read_exact(c, &mut body, end) {
192 return false;
193 }
194 let Some((req, _token)) = decode(&body) else { return false };
195 #[cfg(target_os = "linux")]
196 if let Some(t) = &_token {
197 crate::act_provide(t); }
199 {
200 let mut q = QUEUE.lock();
201 if q.len() == QUEUE_MAX {
202 q.remove(0);
203 }
204 q.push(req);
205 }
206 let _ = write_all(c, b"OK", end);
207 crate::wake();
208 true
209 }
210
211 pub fn claim(app: &str) -> Claim {
212 if !app_ok(app) {
213 return Claim::Alone;
214 }
215 for _ in 0..2 {
216 match inst_listen(app) {
217 Ok(Some(server)) => {
218 let started = crate::thread_spawn(64 << 10, alloc::boxed::Box::new(move || {
220 while !STOP.load(core::sync::atomic::Ordering::Relaxed) {
221 match server.accept() {
222 Ok(mut c) => {
223 serve_one(&mut c);
224 }
225 Err(_) => crate::sleep_ms(100),
226 }
227 }
228 }));
229 return if started.is_some() { Claim::Primary } else { Claim::Alone };
230 }
231 Ok(None) => {
232 let args: Vec<String> = crate::args().into_iter().skip(1).collect();
233 let token = if cfg!(target_os = "linux") { crate::env("XDG_ACTIVATION_TOKEN") } else { None };
234 let sent = |t: Option<&str>| encode(&crate::cwd(), &args, t).is_some_and(|m| send_msg(app, &m));
235 if sent(token.as_deref()) || token.is_some() && sent(None) {
237 return Claim::Forwarded;
238 }
239 }
241 Err(_) => return Claim::Alone,
242 }
243 }
244 Claim::Alone
245 }
246
247 pub fn send(app: &str, cwd: &str, args: &[String]) -> bool {
248 encode(cwd, args, None).is_some_and(|m| send_msg(app, &m))
249 }
250
251 fn send_msg(app: &str, msg: &[u8]) -> bool {
252 if !app_ok(app) {
253 return false;
254 }
255 let Ok(mut c) = inst_connect(app) else { return false };
256 let end = deadline();
257 let mut ok = [0u8; 2];
258 write_all(&mut c, &msg, end) && read_exact(&mut c, &mut ok, end) && &ok == b"OK"
259 }
260}
261
262fn app_ok(app: &str) -> bool {
264 !app.is_empty() && app.len() <= 64 && app.bytes().all(|c| c.is_ascii_alphanumeric() || b"._-".contains(&c))
265}
266
267pub fn claim(app: &str) -> Claim {
269 #[cfg(any(windows, target_os = "linux"))]
270 {
271 native::claim(app)
272 }
273 #[cfg(not(any(windows, target_os = "linux")))]
274 {
275 let _ = (app, app_ok as fn(&str) -> bool);
276 Claim::Alone
277 }
278}
279
280pub fn send(app: &str, cwd: &str, args: &[String]) -> bool {
282 #[cfg(any(windows, target_os = "linux"))]
283 {
284 native::send(app, cwd, args)
285 }
286 #[cfg(not(any(windows, target_os = "linux")))]
287 {
288 let _ = (
289 app,
290 cwd,
291 args,
292 encode as fn(&str, &[String], Option<&str>) -> Option<Vec<u8>>,
293 decode as fn(&[u8]) -> Option<(Request, Option<String>)>,
294 );
295 false
296 }
297}