window.rs

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