1#![allow(unused)]
2
3use crate::{
4 Action, AnyWindowHandle, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DisplayId,
5 ForegroundExecutor, Keymap, LinuxDispatcher, LinuxDisplay, LinuxTextSystem, LinuxWindow,
6 LinuxWindowState, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
7 PlatformTextSystem, PlatformWindow, Point, Result, SemanticVersion, Size, Task, WindowOptions,
8};
9
10use collections::{HashMap, HashSet};
11use futures::channel::oneshot;
12use parking_lot::Mutex;
13
14use std::{
15 path::{Path, PathBuf},
16 rc::Rc,
17 sync::Arc,
18 time::Duration,
19};
20use time::UtcOffset;
21use xcb::{x, Xid as _};
22
23xcb::atoms_struct! {
24 #[derive(Debug)]
25 pub(crate) struct XcbAtoms {
26 pub wm_protocols => b"WM_PROTOCOLS",
27 pub wm_del_window => b"WM_DELETE_WINDOW",
28 wm_state => b"_NET_WM_STATE",
29 wm_state_maxv => b"_NET_WM_STATE_MAXIMIZED_VERT",
30 wm_state_maxh => b"_NET_WM_STATE_MAXIMIZED_HORZ",
31 }
32}
33
34pub(crate) struct LinuxPlatform {
35 xcb_connection: Arc<xcb::Connection>,
36 x_root_index: i32,
37 atoms: XcbAtoms,
38 background_executor: BackgroundExecutor,
39 foreground_executor: ForegroundExecutor,
40 dispatcher: Arc<LinuxDispatcher>,
41 text_system: Arc<LinuxTextSystem>,
42 state: Mutex<LinuxPlatformState>,
43}
44
45pub(crate) struct LinuxPlatformState {
46 windows: HashMap<x::Window, Arc<LinuxWindowState>>,
47}
48
49impl Default for LinuxPlatform {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl LinuxPlatform {
56 pub(crate) fn new() -> Self {
57 let (xcb_connection, x_root_index) = xcb::Connection::connect(None).unwrap();
58 let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
59
60 let dispatcher = Arc::new(LinuxDispatcher::new());
61
62 Self {
63 xcb_connection: Arc::new(xcb_connection),
64 x_root_index,
65 atoms,
66 background_executor: BackgroundExecutor::new(dispatcher.clone()),
67 foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
68 dispatcher,
69 text_system: Arc::new(LinuxTextSystem::new()),
70 state: Mutex::new(LinuxPlatformState {
71 windows: HashMap::default(),
72 }),
73 }
74 }
75}
76
77impl Platform for LinuxPlatform {
78 fn background_executor(&self) -> BackgroundExecutor {
79 self.background_executor.clone()
80 }
81
82 fn foreground_executor(&self) -> ForegroundExecutor {
83 self.foreground_executor.clone()
84 }
85
86 fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
87 self.text_system.clone()
88 }
89
90 fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
91 on_finish_launching();
92 //Note: here and below, don't keep the lock() open when calling
93 // into window functions as they may invoke callbacks that need
94 // to immediately access the platform (self).
95
96 while !self.state.lock().windows.is_empty() {
97 let event = self.xcb_connection.wait_for_event().unwrap();
98 match event {
99 xcb::Event::X(x::Event::ClientMessage(ev)) => {
100 if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
101 if atom == self.atoms.wm_del_window.resource_id() {
102 // window "x" button clicked by user, we gracefully exit
103 let window = self.state.lock().windows.remove(&ev.window()).unwrap();
104 window.destroy();
105 }
106 }
107 }
108 xcb::Event::X(x::Event::Expose(ev)) => {
109 let window = {
110 let state = self.state.lock();
111 Arc::clone(&state.windows[&ev.window()])
112 };
113 window.expose();
114 }
115 xcb::Event::X(x::Event::ConfigureNotify(ev)) => {
116 let bounds = Bounds {
117 origin: Point {
118 x: ev.x().into(),
119 y: ev.y().into(),
120 },
121 size: Size {
122 width: ev.width().into(),
123 height: ev.height().into(),
124 },
125 };
126 let window = {
127 let state = self.state.lock();
128 Arc::clone(&state.windows[&ev.window()])
129 };
130 window.configure(bounds)
131 }
132 ref other => {
133 println!("Other event {:?}", other);
134 }
135 }
136 self.dispatcher.tick_main();
137 }
138 }
139
140 fn quit(&self) {}
141
142 fn restart(&self) {}
143
144 fn activate(&self, ignoring_other_apps: bool) {}
145
146 fn hide(&self) {}
147
148 fn hide_other_apps(&self) {}
149
150 fn unhide_other_apps(&self) {}
151
152 fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
153 let setup = self.xcb_connection.get_setup();
154 setup
155 .roots()
156 .enumerate()
157 .map(|(root_id, _)| {
158 Rc::new(LinuxDisplay::new(&self.xcb_connection, root_id as i32))
159 as Rc<dyn PlatformDisplay>
160 })
161 .collect()
162 }
163
164 fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
165 Some(Rc::new(LinuxDisplay::new(
166 &self.xcb_connection,
167 id.0 as i32,
168 )))
169 }
170
171 fn active_window(&self) -> Option<AnyWindowHandle> {
172 None
173 }
174
175 fn open_window(
176 &self,
177 handle: AnyWindowHandle,
178 options: WindowOptions,
179 ) -> Box<dyn PlatformWindow> {
180 let x_window = self.xcb_connection.generate_id();
181
182 let window_ptr = Arc::new(LinuxWindowState::new(
183 options,
184 &self.xcb_connection,
185 self.x_root_index,
186 x_window,
187 &self.atoms,
188 ));
189 self.state
190 .lock()
191 .windows
192 .insert(x_window, Arc::clone(&window_ptr));
193 Box::new(LinuxWindow(window_ptr))
194 }
195
196 fn set_display_link_output_callback(
197 &self,
198 display_id: DisplayId,
199 callback: Box<dyn FnMut() + Send>,
200 ) {
201 log::warn!("unimplemented: set_display_link_output_callback");
202 }
203
204 fn start_display_link(&self, display_id: DisplayId) {}
205
206 fn stop_display_link(&self, display_id: DisplayId) {}
207
208 fn open_url(&self, url: &str) {}
209
210 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {}
211
212 fn prompt_for_paths(
213 &self,
214 options: PathPromptOptions,
215 ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
216 unimplemented!()
217 }
218
219 fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
220 unimplemented!()
221 }
222
223 fn reveal_path(&self, path: &Path) {}
224
225 fn on_become_active(&self, callback: Box<dyn FnMut()>) {}
226
227 fn on_resign_active(&self, callback: Box<dyn FnMut()>) {}
228
229 fn on_quit(&self, callback: Box<dyn FnMut()>) {}
230
231 fn on_reopen(&self, callback: Box<dyn FnMut()>) {}
232
233 fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {}
234
235 fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {}
236
237 fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {}
238
239 fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {}
240
241 fn os_name(&self) -> &'static str {
242 "Linux"
243 }
244
245 fn double_click_interval(&self) -> Duration {
246 Duration::default()
247 }
248
249 fn os_version(&self) -> Result<SemanticVersion> {
250 Ok(SemanticVersion {
251 major: 1,
252 minor: 0,
253 patch: 0,
254 })
255 }
256
257 fn app_version(&self) -> Result<SemanticVersion> {
258 Ok(SemanticVersion {
259 major: 1,
260 minor: 0,
261 patch: 0,
262 })
263 }
264
265 fn app_path(&self) -> Result<PathBuf> {
266 unimplemented!()
267 }
268
269 fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
270
271 fn local_timezone(&self) -> UtcOffset {
272 UtcOffset::UTC
273 }
274
275 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
276 unimplemented!()
277 }
278
279 fn set_cursor_style(&self, style: CursorStyle) {}
280
281 fn should_auto_hide_scrollbars(&self) -> bool {
282 false
283 }
284
285 fn write_to_clipboard(&self, item: ClipboardItem) {}
286
287 fn read_from_clipboard(&self) -> Option<ClipboardItem> {
288 None
289 }
290
291 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
292 unimplemented!()
293 }
294
295 fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
296 unimplemented!()
297 }
298
299 fn delete_credentials(&self, url: &str) -> Task<Result<()>> {
300 unimplemented!()
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use crate::ClipboardItem;
307
308 use super::*;
309
310 fn build_platform() -> LinuxPlatform {
311 let platform = LinuxPlatform::new();
312 platform
313 }
314}