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