1use std::cell::RefCell;
2use std::rc::Rc;
3
4use anyhow::anyhow;
5use calloop::{EventLoop, LoopHandle};
6use futures::channel::oneshot;
7use util::ResultExt;
8
9use crate::platform::linux::LinuxClient;
10use crate::platform::{LinuxCommon, PlatformWindow};
11use crate::{
12 AnyWindowHandle, CursorStyle, DisplayId, LinuxKeyboardLayout, PlatformDisplay,
13 PlatformKeyboardLayout, ScreenCaptureSource, WindowParams,
14};
15
16pub struct HeadlessClientState {
17 pub(crate) _loop_handle: LoopHandle<'static, HeadlessClient>,
18 pub(crate) event_loop: Option<calloop::EventLoop<'static, HeadlessClient>>,
19 pub(crate) common: LinuxCommon,
20}
21
22#[derive(Clone)]
23pub(crate) struct HeadlessClient(Rc<RefCell<HeadlessClientState>>);
24
25impl HeadlessClient {
26 pub(crate) fn new() -> Self {
27 let event_loop = EventLoop::try_new().unwrap();
28
29 let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
30
31 let handle = event_loop.handle();
32
33 handle
34 .insert_source(main_receiver, |event, _, _: &mut HeadlessClient| {
35 if let calloop::channel::Event::Msg(runnable) = event {
36 runnable.run();
37 }
38 })
39 .ok();
40
41 HeadlessClient(Rc::new(RefCell::new(HeadlessClientState {
42 event_loop: Some(event_loop),
43 _loop_handle: handle,
44 common,
45 })))
46 }
47}
48
49impl LinuxClient for HeadlessClient {
50 fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
51 f(&mut self.0.borrow_mut().common)
52 }
53
54 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
55 Box::new(LinuxKeyboardLayout::new("unknown".to_string()))
56 }
57
58 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
59 vec![]
60 }
61
62 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
63 None
64 }
65
66 fn display(&self, _id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
67 None
68 }
69
70 fn is_screen_capture_supported(&self) -> bool {
71 false
72 }
73
74 fn screen_capture_sources(
75 &self,
76 ) -> oneshot::Receiver<anyhow::Result<Vec<Box<dyn ScreenCaptureSource>>>> {
77 let (mut tx, rx) = oneshot::channel();
78 tx.send(Err(anyhow!(
79 "Headless mode does not support screen capture."
80 )))
81 .ok();
82 rx
83 }
84
85 fn active_window(&self) -> Option<AnyWindowHandle> {
86 None
87 }
88
89 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
90 None
91 }
92
93 fn open_window(
94 &self,
95 _handle: AnyWindowHandle,
96 _params: WindowParams,
97 ) -> anyhow::Result<Box<dyn PlatformWindow>> {
98 anyhow::bail!("neither DISPLAY nor WAYLAND_DISPLAY is set. You can run in headless mode");
99 }
100
101 fn compositor_name(&self) -> &'static str {
102 "headless"
103 }
104
105 fn set_cursor_style(&self, _style: CursorStyle) {}
106
107 fn open_uri(&self, _uri: &str) {}
108
109 fn reveal_path(&self, _path: std::path::PathBuf) {}
110
111 fn write_to_primary(&self, _item: crate::ClipboardItem) {}
112
113 fn write_to_clipboard(&self, _item: crate::ClipboardItem) {}
114
115 fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
116 None
117 }
118
119 fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
120 None
121 }
122
123 fn run(&self) {
124 let mut event_loop = self
125 .0
126 .borrow_mut()
127 .event_loop
128 .take()
129 .expect("App is already running");
130
131 event_loop.run(None, &mut self.clone(), |_| {}).log_err();
132 }
133}