window.rs

  1//todo!(linux): remove
  2#![allow(unused)]
  3
  4use super::BladeRenderer;
  5use crate::{
  6    Bounds, GlobalPixels, LinuxDisplay, Pixels, PlatformDisplay, PlatformInputHandler,
  7    PlatformWindow, Point, Size, WindowAppearance, WindowBounds, WindowOptions, XcbAtoms,
  8};
  9use blade_graphics as gpu;
 10use parking_lot::Mutex;
 11use raw_window_handle as rwh;
 12use std::{
 13    ffi::c_void,
 14    mem,
 15    num::NonZeroU32,
 16    ptr::NonNull,
 17    rc::Rc,
 18    sync::{self, Arc},
 19};
 20use xcb::{x, Xid as _};
 21
 22#[derive(Default)]
 23struct Callbacks {
 24    request_frame: Option<Box<dyn FnMut()>>,
 25    input: Option<Box<dyn FnMut(crate::PlatformInput) -> bool>>,
 26    active_status_change: Option<Box<dyn FnMut(bool)>>,
 27    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
 28    fullscreen: Option<Box<dyn FnMut(bool)>>,
 29    moved: Option<Box<dyn FnMut()>>,
 30    should_close: Option<Box<dyn FnMut() -> bool>>,
 31    close: Option<Box<dyn FnOnce()>>,
 32    appearance_changed: Option<Box<dyn FnMut()>>,
 33}
 34
 35struct LinuxWindowInner {
 36    bounds: Bounds<i32>,
 37    scale_factor: f32,
 38    renderer: BladeRenderer,
 39}
 40
 41impl LinuxWindowInner {
 42    fn content_size(&self) -> Size<Pixels> {
 43        let size = self.renderer.viewport_size();
 44        Size {
 45            width: size.width.into(),
 46            height: size.height.into(),
 47        }
 48    }
 49}
 50
 51fn query_render_extent(xcb_connection: &xcb::Connection, x_window: x::Window) -> gpu::Extent {
 52    let cookie = xcb_connection.send_request(&x::GetGeometry {
 53        drawable: x::Drawable::Window(x_window),
 54    });
 55    let reply = xcb_connection.wait_for_reply(cookie).unwrap();
 56    gpu::Extent {
 57        width: reply.width() as u32,
 58        height: reply.height() as u32,
 59        depth: 1,
 60    }
 61}
 62
 63struct RawWindow {
 64    connection: *mut c_void,
 65    screen_id: i32,
 66    window_id: u32,
 67    visual_id: u32,
 68}
 69
 70pub(crate) struct LinuxWindowState {
 71    xcb_connection: Arc<xcb::Connection>,
 72    display: Rc<dyn PlatformDisplay>,
 73    raw: RawWindow,
 74    x_window: x::Window,
 75    callbacks: Mutex<Callbacks>,
 76    inner: Mutex<LinuxWindowInner>,
 77}
 78
 79#[derive(Clone)]
 80pub(crate) struct LinuxWindow(pub(crate) Arc<LinuxWindowState>);
 81
 82//todo!(linux): Remove other RawWindowHandle implementation
 83unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
 84    fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
 85        let mut wh = blade_rwh::XcbWindowHandle::empty();
 86        wh.window = self.window_id;
 87        wh.visual_id = self.visual_id;
 88        wh.into()
 89    }
 90}
 91unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
 92    fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
 93        let mut dh = blade_rwh::XcbDisplayHandle::empty();
 94        dh.connection = self.connection;
 95        dh.screen = self.screen_id;
 96        dh.into()
 97    }
 98}
 99
100impl rwh::HasWindowHandle for LinuxWindow {
101    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
102        Ok(unsafe {
103            let non_zero = NonZeroU32::new(self.0.raw.window_id).unwrap();
104            let handle = rwh::XcbWindowHandle::new(non_zero);
105            rwh::WindowHandle::borrow_raw(handle.into())
106        })
107    }
108}
109impl rwh::HasDisplayHandle for LinuxWindow {
110    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
111        Ok(unsafe {
112            let non_zero = NonNull::new(self.0.raw.connection).unwrap();
113            let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.0.raw.screen_id);
114            rwh::DisplayHandle::borrow_raw(handle.into())
115        })
116    }
117}
118
119impl LinuxWindowState {
120    pub fn new(
121        options: WindowOptions,
122        xcb_connection: &Arc<xcb::Connection>,
123        x_main_screen_index: i32,
124        x_window: x::Window,
125        atoms: &XcbAtoms,
126    ) -> Self {
127        let x_screen_index = options
128            .display_id
129            .map_or(x_main_screen_index, |did| did.0 as i32);
130        let screen = xcb_connection
131            .get_setup()
132            .roots()
133            .nth(x_screen_index as usize)
134            .unwrap();
135
136        let xcb_values = [
137            x::Cw::BackPixel(screen.white_pixel()),
138            x::Cw::EventMask(
139                x::EventMask::EXPOSURE | x::EventMask::STRUCTURE_NOTIFY | x::EventMask::KEY_PRESS,
140            ),
141        ];
142
143        let bounds = match options.bounds {
144            WindowBounds::Fullscreen | WindowBounds::Maximized => Bounds {
145                origin: Point::default(),
146                size: Size {
147                    width: screen.width_in_pixels() as i32,
148                    height: screen.height_in_pixels() as i32,
149                },
150            },
151            WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32),
152        };
153
154        xcb_connection.send_request(&x::CreateWindow {
155            depth: x::COPY_FROM_PARENT as u8,
156            wid: x_window,
157            parent: screen.root(),
158            x: bounds.origin.x as i16,
159            y: bounds.origin.y as i16,
160            width: bounds.size.width as u16,
161            height: bounds.size.height as u16,
162            border_width: 0,
163            class: x::WindowClass::InputOutput,
164            visual: screen.root_visual(),
165            value_list: &xcb_values,
166        });
167
168        if let Some(titlebar) = options.titlebar {
169            if let Some(title) = titlebar.title {
170                xcb_connection.send_request(&x::ChangeProperty {
171                    mode: x::PropMode::Replace,
172                    window: x_window,
173                    property: x::ATOM_WM_NAME,
174                    r#type: x::ATOM_STRING,
175                    data: title.as_bytes(),
176                });
177            }
178        }
179        xcb_connection
180            .send_and_check_request(&x::ChangeProperty {
181                mode: x::PropMode::Replace,
182                window: x_window,
183                property: atoms.wm_protocols,
184                r#type: x::ATOM_ATOM,
185                data: &[atoms.wm_del_window],
186            })
187            .unwrap();
188
189        xcb_connection.send_request(&x::MapWindow { window: x_window });
190        xcb_connection.flush().unwrap();
191
192        //Warning: it looks like this reported size is immediately invalidated
193        // on some platforms, followed by a "ConfigureNotify" event.
194        let gpu_extent = query_render_extent(&xcb_connection, x_window);
195
196        let raw = RawWindow {
197            connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
198                xcb_connection,
199            ) as *mut _,
200            screen_id: x_screen_index,
201            window_id: x_window.resource_id(),
202            visual_id: screen.root_visual(),
203        };
204        let gpu = Arc::new(
205            unsafe {
206                gpu::Context::init_windowed(
207                    &raw,
208                    gpu::ContextDesc {
209                        validation: cfg!(debug_assertions),
210                        capture: false,
211                    },
212                )
213            }
214            .unwrap(),
215        );
216
217        Self {
218            xcb_connection: Arc::clone(xcb_connection),
219            display: Rc::new(LinuxDisplay::new(xcb_connection, x_screen_index)),
220            raw,
221            x_window,
222            callbacks: Mutex::new(Callbacks::default()),
223            inner: Mutex::new(LinuxWindowInner {
224                bounds,
225                scale_factor: 1.0,
226                renderer: BladeRenderer::new(gpu, gpu_extent),
227            }),
228        }
229    }
230
231    pub fn destroy(&self) {
232        self.inner.lock().renderer.destroy();
233        self.xcb_connection.send_request(&x::UnmapWindow {
234            window: self.x_window,
235        });
236        self.xcb_connection.send_request(&x::DestroyWindow {
237            window: self.x_window,
238        });
239        if let Some(fun) = self.callbacks.lock().close.take() {
240            fun();
241        }
242        self.xcb_connection.flush().unwrap();
243    }
244
245    pub fn expose(&self) {
246        let mut cb = self.callbacks.lock();
247        if let Some(ref mut fun) = cb.request_frame {
248            fun();
249        }
250    }
251
252    pub fn configure(&self, bounds: Bounds<i32>) {
253        let mut resize_args = None;
254        let do_move;
255        {
256            let mut inner = self.inner.lock();
257            let old_bounds = mem::replace(&mut inner.bounds, bounds);
258            do_move = old_bounds.origin != bounds.origin;
259            let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
260            if inner.renderer.viewport_size() != gpu_size {
261                inner.renderer.resize(gpu_size);
262                resize_args = Some((inner.content_size(), inner.scale_factor));
263            }
264        }
265
266        let mut callbacks = self.callbacks.lock();
267        if let Some((content_size, scale_factor)) = resize_args {
268            if let Some(ref mut fun) = callbacks.resize {
269                fun(content_size, scale_factor)
270            }
271        }
272        if do_move {
273            if let Some(ref mut fun) = callbacks.moved {
274                fun()
275            }
276        }
277    }
278}
279
280impl PlatformWindow for LinuxWindow {
281    fn bounds(&self) -> WindowBounds {
282        WindowBounds::Fixed(self.0.inner.lock().bounds.map(|v| GlobalPixels(v as f32)))
283    }
284
285    fn content_size(&self) -> Size<Pixels> {
286        self.0.inner.lock().content_size()
287    }
288
289    fn scale_factor(&self) -> f32 {
290        self.0.inner.lock().scale_factor
291    }
292
293    //todo!(linux)
294    fn titlebar_height(&self) -> Pixels {
295        unimplemented!()
296    }
297
298    //todo!(linux)
299    fn appearance(&self) -> WindowAppearance {
300        WindowAppearance::Light
301    }
302
303    fn display(&self) -> Rc<dyn PlatformDisplay> {
304        Rc::clone(&self.0.display)
305    }
306
307    //todo!(linux)
308    fn mouse_position(&self) -> Point<Pixels> {
309        Point::default()
310    }
311
312    //todo!(linux)
313    fn modifiers(&self) -> crate::Modifiers {
314        crate::Modifiers::default()
315    }
316
317    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
318        self
319    }
320
321    //todo!(linux)
322    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {}
323
324    //todo!(linux)
325    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
326        None
327    }
328
329    //todo!(linux)
330    fn prompt(
331        &self,
332        _level: crate::PromptLevel,
333        _msg: &str,
334        _detail: Option<&str>,
335        _answers: &[&str],
336    ) -> futures::channel::oneshot::Receiver<usize> {
337        unimplemented!()
338    }
339
340    //todo!(linux)
341    fn activate(&self) {}
342
343    fn set_title(&mut self, title: &str) {
344        self.0.xcb_connection.send_request(&x::ChangeProperty {
345            mode: x::PropMode::Replace,
346            window: self.0.x_window,
347            property: x::ATOM_WM_NAME,
348            r#type: x::ATOM_STRING,
349            data: title.as_bytes(),
350        });
351    }
352
353    //todo!(linux)
354    fn set_edited(&mut self, edited: bool) {}
355
356    //todo!(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
357    // but it looks like the equivalent for Linux is GTK specific:
358    //
359    // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
360    //
361    // This API might need to change, or we might need to build an emoji picker into GPUI
362    fn show_character_palette(&self) {
363        unimplemented!()
364    }
365
366    //todo!(linux)
367    fn minimize(&self) {
368        unimplemented!()
369    }
370
371    //todo!(linux)
372    fn zoom(&self) {
373        unimplemented!()
374    }
375
376    //todo!(linux)
377    fn toggle_full_screen(&self) {
378        unimplemented!()
379    }
380
381    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
382        self.0.callbacks.lock().request_frame = Some(callback);
383    }
384
385    fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
386        self.0.callbacks.lock().input = Some(callback);
387    }
388
389    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
390        self.0.callbacks.lock().active_status_change = Some(callback);
391    }
392
393    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
394        self.0.callbacks.lock().resize = Some(callback);
395    }
396
397    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
398        self.0.callbacks.lock().fullscreen = Some(callback);
399    }
400
401    fn on_moved(&self, callback: Box<dyn FnMut()>) {
402        self.0.callbacks.lock().moved = Some(callback);
403    }
404
405    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
406        self.0.callbacks.lock().should_close = Some(callback);
407    }
408
409    fn on_close(&self, callback: Box<dyn FnOnce()>) {
410        self.0.callbacks.lock().close = Some(callback);
411    }
412
413    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
414        self.0.callbacks.lock().appearance_changed = Some(callback);
415    }
416
417    //todo!(linux)
418    fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
419        unimplemented!()
420    }
421
422    fn draw(&self, scene: &crate::Scene) {
423        let mut inner = self.0.inner.lock();
424        inner.renderer.draw(scene);
425    }
426
427    fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
428        let inner = self.0.inner.lock();
429        inner.renderer.atlas().clone()
430    }
431
432    fn set_graphics_profiler_enabled(&self, enabled: bool) {
433        todo!("linux")
434    }
435}