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