window.rs

  1// todo(linux): remove
  2#![allow(unused)]
  3
  4use crate::{
  5    platform::blade::BladeRenderer, size, Bounds, DevicePixels, ForegroundExecutor, Modifiers,
  6    Pixels, Platform, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
  7    PlatformWindow, Point, PromptLevel, Scene, Size, WindowAppearance, WindowBackgroundAppearance,
  8    WindowOptions, WindowParams, X11Client, X11ClientState, X11ClientStatePtr,
  9};
 10use blade_graphics as gpu;
 11use parking_lot::Mutex;
 12use raw_window_handle as rwh;
 13use util::ResultExt;
 14use x11rb::{
 15    connection::Connection,
 16    protocol::{
 17        xinput,
 18        xproto::{self, ConnectionExt as _, CreateWindowAux},
 19    },
 20    resource_manager::Database,
 21    wrapper::ConnectionExt,
 22    xcb_ffi::XCBConnection,
 23};
 24
 25use std::{
 26    cell::{Ref, RefCell, RefMut},
 27    ffi::c_void,
 28    iter::Zip,
 29    mem,
 30    num::NonZeroU32,
 31    ops::Div,
 32    ptr::NonNull,
 33    rc::Rc,
 34    sync::{self, Arc},
 35};
 36
 37use super::X11Display;
 38
 39x11rb::atom_manager! {
 40    pub XcbAtoms: AtomsCookie {
 41        UTF8_STRING,
 42        WM_PROTOCOLS,
 43        WM_DELETE_WINDOW,
 44        _NET_WM_NAME,
 45        _NET_WM_STATE,
 46        _NET_WM_STATE_MAXIMIZED_VERT,
 47        _NET_WM_STATE_MAXIMIZED_HORZ,
 48    }
 49}
 50
 51fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
 52    let reply = xcb_connection
 53        .get_geometry(x_window)
 54        .unwrap()
 55        .reply()
 56        .unwrap();
 57    gpu::Extent {
 58        width: reply.width as u32,
 59        height: reply.height as u32,
 60        depth: 1,
 61    }
 62}
 63
 64struct RawWindow {
 65    connection: *mut c_void,
 66    screen_id: usize,
 67    window_id: u32,
 68    visual_id: u32,
 69}
 70
 71#[derive(Default)]
 72pub struct Callbacks {
 73    request_frame: Option<Box<dyn FnMut()>>,
 74    input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
 75    active_status_change: Option<Box<dyn FnMut(bool)>>,
 76    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
 77    fullscreen: Option<Box<dyn FnMut(bool)>>,
 78    moved: Option<Box<dyn FnMut()>>,
 79    should_close: Option<Box<dyn FnMut() -> bool>>,
 80    close: Option<Box<dyn FnOnce()>>,
 81    appearance_changed: Option<Box<dyn FnMut()>>,
 82}
 83
 84pub(crate) struct X11WindowState {
 85    client: X11ClientStatePtr,
 86    executor: ForegroundExecutor,
 87    atoms: XcbAtoms,
 88    raw: RawWindow,
 89    bounds: Bounds<i32>,
 90    scale_factor: f32,
 91    renderer: BladeRenderer,
 92    display: Rc<dyn PlatformDisplay>,
 93
 94    input_handler: Option<PlatformInputHandler>,
 95}
 96
 97#[derive(Clone)]
 98pub(crate) struct X11WindowStatePtr {
 99    pub(crate) state: Rc<RefCell<X11WindowState>>,
100    pub(crate) callbacks: Rc<RefCell<Callbacks>>,
101    xcb_connection: Rc<XCBConnection>,
102    x_window: xproto::Window,
103}
104
105// todo(linux): Remove other RawWindowHandle implementation
106impl rwh::HasWindowHandle for RawWindow {
107    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
108        let non_zero = NonZeroU32::new(self.window_id).unwrap();
109        let handle = rwh::XcbWindowHandle::new(non_zero);
110        Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
111    }
112}
113impl rwh::HasDisplayHandle for RawWindow {
114    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
115        let non_zero = NonNull::new(self.connection).unwrap();
116        let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
117        Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
118    }
119}
120
121impl rwh::HasWindowHandle for X11Window {
122    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
123        unimplemented!()
124    }
125}
126impl rwh::HasDisplayHandle for X11Window {
127    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
128        unimplemented!()
129    }
130}
131
132impl X11WindowState {
133    #[allow(clippy::too_many_arguments)]
134    pub fn new(
135        client: X11ClientStatePtr,
136        executor: ForegroundExecutor,
137        params: WindowParams,
138        xcb_connection: &Rc<XCBConnection>,
139        x_main_screen_index: usize,
140        x_window: xproto::Window,
141        atoms: &XcbAtoms,
142        scale_factor: f32,
143    ) -> Self {
144        let x_screen_index = params
145            .display_id
146            .map_or(x_main_screen_index, |did| did.0 as usize);
147        let screen = xcb_connection.setup().roots.get(x_screen_index).unwrap();
148
149        let win_aux = xproto::CreateWindowAux::new().event_mask(
150            xproto::EventMask::EXPOSURE
151                | xproto::EventMask::STRUCTURE_NOTIFY
152                | xproto::EventMask::ENTER_WINDOW
153                | xproto::EventMask::LEAVE_WINDOW
154                | xproto::EventMask::FOCUS_CHANGE
155                | xproto::EventMask::KEY_PRESS
156                | xproto::EventMask::KEY_RELEASE
157                | xproto::EventMask::BUTTON_PRESS
158                | xproto::EventMask::BUTTON_RELEASE
159                | xproto::EventMask::POINTER_MOTION
160                | xproto::EventMask::BUTTON1_MOTION
161                | xproto::EventMask::BUTTON2_MOTION
162                | xproto::EventMask::BUTTON3_MOTION
163                | xproto::EventMask::BUTTON_MOTION,
164        );
165
166        xcb_connection
167            .create_window(
168                x11rb::COPY_FROM_PARENT as _,
169                x_window,
170                screen.root,
171                params.bounds.origin.x.0 as i16,
172                params.bounds.origin.y.0 as i16,
173                params.bounds.size.width.0 as u16,
174                params.bounds.size.height.0 as u16,
175                0,
176                xproto::WindowClass::INPUT_OUTPUT,
177                screen.root_visual,
178                &win_aux,
179            )
180            .unwrap();
181
182        xinput::ConnectionExt::xinput_xi_select_events(
183            &xcb_connection,
184            x_window,
185            &[xinput::EventMask {
186                deviceid: 1,
187                mask: vec![xinput::XIEventMask::MOTION],
188            }],
189        )
190        .unwrap()
191        .check()
192        .unwrap();
193
194        if let Some(titlebar) = params.titlebar {
195            if let Some(title) = titlebar.title {
196                xcb_connection
197                    .change_property8(
198                        xproto::PropMode::REPLACE,
199                        x_window,
200                        xproto::AtomEnum::WM_NAME,
201                        xproto::AtomEnum::STRING,
202                        title.as_bytes(),
203                    )
204                    .unwrap();
205            }
206        }
207
208        xcb_connection
209            .change_property32(
210                xproto::PropMode::REPLACE,
211                x_window,
212                atoms.WM_PROTOCOLS,
213                xproto::AtomEnum::ATOM,
214                &[atoms.WM_DELETE_WINDOW],
215            )
216            .unwrap();
217
218        xcb_connection.map_window(x_window).unwrap();
219        xcb_connection.flush().unwrap();
220
221        let raw = RawWindow {
222            connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
223                xcb_connection,
224            ) as *mut _,
225            screen_id: x_screen_index,
226            window_id: x_window,
227            visual_id: screen.root_visual,
228        };
229        let gpu = Arc::new(
230            unsafe {
231                gpu::Context::init_windowed(
232                    &raw,
233                    gpu::ContextDesc {
234                        validation: false,
235                        capture: false,
236                        overlay: false,
237                    },
238                )
239            }
240            .unwrap(),
241        );
242
243        // Note: this has to be done after the GPU init, or otherwise
244        // the sizes are immediately invalidated.
245        let gpu_extent = query_render_extent(xcb_connection, x_window);
246
247        Self {
248            client,
249            executor,
250            display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
251            raw,
252            bounds: params.bounds.map(|v| v.0),
253            scale_factor,
254            renderer: BladeRenderer::new(gpu, gpu_extent),
255            atoms: *atoms,
256
257            input_handler: None,
258        }
259    }
260
261    fn content_size(&self) -> Size<Pixels> {
262        let size = self.renderer.viewport_size();
263        Size {
264            width: size.width.into(),
265            height: size.height.into(),
266        }
267    }
268}
269
270pub(crate) struct X11Window(pub X11WindowStatePtr);
271
272impl Drop for X11Window {
273    fn drop(&mut self) {
274        let mut state = self.0.state.borrow_mut();
275        state.renderer.destroy();
276
277        self.0.xcb_connection.unmap_window(self.0.x_window).unwrap();
278        self.0
279            .xcb_connection
280            .destroy_window(self.0.x_window)
281            .unwrap();
282        self.0.xcb_connection.flush().unwrap();
283
284        let this_ptr = self.0.clone();
285        let client_ptr = state.client.clone();
286        state
287            .executor
288            .spawn(async move {
289                this_ptr.close();
290                client_ptr.drop_window(this_ptr.x_window);
291            })
292            .detach();
293        drop(state);
294    }
295}
296
297impl X11Window {
298    #[allow(clippy::too_many_arguments)]
299    pub fn new(
300        client: X11ClientStatePtr,
301        executor: ForegroundExecutor,
302        params: WindowParams,
303        xcb_connection: &Rc<XCBConnection>,
304        x_main_screen_index: usize,
305        x_window: xproto::Window,
306        atoms: &XcbAtoms,
307        scale_factor: f32,
308    ) -> Self {
309        Self(X11WindowStatePtr {
310            state: Rc::new(RefCell::new(X11WindowState::new(
311                client,
312                executor,
313                params,
314                xcb_connection,
315                x_main_screen_index,
316                x_window,
317                atoms,
318                scale_factor,
319            ))),
320            callbacks: Rc::new(RefCell::new(Callbacks::default())),
321            xcb_connection: xcb_connection.clone(),
322            x_window,
323        })
324    }
325}
326
327impl X11WindowStatePtr {
328    pub fn should_close(&self) -> bool {
329        let mut cb = self.callbacks.borrow_mut();
330        if let Some(mut should_close) = cb.should_close.take() {
331            let result = (should_close)();
332            cb.should_close = Some(should_close);
333            result
334        } else {
335            true
336        }
337    }
338
339    pub fn close(&self) {
340        let mut callbacks = self.callbacks.borrow_mut();
341        if let Some(fun) = callbacks.close.take() {
342            fun()
343        }
344    }
345
346    pub fn refresh(&self) {
347        let mut cb = self.callbacks.borrow_mut();
348        if let Some(ref mut fun) = cb.request_frame {
349            fun();
350        }
351    }
352
353    pub fn handle_input(&self, input: PlatformInput) {
354        if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
355            if !fun(input.clone()).propagate {
356                return;
357            }
358        }
359        if let PlatformInput::KeyDown(event) = input {
360            let mut state = self.state.borrow_mut();
361            if let Some(mut input_handler) = state.input_handler.take() {
362                if let Some(ime_key) = &event.keystroke.ime_key {
363                    drop(state);
364                    input_handler.replace_text_in_range(None, ime_key);
365                    state = self.state.borrow_mut();
366                }
367                state.input_handler = Some(input_handler);
368            }
369        }
370    }
371
372    pub fn configure(&self, bounds: Bounds<i32>) {
373        let mut resize_args = None;
374        let do_move;
375        {
376            let mut state = self.state.borrow_mut();
377            let old_bounds = mem::replace(&mut state.bounds, bounds);
378            do_move = old_bounds.origin != bounds.origin;
379            // todo(linux): use normal GPUI types here, refactor out the double
380            // viewport check and extra casts ( )
381            let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
382            if state.renderer.viewport_size() != gpu_size {
383                state
384                    .renderer
385                    .update_drawable_size(size(gpu_size.width as f64, gpu_size.height as f64));
386                resize_args = Some((state.content_size(), state.scale_factor));
387            }
388        }
389
390        let mut callbacks = self.callbacks.borrow_mut();
391        if let Some((content_size, scale_factor)) = resize_args {
392            if let Some(ref mut fun) = callbacks.resize {
393                fun(content_size, scale_factor)
394            }
395        }
396        if do_move {
397            if let Some(ref mut fun) = callbacks.moved {
398                fun()
399            }
400        }
401    }
402
403    pub fn set_focused(&self, focus: bool) {
404        if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
405            fun(focus);
406        }
407    }
408}
409
410impl PlatformWindow for X11Window {
411    fn bounds(&self) -> Bounds<DevicePixels> {
412        self.0.state.borrow().bounds.map(|v| v.into())
413    }
414
415    // todo(linux)
416    fn is_maximized(&self) -> bool {
417        false
418    }
419
420    // todo(linux)
421    fn is_minimized(&self) -> bool {
422        false
423    }
424
425    fn content_size(&self) -> Size<Pixels> {
426        // We divide by the scale factor here because this value is queried to determine how much to draw,
427        // but it will be multiplied later by the scale to adjust for scaling.
428        let state = self.0.state.borrow();
429        state
430            .content_size()
431            .map(|size| size.div(state.scale_factor))
432    }
433
434    fn scale_factor(&self) -> f32 {
435        self.0.state.borrow().scale_factor
436    }
437
438    // todo(linux)
439    fn appearance(&self) -> WindowAppearance {
440        WindowAppearance::Light
441    }
442
443    fn display(&self) -> Rc<dyn PlatformDisplay> {
444        self.0.state.borrow().display.clone()
445    }
446
447    fn mouse_position(&self) -> Point<Pixels> {
448        let reply = self
449            .0
450            .xcb_connection
451            .query_pointer(self.0.x_window)
452            .unwrap()
453            .reply()
454            .unwrap();
455        Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
456    }
457
458    // todo(linux)
459    fn modifiers(&self) -> Modifiers {
460        Modifiers::default()
461    }
462
463    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
464        self
465    }
466
467    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
468        self.0.state.borrow_mut().input_handler = Some(input_handler);
469    }
470
471    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
472        self.0.state.borrow_mut().input_handler.take()
473    }
474
475    fn prompt(
476        &self,
477        _level: PromptLevel,
478        _msg: &str,
479        _detail: Option<&str>,
480        _answers: &[&str],
481    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
482        None
483    }
484
485    fn activate(&self) {
486        let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
487        self.0
488            .xcb_connection
489            .configure_window(self.0.x_window, &win_aux)
490            .log_err();
491    }
492
493    // todo(linux)
494    fn is_active(&self) -> bool {
495        false
496    }
497
498    fn set_title(&mut self, title: &str) {
499        self.0
500            .xcb_connection
501            .change_property8(
502                xproto::PropMode::REPLACE,
503                self.0.x_window,
504                xproto::AtomEnum::WM_NAME,
505                xproto::AtomEnum::STRING,
506                title.as_bytes(),
507            )
508            .unwrap();
509
510        self.0
511            .xcb_connection
512            .change_property8(
513                xproto::PropMode::REPLACE,
514                self.0.x_window,
515                self.0.state.borrow().atoms._NET_WM_NAME,
516                self.0.state.borrow().atoms.UTF8_STRING,
517                title.as_bytes(),
518            )
519            .unwrap();
520    }
521
522    fn set_app_id(&mut self, app_id: &str) {
523        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
524        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
525        data.push(b'\0');
526        data.extend(app_id.bytes()); // class
527
528        self.0.xcb_connection.change_property8(
529            xproto::PropMode::REPLACE,
530            self.0.x_window,
531            xproto::AtomEnum::WM_CLASS,
532            xproto::AtomEnum::STRING,
533            &data,
534        );
535    }
536
537    // todo(linux)
538    fn set_edited(&mut self, edited: bool) {}
539
540    fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
541        // todo(linux)
542    }
543
544    // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
545    // but it looks like the equivalent for Linux is GTK specific:
546    //
547    // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
548    //
549    // This API might need to change, or we might need to build an emoji picker into GPUI
550    fn show_character_palette(&self) {
551        unimplemented!()
552    }
553
554    // todo(linux)
555    fn minimize(&self) {
556        unimplemented!()
557    }
558
559    // todo(linux)
560    fn zoom(&self) {
561        unimplemented!()
562    }
563
564    // todo(linux)
565    fn toggle_fullscreen(&self) {
566        unimplemented!()
567    }
568
569    // todo(linux)
570    fn is_fullscreen(&self) -> bool {
571        false
572    }
573
574    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
575        self.0.callbacks.borrow_mut().request_frame = Some(callback);
576    }
577
578    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
579        self.0.callbacks.borrow_mut().input = Some(callback);
580    }
581
582    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
583        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
584    }
585
586    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
587        self.0.callbacks.borrow_mut().resize = Some(callback);
588    }
589
590    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
591        self.0.callbacks.borrow_mut().fullscreen = Some(callback);
592    }
593
594    fn on_moved(&self, callback: Box<dyn FnMut()>) {
595        self.0.callbacks.borrow_mut().moved = Some(callback);
596    }
597
598    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
599        self.0.callbacks.borrow_mut().should_close = Some(callback);
600    }
601
602    fn on_close(&self, callback: Box<dyn FnOnce()>) {
603        self.0.callbacks.borrow_mut().close = Some(callback);
604    }
605
606    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
607        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
608    }
609
610    // todo(linux)
611    fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
612        unimplemented!()
613    }
614
615    fn draw(&self, scene: &Scene) {
616        let mut inner = self.0.state.borrow_mut();
617        inner.renderer.draw(scene);
618    }
619
620    fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
621        let inner = self.0.state.borrow();
622        inner.renderer.sprite_atlas().clone()
623    }
624}