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