platform.rs

  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}