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