window.rs

  1// todo(linux): remove
  2#![allow(unused)]
  3
  4use crate::{
  5    platform::blade::{BladeRenderer, BladeSurfaceConfig},
  6    size, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels, Platform, PlatformAtlas,
  7    PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
  8    Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowOptions,
  9    WindowParams, X11Client, X11ClientState, X11ClientStatePtr,
 10};
 11
 12use blade_graphics as gpu;
 13use parking_lot::Mutex;
 14use raw_window_handle as rwh;
 15use util::ResultExt;
 16use x11rb::{
 17    connection::{Connection as _, RequestConnection as _},
 18    protocol::{
 19        render,
 20        xinput::{self, ConnectionExt as _},
 21        xproto::{
 22            self, Atom, ClientMessageEvent, ConnectionExt as _, CreateWindowAux, EventMask,
 23            TranslateCoordinatesReply,
 24        },
 25    },
 26    resource_manager::Database,
 27    wrapper::ConnectionExt as _,
 28    xcb_ffi::XCBConnection,
 29};
 30
 31use std::{
 32    cell::{Ref, RefCell, RefMut},
 33    collections::HashMap,
 34    ffi::c_void,
 35    iter::Zip,
 36    mem,
 37    num::NonZeroU32,
 38    ops::Div,
 39    ptr::NonNull,
 40    rc::Rc,
 41    sync::{self, Arc},
 42};
 43
 44use super::{X11Display, XINPUT_MASTER_DEVICE};
 45
 46x11rb::atom_manager! {
 47    pub XcbAtoms: AtomsCookie {
 48        UTF8_STRING,
 49        WM_PROTOCOLS,
 50        WM_DELETE_WINDOW,
 51        WM_CHANGE_STATE,
 52        _NET_WM_NAME,
 53        _NET_WM_STATE,
 54        _NET_WM_STATE_MAXIMIZED_VERT,
 55        _NET_WM_STATE_MAXIMIZED_HORZ,
 56        _NET_WM_STATE_FULLSCREEN,
 57        _NET_WM_STATE_HIDDEN,
 58        _NET_WM_STATE_FOCUSED,
 59        _NET_WM_MOVERESIZE,
 60        _GTK_SHOW_WINDOW_MENU,
 61    }
 62}
 63
 64fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
 65    let reply = xcb_connection
 66        .get_geometry(x_window)
 67        .unwrap()
 68        .reply()
 69        .unwrap();
 70    gpu::Extent {
 71        width: reply.width as u32,
 72        height: reply.height as u32,
 73        depth: 1,
 74    }
 75}
 76
 77#[derive(Debug)]
 78struct Visual {
 79    id: xproto::Visualid,
 80    colormap: u32,
 81    depth: u8,
 82}
 83
 84struct VisualSet {
 85    inherit: Visual,
 86    opaque: Option<Visual>,
 87    transparent: Option<Visual>,
 88    root: u32,
 89    black_pixel: u32,
 90}
 91
 92fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet {
 93    let screen = &xcb_connection.setup().roots[screen_index];
 94    let mut set = VisualSet {
 95        inherit: Visual {
 96            id: screen.root_visual,
 97            colormap: screen.default_colormap,
 98            depth: screen.root_depth,
 99        },
100        opaque: None,
101        transparent: None,
102        root: screen.root,
103        black_pixel: screen.black_pixel,
104    };
105
106    for depth_info in screen.allowed_depths.iter() {
107        for visual_type in depth_info.visuals.iter() {
108            let visual = Visual {
109                id: visual_type.visual_id,
110                colormap: 0,
111                depth: depth_info.depth,
112            };
113            log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
114                visual_type.visual_id,
115                visual_type.class,
116                depth_info.depth,
117                visual_type.bits_per_rgb_value,
118                visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
119            );
120
121            if (
122                visual_type.red_mask,
123                visual_type.green_mask,
124                visual_type.blue_mask,
125            ) != (0xFF0000, 0xFF00, 0xFF)
126            {
127                continue;
128            }
129            let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
130            let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
131
132            if alpha_mask == 0 {
133                if set.opaque.is_none() {
134                    set.opaque = Some(visual);
135                }
136            } else {
137                if set.transparent.is_none() {
138                    set.transparent = Some(visual);
139                }
140            }
141        }
142    }
143
144    set
145}
146
147struct RawWindow {
148    connection: *mut c_void,
149    screen_id: usize,
150    window_id: u32,
151    visual_id: u32,
152}
153
154#[derive(Default)]
155pub struct Callbacks {
156    request_frame: Option<Box<dyn FnMut()>>,
157    input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
158    active_status_change: Option<Box<dyn FnMut(bool)>>,
159    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
160    moved: Option<Box<dyn FnMut()>>,
161    should_close: Option<Box<dyn FnMut() -> bool>>,
162    close: Option<Box<dyn FnOnce()>>,
163    appearance_changed: Option<Box<dyn FnMut()>>,
164}
165
166pub(crate) struct X11WindowState {
167    client: X11ClientStatePtr,
168    executor: ForegroundExecutor,
169    atoms: XcbAtoms,
170    x_root_window: xproto::Window,
171    raw: RawWindow,
172    bounds: Bounds<i32>,
173    scale_factor: f32,
174    renderer: BladeRenderer,
175    display: Rc<dyn PlatformDisplay>,
176    input_handler: Option<PlatformInputHandler>,
177}
178
179#[derive(Clone)]
180pub(crate) struct X11WindowStatePtr {
181    pub(crate) state: Rc<RefCell<X11WindowState>>,
182    pub(crate) callbacks: Rc<RefCell<Callbacks>>,
183    xcb_connection: Rc<XCBConnection>,
184    x_window: xproto::Window,
185}
186
187// todo(linux): Remove other RawWindowHandle implementation
188impl rwh::HasWindowHandle for RawWindow {
189    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
190        let non_zero = NonZeroU32::new(self.window_id).unwrap();
191        let mut handle = rwh::XcbWindowHandle::new(non_zero);
192        handle.visual_id = NonZeroU32::new(self.visual_id);
193        Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
194    }
195}
196impl rwh::HasDisplayHandle for RawWindow {
197    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
198        let non_zero = NonNull::new(self.connection).unwrap();
199        let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
200        Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
201    }
202}
203
204impl rwh::HasWindowHandle for X11Window {
205    fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
206        unimplemented!()
207    }
208}
209impl rwh::HasDisplayHandle for X11Window {
210    fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
211        unimplemented!()
212    }
213}
214
215impl X11WindowState {
216    #[allow(clippy::too_many_arguments)]
217    pub fn new(
218        client: X11ClientStatePtr,
219        executor: ForegroundExecutor,
220        params: WindowParams,
221        xcb_connection: &Rc<XCBConnection>,
222        x_main_screen_index: usize,
223        x_window: xproto::Window,
224        atoms: &XcbAtoms,
225        scale_factor: f32,
226    ) -> Self {
227        let x_screen_index = params
228            .display_id
229            .map_or(x_main_screen_index, |did| did.0 as usize);
230
231        let visual_set = find_visuals(&xcb_connection, x_screen_index);
232        let visual_maybe = match params.window_background {
233            WindowBackgroundAppearance::Opaque => visual_set.opaque,
234            WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
235                visual_set.transparent
236            }
237        };
238        let visual = match visual_maybe {
239            Some(visual) => visual,
240            None => {
241                log::warn!(
242                    "Unable to find a matching visual for {:?}",
243                    params.window_background
244                );
245                visual_set.inherit
246            }
247        };
248        log::info!("Using {:?}", visual);
249
250        let colormap = if visual.colormap != 0 {
251            visual.colormap
252        } else {
253            let id = xcb_connection.generate_id().unwrap();
254            log::info!("Creating colormap {}", id);
255            xcb_connection
256                .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
257                .unwrap()
258                .check()
259                .unwrap();
260            id
261        };
262
263        let win_aux = xproto::CreateWindowAux::new()
264            .background_pixel(x11rb::NONE)
265            // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
266            .border_pixel(visual_set.black_pixel)
267            .colormap(colormap)
268            .event_mask(
269                xproto::EventMask::EXPOSURE
270                    | xproto::EventMask::STRUCTURE_NOTIFY
271                    | xproto::EventMask::ENTER_WINDOW
272                    | xproto::EventMask::LEAVE_WINDOW
273                    | xproto::EventMask::FOCUS_CHANGE
274                    | xproto::EventMask::KEY_PRESS
275                    | xproto::EventMask::KEY_RELEASE,
276            );
277
278        xcb_connection
279            .create_window(
280                visual.depth,
281                x_window,
282                visual_set.root,
283                params.bounds.origin.x.0 as i16,
284                params.bounds.origin.y.0 as i16,
285                params.bounds.size.width.0 as u16,
286                params.bounds.size.height.0 as u16,
287                0,
288                xproto::WindowClass::INPUT_OUTPUT,
289                visual.id,
290                &win_aux,
291            )
292            .unwrap()
293            .check()
294            .unwrap();
295
296        if let Some(titlebar) = params.titlebar {
297            if let Some(title) = titlebar.title {
298                xcb_connection
299                    .change_property8(
300                        xproto::PropMode::REPLACE,
301                        x_window,
302                        xproto::AtomEnum::WM_NAME,
303                        xproto::AtomEnum::STRING,
304                        title.as_bytes(),
305                    )
306                    .unwrap();
307            }
308        }
309
310        xcb_connection
311            .change_property32(
312                xproto::PropMode::REPLACE,
313                x_window,
314                atoms.WM_PROTOCOLS,
315                xproto::AtomEnum::ATOM,
316                &[atoms.WM_DELETE_WINDOW],
317            )
318            .unwrap();
319
320        xcb_connection
321            .xinput_xi_select_events(
322                x_window,
323                &[xinput::EventMask {
324                    deviceid: XINPUT_MASTER_DEVICE,
325                    mask: vec![
326                        xinput::XIEventMask::MOTION
327                            | xinput::XIEventMask::BUTTON_PRESS
328                            | xinput::XIEventMask::BUTTON_RELEASE
329                            | xinput::XIEventMask::LEAVE,
330                    ],
331                }],
332            )
333            .unwrap();
334
335        xcb_connection.map_window(x_window).unwrap();
336        xcb_connection.flush().unwrap();
337
338        let raw = RawWindow {
339            connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
340                xcb_connection,
341            ) as *mut _,
342            screen_id: x_screen_index,
343            window_id: x_window,
344            visual_id: visual.id,
345        };
346        let gpu = Arc::new(
347            unsafe {
348                gpu::Context::init_windowed(
349                    &raw,
350                    gpu::ContextDesc {
351                        validation: false,
352                        capture: false,
353                        overlay: false,
354                    },
355                )
356            }
357            .unwrap(),
358        );
359
360        let config = BladeSurfaceConfig {
361            // Note: this has to be done after the GPU init, or otherwise
362            // the sizes are immediately invalidated.
363            size: query_render_extent(xcb_connection, x_window),
364            transparent: params.window_background != WindowBackgroundAppearance::Opaque,
365        };
366
367        Self {
368            client,
369            executor,
370            display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
371            raw,
372            x_root_window: visual_set.root,
373            bounds: params.bounds.map(|v| v.0),
374            scale_factor,
375            renderer: BladeRenderer::new(gpu, config),
376            atoms: *atoms,
377            input_handler: None,
378        }
379    }
380
381    fn content_size(&self) -> Size<Pixels> {
382        let size = self.renderer.viewport_size();
383        Size {
384            width: size.width.into(),
385            height: size.height.into(),
386        }
387    }
388}
389
390pub(crate) struct X11Window(pub X11WindowStatePtr);
391
392impl Drop for X11Window {
393    fn drop(&mut self) {
394        let mut state = self.0.state.borrow_mut();
395        state.renderer.destroy();
396
397        self.0.xcb_connection.unmap_window(self.0.x_window).unwrap();
398        self.0
399            .xcb_connection
400            .destroy_window(self.0.x_window)
401            .unwrap();
402        self.0.xcb_connection.flush().unwrap();
403
404        let this_ptr = self.0.clone();
405        let client_ptr = state.client.clone();
406        state
407            .executor
408            .spawn(async move {
409                this_ptr.close();
410                client_ptr.drop_window(this_ptr.x_window);
411            })
412            .detach();
413        drop(state);
414    }
415}
416
417enum WmHintPropertyState {
418    Remove = 0,
419    Add = 1,
420    Toggle = 2,
421}
422
423impl X11Window {
424    #[allow(clippy::too_many_arguments)]
425    pub fn new(
426        client: X11ClientStatePtr,
427        executor: ForegroundExecutor,
428        params: WindowParams,
429        xcb_connection: &Rc<XCBConnection>,
430        x_main_screen_index: usize,
431        x_window: xproto::Window,
432        atoms: &XcbAtoms,
433        scale_factor: f32,
434    ) -> Self {
435        Self(X11WindowStatePtr {
436            state: Rc::new(RefCell::new(X11WindowState::new(
437                client,
438                executor,
439                params,
440                xcb_connection,
441                x_main_screen_index,
442                x_window,
443                atoms,
444                scale_factor,
445            ))),
446            callbacks: Rc::new(RefCell::new(Callbacks::default())),
447            xcb_connection: xcb_connection.clone(),
448            x_window,
449        })
450    }
451
452    fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
453        let state = self.0.state.borrow();
454        let message = ClientMessageEvent::new(
455            32,
456            self.0.x_window,
457            state.atoms._NET_WM_STATE,
458            [wm_hint_property_state as u32, prop1, prop2, 1, 0],
459        );
460        self.0
461            .xcb_connection
462            .send_event(
463                false,
464                state.x_root_window,
465                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
466                message,
467            )
468            .unwrap();
469    }
470
471    fn get_wm_hints(&self) -> Vec<u32> {
472        let reply = self
473            .0
474            .xcb_connection
475            .get_property(
476                false,
477                self.0.x_window,
478                self.0.state.borrow().atoms._NET_WM_STATE,
479                xproto::AtomEnum::ATOM,
480                0,
481                u32::MAX,
482            )
483            .unwrap()
484            .reply()
485            .unwrap();
486        // Reply is in u8 but atoms are represented as u32
487        reply
488            .value
489            .chunks_exact(4)
490            .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
491            .collect()
492    }
493
494    fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply {
495        let state = self.0.state.borrow();
496        self.0
497            .xcb_connection
498            .translate_coordinates(
499                self.0.x_window,
500                state.x_root_window,
501                (position.x.0 * state.scale_factor) as i16,
502                (position.y.0 * state.scale_factor) as i16,
503            )
504            .unwrap()
505            .reply()
506            .unwrap()
507    }
508}
509
510impl X11WindowStatePtr {
511    pub fn should_close(&self) -> bool {
512        let mut cb = self.callbacks.borrow_mut();
513        if let Some(mut should_close) = cb.should_close.take() {
514            let result = (should_close)();
515            cb.should_close = Some(should_close);
516            result
517        } else {
518            true
519        }
520    }
521
522    pub fn close(&self) {
523        let mut callbacks = self.callbacks.borrow_mut();
524        if let Some(fun) = callbacks.close.take() {
525            fun()
526        }
527    }
528
529    pub fn refresh(&self) {
530        let mut cb = self.callbacks.borrow_mut();
531        if let Some(ref mut fun) = cb.request_frame {
532            fun();
533        }
534    }
535
536    pub fn handle_input(&self, input: PlatformInput) {
537        if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
538            if !fun(input.clone()).propagate {
539                return;
540            }
541        }
542        if let PlatformInput::KeyDown(event) = input {
543            let mut state = self.state.borrow_mut();
544            if let Some(mut input_handler) = state.input_handler.take() {
545                if let Some(ime_key) = &event.keystroke.ime_key {
546                    drop(state);
547                    input_handler.replace_text_in_range(None, ime_key);
548                    state = self.state.borrow_mut();
549                }
550                state.input_handler = Some(input_handler);
551            }
552        }
553    }
554
555    pub fn handle_ime_commit(&self, text: String) {
556        let mut state = self.state.borrow_mut();
557        if let Some(mut input_handler) = state.input_handler.take() {
558            drop(state);
559            input_handler.replace_text_in_range(None, &text);
560            let mut state = self.state.borrow_mut();
561            state.input_handler = Some(input_handler);
562        }
563    }
564
565    pub fn handle_ime_preedit(&self, text: String) {
566        let mut state = self.state.borrow_mut();
567        if let Some(mut input_handler) = state.input_handler.take() {
568            drop(state);
569            input_handler.replace_and_mark_text_in_range(None, &text, None);
570            let mut state = self.state.borrow_mut();
571            state.input_handler = Some(input_handler);
572        }
573    }
574
575    pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
576        let mut state = self.state.borrow_mut();
577        let mut bounds: Option<Bounds<Pixels>> = None;
578        if let Some(mut input_handler) = state.input_handler.take() {
579            drop(state);
580            if let Some(range) = input_handler.selected_text_range() {
581                bounds = input_handler.bounds_for_range(range);
582            }
583            let mut state = self.state.borrow_mut();
584            state.input_handler = Some(input_handler);
585        };
586        bounds
587    }
588
589    pub fn configure(&self, bounds: Bounds<i32>) {
590        let mut resize_args = None;
591        let do_move;
592        {
593            let mut state = self.state.borrow_mut();
594            let old_bounds = mem::replace(&mut state.bounds, bounds);
595            do_move = old_bounds.origin != bounds.origin;
596            // todo(linux): use normal GPUI types here, refactor out the double
597            // viewport check and extra casts ( )
598            let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
599            if state.renderer.viewport_size() != gpu_size {
600                state
601                    .renderer
602                    .update_drawable_size(size(gpu_size.width as f64, gpu_size.height as f64));
603                resize_args = Some((state.content_size(), state.scale_factor));
604            }
605        }
606
607        let mut callbacks = self.callbacks.borrow_mut();
608        if let Some((content_size, scale_factor)) = resize_args {
609            if let Some(ref mut fun) = callbacks.resize {
610                fun(content_size, scale_factor)
611            }
612        }
613        if do_move {
614            if let Some(ref mut fun) = callbacks.moved {
615                fun()
616            }
617        }
618    }
619
620    pub fn set_focused(&self, focus: bool) {
621        if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
622            fun(focus);
623        }
624    }
625}
626
627impl PlatformWindow for X11Window {
628    fn bounds(&self) -> Bounds<DevicePixels> {
629        self.0.state.borrow().bounds.map(|v| v.into())
630    }
631
632    fn is_maximized(&self) -> bool {
633        let state = self.0.state.borrow();
634        let wm_hints = self.get_wm_hints();
635        // A maximized window that gets minimized will still retain its maximized state.
636        !wm_hints.contains(&state.atoms._NET_WM_STATE_HIDDEN)
637            && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_VERT)
638            && wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_HORZ)
639    }
640
641    fn window_bounds(&self) -> WindowBounds {
642        let state = self.0.state.borrow();
643        WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p)))
644    }
645
646    fn content_size(&self) -> Size<Pixels> {
647        // We divide by the scale factor here because this value is queried to determine how much to draw,
648        // but it will be multiplied later by the scale to adjust for scaling.
649        let state = self.0.state.borrow();
650        state
651            .content_size()
652            .map(|size| size.div(state.scale_factor))
653    }
654
655    fn scale_factor(&self) -> f32 {
656        self.0.state.borrow().scale_factor
657    }
658
659    // todo(linux)
660    fn appearance(&self) -> WindowAppearance {
661        WindowAppearance::Light
662    }
663
664    fn display(&self) -> Rc<dyn PlatformDisplay> {
665        self.0.state.borrow().display.clone()
666    }
667
668    fn mouse_position(&self) -> Point<Pixels> {
669        let reply = self
670            .0
671            .xcb_connection
672            .query_pointer(self.0.x_window)
673            .unwrap()
674            .reply()
675            .unwrap();
676        Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
677    }
678
679    // todo(linux)
680    fn modifiers(&self) -> Modifiers {
681        Modifiers::default()
682    }
683
684    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
685        self.0.state.borrow_mut().input_handler = Some(input_handler);
686    }
687
688    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
689        self.0.state.borrow_mut().input_handler.take()
690    }
691
692    fn prompt(
693        &self,
694        _level: PromptLevel,
695        _msg: &str,
696        _detail: Option<&str>,
697        _answers: &[&str],
698    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
699        None
700    }
701
702    fn activate(&self) {
703        let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
704        self.0
705            .xcb_connection
706            .configure_window(self.0.x_window, &win_aux)
707            .log_err();
708    }
709
710    fn is_active(&self) -> bool {
711        let state = self.0.state.borrow();
712        self.get_wm_hints()
713            .contains(&state.atoms._NET_WM_STATE_FOCUSED)
714    }
715
716    fn set_title(&mut self, title: &str) {
717        self.0
718            .xcb_connection
719            .change_property8(
720                xproto::PropMode::REPLACE,
721                self.0.x_window,
722                xproto::AtomEnum::WM_NAME,
723                xproto::AtomEnum::STRING,
724                title.as_bytes(),
725            )
726            .unwrap();
727
728        self.0
729            .xcb_connection
730            .change_property8(
731                xproto::PropMode::REPLACE,
732                self.0.x_window,
733                self.0.state.borrow().atoms._NET_WM_NAME,
734                self.0.state.borrow().atoms.UTF8_STRING,
735                title.as_bytes(),
736            )
737            .unwrap();
738    }
739
740    fn set_app_id(&mut self, app_id: &str) {
741        let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
742        data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
743        data.push(b'\0');
744        data.extend(app_id.bytes()); // class
745
746        self.0
747            .xcb_connection
748            .change_property8(
749                xproto::PropMode::REPLACE,
750                self.0.x_window,
751                xproto::AtomEnum::WM_CLASS,
752                xproto::AtomEnum::STRING,
753                &data,
754            )
755            .unwrap();
756    }
757
758    // todo(linux)
759    fn set_edited(&mut self, edited: bool) {}
760
761    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
762        let mut inner = self.0.state.borrow_mut();
763        let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
764        inner.renderer.update_transparency(transparent);
765    }
766
767    // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
768    // but it looks like the equivalent for Linux is GTK specific:
769    //
770    // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
771    //
772    // This API might need to change, or we might need to build an emoji picker into GPUI
773    fn show_character_palette(&self) {
774        unimplemented!()
775    }
776
777    fn minimize(&self) {
778        let state = self.0.state.borrow();
779        const WINDOW_ICONIC_STATE: u32 = 3;
780        let message = ClientMessageEvent::new(
781            32,
782            self.0.x_window,
783            state.atoms.WM_CHANGE_STATE,
784            [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
785        );
786        self.0
787            .xcb_connection
788            .send_event(
789                false,
790                state.x_root_window,
791                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
792                message,
793            )
794            .unwrap();
795    }
796
797    fn zoom(&self) {
798        let state = self.0.state.borrow();
799        self.set_wm_hints(
800            WmHintPropertyState::Toggle,
801            state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
802            state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
803        );
804    }
805
806    fn toggle_fullscreen(&self) {
807        let state = self.0.state.borrow();
808        self.set_wm_hints(
809            WmHintPropertyState::Toggle,
810            state.atoms._NET_WM_STATE_FULLSCREEN,
811            xproto::AtomEnum::NONE.into(),
812        );
813    }
814
815    fn is_fullscreen(&self) -> bool {
816        let state = self.0.state.borrow();
817        self.get_wm_hints()
818            .contains(&state.atoms._NET_WM_STATE_FULLSCREEN)
819    }
820
821    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
822        self.0.callbacks.borrow_mut().request_frame = Some(callback);
823    }
824
825    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
826        self.0.callbacks.borrow_mut().input = Some(callback);
827    }
828
829    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
830        self.0.callbacks.borrow_mut().active_status_change = Some(callback);
831    }
832
833    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
834        self.0.callbacks.borrow_mut().resize = Some(callback);
835    }
836
837    fn on_moved(&self, callback: Box<dyn FnMut()>) {
838        self.0.callbacks.borrow_mut().moved = Some(callback);
839    }
840
841    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
842        self.0.callbacks.borrow_mut().should_close = Some(callback);
843    }
844
845    fn on_close(&self, callback: Box<dyn FnOnce()>) {
846        self.0.callbacks.borrow_mut().close = Some(callback);
847    }
848
849    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
850        self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
851    }
852
853    fn draw(&self, scene: &Scene) {
854        let mut inner = self.0.state.borrow_mut();
855        inner.renderer.draw(scene);
856    }
857
858    fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
859        let inner = self.0.state.borrow();
860        inner.renderer.sprite_atlas().clone()
861    }
862
863    fn show_window_menu(&self, position: Point<Pixels>) {
864        let state = self.0.state.borrow();
865        let coords = self.get_root_position(position);
866        let message = ClientMessageEvent::new(
867            32,
868            self.0.x_window,
869            state.atoms._GTK_SHOW_WINDOW_MENU,
870            [
871                XINPUT_MASTER_DEVICE as u32,
872                coords.dst_x as u32,
873                coords.dst_y as u32,
874                0,
875                0,
876            ],
877        );
878        self.0
879            .xcb_connection
880            .send_event(
881                false,
882                state.x_root_window,
883                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
884                message,
885            )
886            .unwrap();
887    }
888
889    fn start_system_move(&self) {
890        let state = self.0.state.borrow();
891        let pointer = self
892            .0
893            .xcb_connection
894            .query_pointer(self.0.x_window)
895            .unwrap()
896            .reply()
897            .unwrap();
898        const MOVERESIZE_MOVE: u32 = 8;
899        let message = ClientMessageEvent::new(
900            32,
901            self.0.x_window,
902            state.atoms._NET_WM_MOVERESIZE,
903            [
904                pointer.root_x as u32,
905                pointer.root_y as u32,
906                MOVERESIZE_MOVE,
907                1, // Left mouse button
908                1,
909            ],
910        );
911        self.0
912            .xcb_connection
913            .send_event(
914                false,
915                state.x_root_window,
916                EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
917                message,
918            )
919            .unwrap();
920    }
921
922    fn should_render_window_controls(&self) -> bool {
923        false
924    }
925}