1use alloc::boxed::Box;
25use alloc::string::String;
26use alloc::vec;
27use alloc::vec::Vec;
28use core::sync::atomic::{AtomicBool, Ordering};
29
30use rt::sync::SpinLock;
31
32use crate::Frame;
33
34pub const BK_NONE: u32 = 0;
35pub const BK_WAYLAND: u32 = 1;
36pub const BK_X11: u32 = 2;
37
38pub trait Display: Send {
40 fn ime_pos(&mut self,_x:i32,_y:i32,_h:i32) {}
41 fn win_create(&mut self, title: &str, w_dip: i32, h_dip: i32, flags: u32, owner: i32, x: i32, y: i32) -> i32;
42 fn win_destroy(&mut self, w: i32);
43 fn win_show(&mut self, w: i32, mode: u32);
44 fn win_activate(&mut self, w: i32);
45 fn win_enable(&mut self, w: i32, on: bool);
46 fn win_set_rect(&mut self, w: i32, x: i32, y: i32, cw: i32, ch: i32);
47 fn win_get_rect(&mut self, w: i32) -> Option<[i32; 4]>;
48 fn win_work_area(&mut self, w: i32) -> Option<[i32; 4]>;
49 fn win_set_title(&mut self, w: i32, title: &str);
50 fn win_state(&mut self, w: i32) -> u32;
51 fn win_cmd(&mut self, w: i32, cmd: u32);
52 fn win_begin_move(&mut self, w: i32);
53 fn win_begin_resize(&mut self, w: i32, edges: u32);
54 fn win_show_menu(&mut self, _w: i32, _x: i32, _y: i32) {}
57 fn win_size(&mut self, w: i32) -> (i32, i32);
58 fn win_dpi(&mut self, w: i32) -> i32;
59 fn win_set_min_size(&mut self, w: i32, w_dip: i32, h_dip: i32);
60 fn win_present(&mut self, w: i32, f: &Frame, rects: Option<&[[i32; 4]]>);
61 fn win_set_cursor(&mut self, w: i32, shape: u32);
62 fn set_refresh(&mut self, f: Option<fn()>);
63 fn wait(&mut self, deadline_us: u64) -> u32;
64 fn popup(&mut self, slot: u32, owner: i32, rect: Option<[i32; 4]>, f: Option<&Frame>);
65 fn popup_anchor(&mut self, _slot: u32, _anchor: [i32; 4], _kind: u32, _nat: [i32; 2]) {}
68 fn popup_placed(&mut self, _slot: u32) -> Option<[i32; 2]> {
70 None
71 }
72 fn cursor_create(&mut self, px: &[u8], w: i32, h: i32, hot_x: i32, hot_y: i32, dpi: i32) -> i32;
73 fn cursor_destroy(&mut self, id: i32);
74 fn cursor_dpi(&mut self, dpi: i32) -> i32;
75 fn clip_set(&mut self, fmt: u32, data: &[u8]) -> bool;
76 fn clip_set_many(&mut self, entries: &[(u32, &[u8])]) -> bool;
77 fn clip_get(&mut self, fmt: u32) -> Option<Vec<u8>>;
78 fn clip_has(&mut self, fmt: u32) -> bool;
79 fn pen_devices(&mut self) -> i32 {
82 -1
83 }
84 fn drag_start(&mut self, _data: crate::DragData) -> bool {
87 false
88 }
89}
90
91struct Disp {
92 bk: Option<Box<dyn Display>>,
93 kind: u32,
94 min0: (i32, i32),
95 refresh: Option<fn()>,
96 hit: Option<fn(i32, i32, i32) -> u32>,
97 mv: Option<fn(i32, &mut [i32; 4])>,
98}
99
100static DISP: SpinLock<Disp> =
101 SpinLock::new(Disp { bk: None, kind: BK_NONE, min0: (0, 0), refresh: None, hit: None, mv: None });
102static WL_QUIET: AtomicBool = AtomicBool::new(false);
103
104pub fn disp_backend() -> u32 {
106 DISP.lock().kind
107}
108
109pub fn disp_wl_quiet() -> bool {
112 WL_QUIET.load(Ordering::Relaxed)
113}
114
115fn nonempty_env(name: &str) -> bool {
116 crate::env(name).is_some_and(|v| !v.is_empty())
117}
118
119fn try_backend(k: u32, title: &str, w_dip: i32, h_dip: i32, flags: u32, x: i32, y: i32) -> Option<(Box<dyn Display>, i32)> {
122 let mut b = if k == BK_WAYLAND { crate::wl_connect()? } else { crate::x11_connect()? };
123 let (min0, refresh) = {
124 let d = DISP.lock();
125 (d.min0, d.refresh)
126 };
127 b.win_set_min_size(0, min0.0, min0.1);
128 if refresh.is_some() {
129 b.set_refresh(refresh);
130 }
131 let w = b.win_create(title, w_dip, h_dip, flags, crate::PW_NOOWNER, x, y);
132 if w < 0 { None } else { Some((b, w)) }
133}
134
135fn choose(title: &str, w_dip: i32, h_dip: i32, flags: u32, x: i32, y: i32) -> i32 {
137 let forced = match crate::env("jfgui_BACKEND").and_then(|v| v.bytes().next()) {
139 Some(b'x') => BK_X11,
140 Some(b'w') => BK_WAYLAND,
141 _ => 0,
142 };
143 let mut x11_first = false;
144 let mut next = BK_WAYLAND;
145 if forced != 0 {
146 next = forced;
147 } else if !nonempty_env("WAYLAND_DISPLAY") && nonempty_env("DISPLAY") {
148 x11_first = true;
149 next = BK_X11;
150 }
151 loop {
152 if next == BK_WAYLAND {
153 WL_QUIET.store(forced != BK_WAYLAND, Ordering::Relaxed);
154 DISP.lock().kind = BK_WAYLAND;
155 if let Some((b, w)) = try_backend(BK_WAYLAND, title, w_dip, h_dip, flags, x, y) {
156 DISP.lock().bk = Some(b);
157 return w;
158 }
159 if forced != 0 || x11_first || !nonempty_env("DISPLAY") {
160 break;
161 }
162 next = BK_X11;
163 } else {
164 DISP.lock().kind = BK_X11;
165 if let Some((b, w)) = try_backend(BK_X11, title, w_dip, h_dip, flags, x, y) {
166 DISP.lock().bk = Some(b);
167 return w;
168 }
169 if forced != 0 || !x11_first {
170 break;
171 }
172 x11_first = false; next = BK_WAYLAND;
174 }
175 }
176 crate::write_stderr(
177 b"error: no display available -- neither a Wayland compositor (WAYLAND_DISPLAY) nor an X11 server \
178(DISPLAY) could be opened (use --shot FILE.png for a headless render)\n",
179 );
180 crate::exit(1)
181}
182
183fn with<R>(f: impl FnOnce(&mut dyn Display) -> R) -> Option<R> {
187 let mut b = DISP.lock().bk.take()?;
188 let r = f(b.as_mut());
189 DISP.lock().bk = Some(b);
190 Some(r)
191}
192
193pub fn win_create(title: &str, w_dip: i32, h_dip: i32, flags: u32, owner: i32, x: i32, y: i32) -> i32 {
195 let none = {
196 let d = DISP.lock();
197 d.bk.is_none() && d.kind == BK_NONE
198 };
199 if none {
200 if owner != crate::PW_NOOWNER {
201 return -1; }
203 let w = choose(title, w_dip, h_dip, flags, x, y);
204 if w >= 0 {
205 crate::life_start(); }
207 return w;
208 }
209 with(|b| b.win_create(title, w_dip, h_dip, flags, owner, x, y)).unwrap_or(-1)
210}
211
212pub fn win_destroy(w: i32) {
213 with(|b| b.win_destroy(w));
214}
215
216pub fn win_show(w: i32, mode: u32) {
217 with(|b| b.win_show(w, mode));
218}
219
220pub fn win_activate(w: i32) {
221 with(|b| b.win_activate(w));
222}
223
224pub fn win_enable(w: i32, on: bool) {
225 with(|b| b.win_enable(w, on));
226}
227
228pub fn win_set_rect(w: i32, x: i32, y: i32, cw: i32, ch: i32) {
229 with(|b| b.win_set_rect(w, x, y, cw, ch));
230}
231
232pub fn win_get_rect(w: i32) -> Option<[i32; 4]> {
233 with(|b| b.win_get_rect(w)).flatten()
234}
235
236pub fn win_work_area(w: i32) -> Option<[i32; 4]> {
237 with(|b| b.win_work_area(w)).flatten()
238}
239
240pub fn win_set_title(w: i32, title: &str) {
241 with(|b| b.win_set_title(w, title));
242}
243
244pub fn win_state(w: i32) -> u32 {
245 with(|b| b.win_state(w)).unwrap_or(0)
246}
247
248pub fn win_cmd(w: i32, cmd: u32) {
249 with(|b| b.win_cmd(w, cmd));
250}
251
252pub fn win_begin_move(w: i32) {
253 with(|b| b.win_begin_move(w));
254}
255
256pub fn win_begin_resize(w: i32, edges: u32) {
257 with(|b| b.win_begin_resize(w, edges));
258}
259
260pub fn win_show_menu(w: i32, x: i32, y: i32) {
263 with(|b| b.win_show_menu(w, x, y));
264}
265
266pub fn win_size(w: i32) -> (i32, i32) {
267 with(|b| b.win_size(w)).unwrap_or((0, 0))
268}
269
270pub fn win_dpi(w: i32) -> i32 {
271 with(|b| b.win_dpi(w)).unwrap_or(96)
272}
273
274pub fn win_set_min_size(w: i32, w_dip: i32, h_dip: i32) {
275 let mut d = DISP.lock();
276 match d.bk.as_mut() {
277 Some(b) => b.win_set_min_size(w, w_dip, h_dip),
278 None if w == 0 => d.min0 = (w_dip, h_dip),
279 None => {}
280 }
281}
282
283pub fn win_present(w: i32, f: &Frame, rects: Option<&[[i32; 4]]>) {
286 let t0 = crate::time_us();
287 with(|b| b.win_present(w, f, rects));
288 crate::present_account(t0, true);
289}
290
291pub fn win_set_cursor(w: i32, shape: u32) {
292 with(|b| b.win_set_cursor(w, shape));
293}
294
295pub fn win_hit_hook(f: Option<fn(i32, i32, i32) -> u32>) {
298 DISP.lock().hit = f;
299}
300
301pub fn win_move_hook(f: Option<fn(i32, &mut [i32; 4])>) {
304 DISP.lock().mv = f;
305}
306
307pub(crate) fn disp_hit(w: i32, x: i32, y: i32) -> u32 {
309 let f = DISP.lock().hit;
310 f.map_or(0, |f| f(w, x, y))
311}
312
313pub(crate) fn disp_moving(w: i32, rect: &mut [i32; 4]) {
316 let f = DISP.lock().mv;
317 if let Some(f) = f {
318 f(w, rect);
319 }
320}
321
322pub fn set_refresh(f: Option<fn()>) {
323 let mut d = DISP.lock();
324 d.refresh = f;
325 if let Some(b) = d.bk.as_mut() {
326 b.set_refresh(f);
327 }
328}
329
330pub fn wait(deadline_us: u64) -> u32 {
334 crate::life_poll(); if crate::evq_count() != 0 {
336 return crate::evq_count();
337 }
338 if let Some(n) = with(|b| b.wait(deadline_us)) {
339 crate::life_poll();
340 return n.max(crate::evq_count());
341 }
342 if crate::evq_count() == 0 {
343 crate::wait_events(deadline_us, &mut |e| {
344 crate::evq_push(*e);
345 });
346 }
347 crate::evq_count()
348}
349
350pub fn popup(slot: u32, owner: i32, rect: Option<[i32; 4]>, f: Option<&Frame>) {
352 with(|b| b.popup(slot, owner, rect, f));
353}
354
355pub fn popup_anchor(slot: u32, anchor: [i32; 4], kind: u32, nat: [i32; 2]) {
359 with(|b| b.popup_anchor(slot, anchor, kind, nat));
360}
361
362pub fn popup_placed(slot: u32) -> Option<[i32; 2]> {
365 with(|b| b.popup_placed(slot)).flatten()
366}
367
368pub fn cursor_create(px: &[u8], w: i32, h: i32, hot_x: i32, hot_y: i32, dpi: i32) -> i32 {
369 with(|b| b.cursor_create(px, w, h, hot_x, hot_y, dpi)).unwrap_or(-1)
370}
371
372pub fn cursor_destroy(id: i32) {
373 with(|b| b.cursor_destroy(id));
374}
375
376pub fn cursor_dpi(dpi: i32) -> i32 {
378 with(|b| b.cursor_dpi(dpi)).unwrap_or(dpi)
379}
380
381pub fn clip_set(fmt: u32, data: &[u8]) -> bool {
382 with(|b| b.clip_set(fmt, data)).unwrap_or(false)
383}
384
385pub fn clip_set_many(entries: &[(u32, &[u8])]) -> bool {
387 if !crate::clip_many_valid(entries) { return false; }
388 with(|b| b.clip_set_many(entries)).unwrap_or(false)
389}
390
391pub fn clip_get(fmt: u32) -> Option<Vec<u8>> {
392 let data = with(|b| b.clip_get(fmt)).flatten()?;
393 if matches!(fmt, crate::CLIP_HTML | crate::CLIP_RTF | crate::CLIP_DOCX) && data.len() > crate::CLIP_RICH_MAX {
394 return None;
395 }
396 if fmt == crate::CLIP_HTML && (data.contains(&0) || core::str::from_utf8(&data).is_err()) {
397 return None;
398 }
399 Some(data)
400}
401
402pub fn clip_has(fmt: u32) -> bool {
403 with(|b| b.clip_has(fmt)).unwrap_or(false)
404}
405
406pub fn ime_pos(x: i32, y: i32, h: i32) { with(|b| b.ime_pos(x,y,h)); }
408
409pub(crate) fn os_drag_start(data: crate::DragData) -> bool {
411 with(|b| b.drag_start(data)).unwrap_or(false)
412}
413
414pub fn theme() -> i32 {
422 if let Some(t) = crate::env("jfgui_THEME") {
423 if t == "dark" {
424 return 1;
425 }
426 if t == "light" {
427 return 0;
428 }
429 }
430 if crate::env("GTK_THEME").is_some_and(|g| crate::linux_settings::theme_is_dark(&g)) {
431 return 1;
432 }
433 crate::linux_settings::portal_dark()
434}
435
436pub fn pen_devices() -> i32 {
438 with(|b| b.pen_devices()).unwrap_or(-1)
439}
440
441pub fn home() -> Option<String> {
443 Some(crate::env("HOME").unwrap_or_else(|| String::from("/")))
444}
445
446pub fn drives() -> Vec<String> {
448 vec![String::from("/")]
449}
450
451const SPAWN_ARGS: usize = 32; pub fn spawn_self(args: &[&str]) -> i32 {
456 spawn_exe(b"/proc/self/exe\0", args)
457}
458
459pub fn restart_self(args: &[&str]) -> i32 {
462 let Some(p) = crate::current_exe() else { return -1 };
463 let mut path = String::from(p.strip_suffix(" (deleted)").unwrap_or(&p)).into_bytes();
464 if path.contains(&0) {
465 return -1;
466 }
467 path.push(0);
468 spawn_exe(&path, args)
469}
470
471pub(crate) fn spawn_file(path: &str, args: &[&str]) -> i32 {
474 if path.is_empty() || path.contains('\0') {
475 return -1;
476 }
477 let mut p = String::from(path).into_bytes();
478 p.push(0);
479 spawn_exe(&p, args)
480}
481
482fn spawn_exe(exe: &[u8], args: &[&str]) -> i32 {
484 if args.len() >= SPAWN_ARGS {
485 return -1;
486 }
487 fn z(s: &[u8]) -> Vec<u8> {
488 let mut v = s.to_vec();
489 v.push(0);
490 v
491 }
492 let a0 = super::args().into_iter().next().unwrap_or_default();
494 let mut strs: Vec<Vec<u8>> = Vec::with_capacity(args.len() + 1);
495 strs.push(z(a0.as_bytes()));
496 for a in args {
497 strs.push(z(a.as_bytes()));
498 }
499 let envs: Vec<Vec<u8>> = {
500 let mut g = super::ENV.lock();
501 super::env_list(&mut g).iter().map(|e| z(e)).collect()
502 };
503 let mut argv: Vec<usize> = strs.iter().map(|s| s.as_ptr() as usize).collect();
504 argv.push(0);
505 let mut envp: Vec<usize> = envs.iter().map(|s| s.as_ptr() as usize).collect();
506 envp.push(0);
507 unsafe {
510 let pid = rt::os::fork();
511 if pid < 0 {
512 return -1;
513 }
514 if pid == 0 {
515 rt::os::syscall3(rt::os::SYS_EXECVE, exe.as_ptr() as usize, argv.as_ptr() as usize, envp.as_ptr() as usize);
516 rt::os::syscall3(rt::os::SYS_EXIT_GROUP, 127, 0, 0);
517 loop {}
518 }
519 }
520 0
521}