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 match runnable {
35 crate::RunnableVariant::Meta(runnable) => {
36 if runnable.metadata().is_app_alive() {
37 runnable.run();
38 }
39 }
40 crate::RunnableVariant::Compat(runnable) => runnable.run(),
41 };
42 }
43 })
44 .ok();
45
46 HeadlessClient(Rc::new(RefCell::new(HeadlessClientState {
47 event_loop: Some(event_loop),
48 _loop_handle: handle,
49 common,
50 })))
51 }
52}
53
54impl LinuxClient for HeadlessClient {
55 fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R {
56 f(&mut self.0.borrow_mut().common)
57 }
58
59 fn keyboard_layout(&self) -> Box<dyn PlatformKeyboardLayout> {
60 Box::new(LinuxKeyboardLayout::new("unknown".into()))
61 }
62
63 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
64 vec![]
65 }
66
67 fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>> {
68 None
69 }
70
71 fn display(&self, _id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
72 None
73 }
74
75 #[cfg(feature = "screen-capture")]
76 fn is_screen_capture_supported(&self) -> bool {
77 false
78 }
79
80 #[cfg(feature = "screen-capture")]
81 fn screen_capture_sources(
82 &self,
83 ) -> futures::channel::oneshot::Receiver<anyhow::Result<Vec<Rc<dyn crate::ScreenCaptureSource>>>>
84 {
85 let (mut tx, rx) = futures::channel::oneshot::channel();
86 tx.send(Err(anyhow::anyhow!(
87 "Headless mode does not support screen capture."
88 )))
89 .ok();
90 rx
91 }
92
93 fn active_window(&self) -> Option<AnyWindowHandle> {
94 None
95 }
96
97 fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
98 None
99 }
100
101 fn open_window(
102 &self,
103 _handle: AnyWindowHandle,
104 _params: WindowParams,
105 ) -> anyhow::Result<Box<dyn PlatformWindow>> {
106 anyhow::bail!("neither DISPLAY nor WAYLAND_DISPLAY is set. You can run in headless mode");
107 }
108
109 fn compositor_name(&self) -> &'static str {
110 "headless"
111 }
112
113 fn set_cursor_style(&self, _style: CursorStyle) {}
114
115 fn open_uri(&self, _uri: &str) {}
116
117 fn reveal_path(&self, _path: std::path::PathBuf) {}
118
119 fn write_to_primary(&self, _item: crate::ClipboardItem) {}
120
121 fn write_to_clipboard(&self, _item: crate::ClipboardItem) {}
122
123 fn read_from_primary(&self) -> Option<crate::ClipboardItem> {
124 None
125 }
126
127 fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
128 None
129 }
130
131 fn run(&self) {
132 let mut event_loop = self
133 .0
134 .borrow_mut()
135 .event_loop
136 .take()
137 .expect("App is already running");
138
139 event_loop.run(None, &mut self.clone(), |_| {}).log_err();
140 }
141}