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