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