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