1use anyhow::Context;
2
3use crate::{
4 platform::blade::{BladeRenderer, BladeSurfaceConfig},
5 px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, Modifiers,
6 Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
7 Point, PromptLevel, ResizeEdge, Scene, Size, Tiling, WindowAppearance,
8 WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowParams,
9 X11ClientStatePtr,
10};
11
12use blade_graphics as gpu;
13use raw_window_handle as rwh;
14use util::{maybe, ResultExt};
15use x11rb::{
16 connection::Connection,
17 protocol::{
18 sync,
19 xinput::{self, ConnectionExt as _},
20 xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},
21 },
22 wrapper::ConnectionExt as _,
23 xcb_ffi::XCBConnection,
24};
25
26use std::{
27 cell::RefCell, ffi::c_void, mem::size_of, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
28 sync::Arc,
29};
30
31use super::{X11Display, XINPUT_MASTER_DEVICE};
32x11rb::atom_manager! {
33 pub XcbAtoms: AtomsCookie {
34 UTF8_STRING,
35 WM_PROTOCOLS,
36 WM_DELETE_WINDOW,
37 WM_CHANGE_STATE,
38 _NET_WM_NAME,
39 _NET_WM_STATE,
40 _NET_WM_STATE_MAXIMIZED_VERT,
41 _NET_WM_STATE_MAXIMIZED_HORZ,
42 _NET_WM_STATE_FULLSCREEN,
43 _NET_WM_STATE_HIDDEN,
44 _NET_WM_STATE_FOCUSED,
45 _NET_ACTIVE_WINDOW,
46 _NET_WM_SYNC_REQUEST,
47 _NET_WM_SYNC_REQUEST_COUNTER,
48 _NET_WM_BYPASS_COMPOSITOR,
49 _NET_WM_MOVERESIZE,
50 _NET_WM_WINDOW_TYPE,
51 _NET_WM_WINDOW_TYPE_NOTIFICATION,
52 _NET_WM_SYNC,
53 _NET_SUPPORTED,
54 _MOTIF_WM_HINTS,
55 _GTK_SHOW_WINDOW_MENU,
56 _GTK_FRAME_EXTENTS,
57 _GTK_EDGE_CONSTRAINTS,
58 }
59}
60
61fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
62 let reply = xcb_connection
63 .get_geometry(x_window)
64 .unwrap()
65 .reply()
66 .unwrap();
67 gpu::Extent {
68 width: reply.width as u32,
69 height: reply.height as u32,
70 depth: 1,
71 }
72}
73
74impl ResizeEdge {
75 fn to_moveresize(&self) -> u32 {
76 match self {
77 ResizeEdge::TopLeft => 0,
78 ResizeEdge::Top => 1,
79 ResizeEdge::TopRight => 2,
80 ResizeEdge::Right => 3,
81 ResizeEdge::BottomRight => 4,
82 ResizeEdge::Bottom => 5,
83 ResizeEdge::BottomLeft => 6,
84 ResizeEdge::Left => 7,
85 }
86 }
87}
88
89#[derive(Debug)]
90struct EdgeConstraints {
91 top_tiled: bool,
92 #[allow(dead_code)]
93 top_resizable: bool,
94
95 right_tiled: bool,
96 #[allow(dead_code)]
97 right_resizable: bool,
98
99 bottom_tiled: bool,
100 #[allow(dead_code)]
101 bottom_resizable: bool,
102
103 left_tiled: bool,
104 #[allow(dead_code)]
105 left_resizable: bool,
106}
107
108impl EdgeConstraints {
109 fn from_atom(atom: u32) -> Self {
110 EdgeConstraints {
111 top_tiled: (atom & (1 << 0)) != 0,
112 top_resizable: (atom & (1 << 1)) != 0,
113 right_tiled: (atom & (1 << 2)) != 0,
114 right_resizable: (atom & (1 << 3)) != 0,
115 bottom_tiled: (atom & (1 << 4)) != 0,
116 bottom_resizable: (atom & (1 << 5)) != 0,
117 left_tiled: (atom & (1 << 6)) != 0,
118 left_resizable: (atom & (1 << 7)) != 0,
119 }
120 }
121
122 fn to_tiling(&self) -> Tiling {
123 Tiling {
124 top: self.top_tiled,
125 right: self.right_tiled,
126 bottom: self.bottom_tiled,
127 left: self.left_tiled,
128 }
129 }
130}
131
132#[derive(Debug)]
133struct Visual {
134 id: xproto::Visualid,
135 colormap: u32,
136 depth: u8,
137}
138
139struct VisualSet {
140 inherit: Visual,
141 opaque: Option<Visual>,
142 transparent: Option<Visual>,
143 root: u32,
144 black_pixel: u32,
145}
146
147fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet {
148 let screen = &xcb_connection.setup().roots[screen_index];
149 let mut set = VisualSet {
150 inherit: Visual {
151 id: screen.root_visual,
152 colormap: screen.default_colormap,
153 depth: screen.root_depth,
154 },
155 opaque: None,
156 transparent: None,
157 root: screen.root,
158 black_pixel: screen.black_pixel,
159 };
160
161 for depth_info in screen.allowed_depths.iter() {
162 for visual_type in depth_info.visuals.iter() {
163 let visual = Visual {
164 id: visual_type.visual_id,
165 colormap: 0,
166 depth: depth_info.depth,
167 };
168 log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
169 visual_type.visual_id,
170 visual_type.class,
171 depth_info.depth,
172 visual_type.bits_per_rgb_value,
173 visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
174 );
175
176 if (
177 visual_type.red_mask,
178 visual_type.green_mask,
179 visual_type.blue_mask,
180 ) != (0xFF0000, 0xFF00, 0xFF)
181 {
182 continue;
183 }
184 let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
185 let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
186
187 if alpha_mask == 0 {
188 if set.opaque.is_none() {
189 set.opaque = Some(visual);
190 }
191 } else {
192 if set.transparent.is_none() {
193 set.transparent = Some(visual);
194 }
195 }
196 }
197 }
198
199 set
200}
201
202struct RawWindow {
203 connection: *mut c_void,
204 screen_id: usize,
205 window_id: u32,
206 visual_id: u32,
207}
208
209#[derive(Default)]
210pub struct Callbacks {
211 request_frame: Option<Box<dyn FnMut()>>,
212 input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
213 active_status_change: Option<Box<dyn FnMut(bool)>>,
214 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
215 moved: Option<Box<dyn FnMut()>>,
216 should_close: Option<Box<dyn FnMut() -> bool>>,
217 close: Option<Box<dyn FnOnce()>>,
218 appearance_changed: Option<Box<dyn FnMut()>>,
219}
220
221pub struct X11WindowState {
222 pub destroyed: bool,
223 client: X11ClientStatePtr,
224 executor: ForegroundExecutor,
225 atoms: XcbAtoms,
226 x_root_window: xproto::Window,
227 pub(crate) counter_id: sync::Counter,
228 pub(crate) last_sync_counter: Option<sync::Int64>,
229 _raw: RawWindow,
230 bounds: Bounds<Pixels>,
231 scale_factor: f32,
232 renderer: BladeRenderer,
233 display: Rc<dyn PlatformDisplay>,
234 input_handler: Option<PlatformInputHandler>,
235 appearance: WindowAppearance,
236 background_appearance: WindowBackgroundAppearance,
237 maximized_vertical: bool,
238 maximized_horizontal: bool,
239 hidden: bool,
240 active: bool,
241 fullscreen: bool,
242 client_side_decorations_supported: bool,
243 decorations: WindowDecorations,
244 edge_constraints: Option<EdgeConstraints>,
245 pub handle: AnyWindowHandle,
246 last_insets: [u32; 4],
247}
248
249impl X11WindowState {
250 fn is_transparent(&self) -> bool {
251 self.background_appearance != WindowBackgroundAppearance::Opaque
252 }
253}
254
255#[derive(Clone)]
256pub(crate) struct X11WindowStatePtr {
257 pub state: Rc<RefCell<X11WindowState>>,
258 pub(crate) callbacks: Rc<RefCell<Callbacks>>,
259 xcb_connection: Rc<XCBConnection>,
260 x_window: xproto::Window,
261}
262
263impl rwh::HasWindowHandle for RawWindow {
264 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
265 let non_zero = NonZeroU32::new(self.window_id).unwrap();
266 let mut handle = rwh::XcbWindowHandle::new(non_zero);
267 handle.visual_id = NonZeroU32::new(self.visual_id);
268 Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
269 }
270}
271impl rwh::HasDisplayHandle for RawWindow {
272 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
273 let non_zero = NonNull::new(self.connection).unwrap();
274 let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
275 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
276 }
277}
278
279impl rwh::HasWindowHandle for X11Window {
280 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
281 unimplemented!()
282 }
283}
284impl rwh::HasDisplayHandle for X11Window {
285 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
286 unimplemented!()
287 }
288}
289
290impl X11WindowState {
291 #[allow(clippy::too_many_arguments)]
292 pub fn new(
293 handle: AnyWindowHandle,
294 client: X11ClientStatePtr,
295 executor: ForegroundExecutor,
296 params: WindowParams,
297 xcb_connection: &Rc<XCBConnection>,
298 client_side_decorations_supported: bool,
299 x_main_screen_index: usize,
300 x_window: xproto::Window,
301 atoms: &XcbAtoms,
302 scale_factor: f32,
303 appearance: WindowAppearance,
304 ) -> anyhow::Result<Self> {
305 let x_screen_index = params
306 .display_id
307 .map_or(x_main_screen_index, |did| did.0 as usize);
308
309 let visual_set = find_visuals(&xcb_connection, x_screen_index);
310
311 let visual = match visual_set.transparent {
312 Some(visual) => visual,
313 None => {
314 log::warn!("Unable to find a transparent visual",);
315 visual_set.inherit
316 }
317 };
318 log::info!("Using {:?}", visual);
319
320 let colormap = if visual.colormap != 0 {
321 visual.colormap
322 } else {
323 let id = xcb_connection.generate_id().unwrap();
324 log::info!("Creating colormap {}", id);
325 xcb_connection
326 .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
327 .unwrap()
328 .check()?;
329 id
330 };
331
332 let win_aux = xproto::CreateWindowAux::new()
333 // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
334 .border_pixel(visual_set.black_pixel)
335 .colormap(colormap)
336 .event_mask(
337 xproto::EventMask::EXPOSURE
338 | xproto::EventMask::STRUCTURE_NOTIFY
339 | xproto::EventMask::FOCUS_CHANGE
340 | xproto::EventMask::KEY_PRESS
341 | xproto::EventMask::KEY_RELEASE
342 | EventMask::PROPERTY_CHANGE,
343 );
344
345 let mut bounds = params.bounds.to_device_pixels(scale_factor);
346 if bounds.size.width.0 == 0 || bounds.size.height.0 == 0 {
347 log::warn!("Window bounds contain a zero value. height={}, width={}. Falling back to defaults.", bounds.size.height.0, bounds.size.width.0);
348 bounds.size.width = 800.into();
349 bounds.size.height = 600.into();
350 }
351
352 xcb_connection
353 .create_window(
354 visual.depth,
355 x_window,
356 visual_set.root,
357 (bounds.origin.x.0 + 2) as i16,
358 bounds.origin.y.0 as i16,
359 bounds.size.width.0 as u16,
360 bounds.size.height.0 as u16,
361 0,
362 xproto::WindowClass::INPUT_OUTPUT,
363 visual.id,
364 &win_aux,
365 )
366 .unwrap()
367 .check().with_context(|| {
368 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: {}",
369 visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0)
370 })?;
371
372 let reply = xcb_connection
373 .get_geometry(x_window)
374 .unwrap()
375 .reply()
376 .unwrap();
377 if reply.x == 0 && reply.y == 0 {
378 bounds.origin.x.0 += 2;
379 // Work around a bug where our rendered content appears
380 // outside the window bounds when opened at the default position
381 // (14px, 49px on X + Gnome + Ubuntu 22).
382 xcb_connection
383 .configure_window(
384 x_window,
385 &xproto::ConfigureWindowAux::new()
386 .x(bounds.origin.x.0)
387 .y(bounds.origin.y.0),
388 )
389 .unwrap();
390 }
391 if let Some(titlebar) = params.titlebar {
392 if let Some(title) = titlebar.title {
393 xcb_connection
394 .change_property8(
395 xproto::PropMode::REPLACE,
396 x_window,
397 xproto::AtomEnum::WM_NAME,
398 xproto::AtomEnum::STRING,
399 title.as_bytes(),
400 )
401 .unwrap();
402 }
403 }
404 if params.kind == WindowKind::PopUp {
405 xcb_connection
406 .change_property32(
407 xproto::PropMode::REPLACE,
408 x_window,
409 atoms._NET_WM_WINDOW_TYPE,
410 xproto::AtomEnum::ATOM,
411 &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
412 )
413 .unwrap();
414 }
415
416 xcb_connection
417 .change_property32(
418 xproto::PropMode::REPLACE,
419 x_window,
420 atoms.WM_PROTOCOLS,
421 xproto::AtomEnum::ATOM,
422 &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
423 )
424 .unwrap();
425
426 sync::initialize(xcb_connection, 3, 1).unwrap();
427 let sync_request_counter = xcb_connection.generate_id().unwrap();
428 sync::create_counter(
429 xcb_connection,
430 sync_request_counter,
431 sync::Int64 { lo: 0, hi: 0 },
432 )
433 .unwrap();
434
435 xcb_connection
436 .change_property32(
437 xproto::PropMode::REPLACE,
438 x_window,
439 atoms._NET_WM_SYNC_REQUEST_COUNTER,
440 xproto::AtomEnum::CARDINAL,
441 &[sync_request_counter],
442 )
443 .unwrap();
444
445 xcb_connection
446 .xinput_xi_select_events(
447 x_window,
448 &[xinput::EventMask {
449 deviceid: XINPUT_MASTER_DEVICE,
450 mask: vec![
451 xinput::XIEventMask::MOTION
452 | xinput::XIEventMask::BUTTON_PRESS
453 | xinput::XIEventMask::BUTTON_RELEASE
454 | xinput::XIEventMask::LEAVE,
455 ],
456 }],
457 )
458 .unwrap();
459
460 xcb_connection.flush().unwrap();
461
462 let raw = RawWindow {
463 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
464 xcb_connection,
465 ) as *mut _,
466 screen_id: x_screen_index,
467 window_id: x_window,
468 visual_id: visual.id,
469 };
470 let gpu = Arc::new(
471 unsafe {
472 gpu::Context::init_windowed(
473 &raw,
474 gpu::ContextDesc {
475 validation: false,
476 capture: false,
477 overlay: false,
478 },
479 )
480 }
481 .map_err(|e| anyhow::anyhow!("{:?}", e))?,
482 );
483
484 let config = BladeSurfaceConfig {
485 // Note: this has to be done after the GPU init, or otherwise
486 // the sizes are immediately invalidated.
487 size: query_render_extent(xcb_connection, x_window),
488 // We set it to transparent by default, even if we have client-side
489 // decorations, since those seem to work on X11 even without `true` here.
490 // If the window appearance changes, then the renderer will get updated
491 // too
492 transparent: false,
493 };
494 xcb_connection.map_window(x_window).unwrap();
495
496 Ok(Self {
497 client,
498 executor,
499 display: Rc::new(
500 X11Display::new(xcb_connection, scale_factor, x_screen_index).unwrap(),
501 ),
502 _raw: raw,
503 x_root_window: visual_set.root,
504 bounds: bounds.to_pixels(scale_factor),
505 scale_factor,
506 renderer: BladeRenderer::new(gpu, config),
507 atoms: *atoms,
508 input_handler: None,
509 active: false,
510 fullscreen: false,
511 maximized_vertical: false,
512 maximized_horizontal: false,
513 hidden: false,
514 appearance,
515 handle,
516 background_appearance: WindowBackgroundAppearance::Opaque,
517 destroyed: false,
518 client_side_decorations_supported,
519 decorations: WindowDecorations::Server,
520 last_insets: [0, 0, 0, 0],
521 edge_constraints: None,
522 counter_id: sync_request_counter,
523 last_sync_counter: None,
524 })
525 }
526
527 fn content_size(&self) -> Size<Pixels> {
528 let size = self.renderer.viewport_size();
529 Size {
530 width: size.width.into(),
531 height: size.height.into(),
532 }
533 }
534}
535
536pub(crate) struct X11Window(pub X11WindowStatePtr);
537
538impl Drop for X11Window {
539 fn drop(&mut self) {
540 let mut state = self.0.state.borrow_mut();
541 state.renderer.destroy();
542
543 let destroy_x_window = maybe!({
544 self.0.xcb_connection.unmap_window(self.0.x_window)?;
545 self.0.xcb_connection.destroy_window(self.0.x_window)?;
546 self.0.xcb_connection.flush()?;
547
548 anyhow::Ok(())
549 })
550 .context("unmapping and destroying X11 window")
551 .log_err();
552
553 if destroy_x_window.is_some() {
554 // Mark window as destroyed so that we can filter out when X11 events
555 // for it still come in.
556 state.destroyed = true;
557
558 let this_ptr = self.0.clone();
559 let client_ptr = state.client.clone();
560 state
561 .executor
562 .spawn(async move {
563 this_ptr.close();
564 client_ptr.drop_window(this_ptr.x_window);
565 })
566 .detach();
567 }
568
569 drop(state);
570 }
571}
572
573enum WmHintPropertyState {
574 // Remove = 0,
575 // Add = 1,
576 Toggle = 2,
577}
578
579impl X11Window {
580 #[allow(clippy::too_many_arguments)]
581 pub fn new(
582 handle: AnyWindowHandle,
583 client: X11ClientStatePtr,
584 executor: ForegroundExecutor,
585 params: WindowParams,
586 xcb_connection: &Rc<XCBConnection>,
587 client_side_decorations_supported: bool,
588 x_main_screen_index: usize,
589 x_window: xproto::Window,
590 atoms: &XcbAtoms,
591 scale_factor: f32,
592 appearance: WindowAppearance,
593 ) -> anyhow::Result<Self> {
594 let ptr = X11WindowStatePtr {
595 state: Rc::new(RefCell::new(X11WindowState::new(
596 handle,
597 client,
598 executor,
599 params,
600 xcb_connection,
601 client_side_decorations_supported,
602 x_main_screen_index,
603 x_window,
604 atoms,
605 scale_factor,
606 appearance,
607 )?)),
608 callbacks: Rc::new(RefCell::new(Callbacks::default())),
609 xcb_connection: xcb_connection.clone(),
610 x_window,
611 };
612
613 let state = ptr.state.borrow_mut();
614 ptr.set_wm_properties(state);
615
616 Ok(Self(ptr))
617 }
618
619 fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
620 let state = self.0.state.borrow();
621 let message = ClientMessageEvent::new(
622 32,
623 self.0.x_window,
624 state.atoms._NET_WM_STATE,
625 [wm_hint_property_state as u32, prop1, prop2, 1, 0],
626 );
627 self.0
628 .xcb_connection
629 .send_event(
630 false,
631 state.x_root_window,
632 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
633 message,
634 )
635 .unwrap()
636 .check()
637 .unwrap();
638 }
639
640 fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply {
641 let state = self.0.state.borrow();
642 self.0
643 .xcb_connection
644 .translate_coordinates(
645 self.0.x_window,
646 state.x_root_window,
647 (position.x.0 * state.scale_factor) as i16,
648 (position.y.0 * state.scale_factor) as i16,
649 )
650 .unwrap()
651 .reply()
652 .unwrap()
653 }
654
655 fn send_moveresize(&self, flag: u32) {
656 let state = self.0.state.borrow();
657
658 self.0
659 .xcb_connection
660 .ungrab_pointer(x11rb::CURRENT_TIME)
661 .unwrap()
662 .check()
663 .unwrap();
664
665 let pointer = self
666 .0
667 .xcb_connection
668 .query_pointer(self.0.x_window)
669 .unwrap()
670 .reply()
671 .unwrap();
672 let message = ClientMessageEvent::new(
673 32,
674 self.0.x_window,
675 state.atoms._NET_WM_MOVERESIZE,
676 [
677 pointer.root_x as u32,
678 pointer.root_y as u32,
679 flag,
680 0, // Left mouse button
681 0,
682 ],
683 );
684 self.0
685 .xcb_connection
686 .send_event(
687 false,
688 state.x_root_window,
689 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
690 message,
691 )
692 .unwrap();
693
694 self.0.xcb_connection.flush().unwrap();
695 }
696}
697
698impl X11WindowStatePtr {
699 pub fn should_close(&self) -> bool {
700 let mut cb = self.callbacks.borrow_mut();
701 if let Some(mut should_close) = cb.should_close.take() {
702 let result = (should_close)();
703 cb.should_close = Some(should_close);
704 result
705 } else {
706 true
707 }
708 }
709
710 pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) {
711 let mut state = self.state.borrow_mut();
712 if event.atom == state.atoms._NET_WM_STATE {
713 self.set_wm_properties(state);
714 } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
715 self.set_edge_constraints(state);
716 }
717 }
718
719 fn set_edge_constraints(&self, mut state: std::cell::RefMut<X11WindowState>) {
720 let reply = self
721 .xcb_connection
722 .get_property(
723 false,
724 self.x_window,
725 state.atoms._GTK_EDGE_CONSTRAINTS,
726 xproto::AtomEnum::CARDINAL,
727 0,
728 4,
729 )
730 .unwrap()
731 .reply()
732 .unwrap();
733
734 if reply.value_len != 0 {
735 let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap());
736 let edge_constraints = EdgeConstraints::from_atom(atom);
737 state.edge_constraints.replace(edge_constraints);
738 }
739 }
740
741 fn set_wm_properties(&self, mut state: std::cell::RefMut<X11WindowState>) {
742 let reply = self
743 .xcb_connection
744 .get_property(
745 false,
746 self.x_window,
747 state.atoms._NET_WM_STATE,
748 xproto::AtomEnum::ATOM,
749 0,
750 u32::MAX,
751 )
752 .unwrap()
753 .reply()
754 .unwrap();
755
756 let atoms = reply
757 .value
758 .chunks_exact(4)
759 .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
760
761 state.active = false;
762 state.fullscreen = false;
763 state.maximized_vertical = false;
764 state.maximized_horizontal = false;
765 state.hidden = true;
766
767 for atom in atoms {
768 if atom == state.atoms._NET_WM_STATE_FOCUSED {
769 state.active = true;
770 } else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
771 state.fullscreen = true;
772 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
773 state.maximized_vertical = true;
774 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
775 state.maximized_horizontal = true;
776 } else if atom == state.atoms._NET_WM_STATE_HIDDEN {
777 state.hidden = true;
778 }
779 }
780 }
781
782 pub fn close(&self) {
783 let mut callbacks = self.callbacks.borrow_mut();
784 if let Some(fun) = callbacks.close.take() {
785 fun()
786 }
787 }
788
789 pub fn refresh(&self) {
790 let mut cb = self.callbacks.borrow_mut();
791 if let Some(ref mut fun) = cb.request_frame {
792 fun();
793 }
794 }
795
796 pub fn handle_input(&self, input: PlatformInput) {
797 if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
798 if !fun(input.clone()).propagate {
799 return;
800 }
801 }
802 if let PlatformInput::KeyDown(event) = input {
803 let mut state = self.state.borrow_mut();
804 if let Some(mut input_handler) = state.input_handler.take() {
805 if let Some(ime_key) = &event.keystroke.ime_key {
806 drop(state);
807 input_handler.replace_text_in_range(None, ime_key);
808 state = self.state.borrow_mut();
809 }
810 state.input_handler = Some(input_handler);
811 }
812 }
813 }
814
815 pub fn handle_ime_commit(&self, text: String) {
816 let mut state = self.state.borrow_mut();
817 if let Some(mut input_handler) = state.input_handler.take() {
818 drop(state);
819 input_handler.replace_text_in_range(None, &text);
820 let mut state = self.state.borrow_mut();
821 state.input_handler = Some(input_handler);
822 }
823 }
824
825 pub fn handle_ime_preedit(&self, text: String) {
826 let mut state = self.state.borrow_mut();
827 if let Some(mut input_handler) = state.input_handler.take() {
828 drop(state);
829 input_handler.replace_and_mark_text_in_range(None, &text, None);
830 let mut state = self.state.borrow_mut();
831 state.input_handler = Some(input_handler);
832 }
833 }
834
835 pub fn handle_ime_unmark(&self) {
836 let mut state = self.state.borrow_mut();
837 if let Some(mut input_handler) = state.input_handler.take() {
838 drop(state);
839 input_handler.unmark_text();
840 let mut state = self.state.borrow_mut();
841 state.input_handler = Some(input_handler);
842 }
843 }
844
845 pub fn handle_ime_delete(&self) {
846 let mut state = self.state.borrow_mut();
847 if let Some(mut input_handler) = state.input_handler.take() {
848 drop(state);
849 if let Some(marked) = input_handler.marked_text_range() {
850 input_handler.replace_text_in_range(Some(marked), "");
851 }
852 let mut state = self.state.borrow_mut();
853 state.input_handler = Some(input_handler);
854 }
855 }
856
857 pub fn get_ime_area(&self) -> Option<Bounds<Pixels>> {
858 let mut state = self.state.borrow_mut();
859 let mut bounds: Option<Bounds<Pixels>> = None;
860 if let Some(mut input_handler) = state.input_handler.take() {
861 drop(state);
862 if let Some(range) = input_handler.selected_text_range() {
863 bounds = input_handler.bounds_for_range(range);
864 }
865 let mut state = self.state.borrow_mut();
866 state.input_handler = Some(input_handler);
867 };
868 bounds
869 }
870
871 pub fn configure(&self, bounds: Bounds<i32>) {
872 let mut resize_args = None;
873 let is_resize;
874 {
875 let mut state = self.state.borrow_mut();
876 let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
877
878 is_resize = bounds.size.width != state.bounds.size.width
879 || bounds.size.height != state.bounds.size.height;
880
881 // If it's a resize event (only width/height changed), we ignore `bounds.origin`
882 // because it contains wrong values.
883 if is_resize {
884 state.bounds.size = bounds.size;
885 } else {
886 state.bounds = bounds;
887 }
888
889 let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
890 if true {
891 state.renderer.update_drawable_size(size(
892 DevicePixels(gpu_size.width as i32),
893 DevicePixels(gpu_size.height as i32),
894 ));
895 resize_args = Some((state.content_size(), state.scale_factor));
896 }
897 if let Some(value) = state.last_sync_counter.take() {
898 sync::set_counter(&self.xcb_connection, state.counter_id, value).unwrap();
899 }
900 }
901
902 let mut callbacks = self.callbacks.borrow_mut();
903 if let Some((content_size, scale_factor)) = resize_args {
904 if let Some(ref mut fun) = callbacks.resize {
905 fun(content_size, scale_factor)
906 }
907 }
908 if !is_resize {
909 if let Some(ref mut fun) = callbacks.moved {
910 fun()
911 }
912 }
913 }
914
915 pub fn set_focused(&self, focus: bool) {
916 if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
917 fun(focus);
918 }
919 }
920
921 pub fn set_appearance(&mut self, appearance: WindowAppearance) {
922 let mut state = self.state.borrow_mut();
923 state.appearance = appearance;
924 let is_transparent = state.is_transparent();
925 state.renderer.update_transparency(is_transparent);
926 state.appearance = appearance;
927 drop(state);
928 let mut callbacks = self.callbacks.borrow_mut();
929 if let Some(ref mut fun) = callbacks.appearance_changed {
930 (fun)()
931 }
932 }
933}
934
935impl PlatformWindow for X11Window {
936 fn bounds(&self) -> Bounds<Pixels> {
937 self.0.state.borrow().bounds
938 }
939
940 fn is_maximized(&self) -> bool {
941 let state = self.0.state.borrow();
942
943 // A maximized window that gets minimized will still retain its maximized state.
944 !state.hidden && state.maximized_vertical && state.maximized_horizontal
945 }
946
947 fn window_bounds(&self) -> WindowBounds {
948 let state = self.0.state.borrow();
949 if self.is_maximized() {
950 WindowBounds::Maximized(state.bounds)
951 } else {
952 WindowBounds::Windowed(state.bounds)
953 }
954 }
955
956 fn content_size(&self) -> Size<Pixels> {
957 // We divide by the scale factor here because this value is queried to determine how much to draw,
958 // but it will be multiplied later by the scale to adjust for scaling.
959 let state = self.0.state.borrow();
960 state
961 .content_size()
962 .map(|size| size.div(state.scale_factor))
963 }
964
965 fn scale_factor(&self) -> f32 {
966 self.0.state.borrow().scale_factor
967 }
968
969 fn appearance(&self) -> WindowAppearance {
970 self.0.state.borrow().appearance
971 }
972
973 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
974 Some(self.0.state.borrow().display.clone())
975 }
976
977 fn mouse_position(&self) -> Point<Pixels> {
978 let reply = self
979 .0
980 .xcb_connection
981 .query_pointer(self.0.x_window)
982 .unwrap()
983 .reply()
984 .unwrap();
985 Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
986 }
987
988 fn modifiers(&self) -> Modifiers {
989 self.0
990 .state
991 .borrow()
992 .client
993 .0
994 .upgrade()
995 .map(|ref_cell| ref_cell.borrow().modifiers)
996 .unwrap_or_default()
997 }
998
999 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1000 self.0.state.borrow_mut().input_handler = Some(input_handler);
1001 }
1002
1003 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1004 self.0.state.borrow_mut().input_handler.take()
1005 }
1006
1007 fn prompt(
1008 &self,
1009 _level: PromptLevel,
1010 _msg: &str,
1011 _detail: Option<&str>,
1012 _answers: &[&str],
1013 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
1014 None
1015 }
1016
1017 fn activate(&self) {
1018 let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
1019 let message = xproto::ClientMessageEvent::new(
1020 32,
1021 self.0.x_window,
1022 self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
1023 data,
1024 );
1025 self.0
1026 .xcb_connection
1027 .send_event(
1028 false,
1029 self.0.state.borrow().x_root_window,
1030 xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1031 message,
1032 )
1033 .log_err();
1034 self.0
1035 .xcb_connection
1036 .set_input_focus(
1037 xproto::InputFocus::POINTER_ROOT,
1038 self.0.x_window,
1039 xproto::Time::CURRENT_TIME,
1040 )
1041 .log_err();
1042 self.0.xcb_connection.flush().unwrap();
1043 }
1044
1045 fn is_active(&self) -> bool {
1046 self.0.state.borrow().active
1047 }
1048
1049 fn set_title(&mut self, title: &str) {
1050 self.0
1051 .xcb_connection
1052 .change_property8(
1053 xproto::PropMode::REPLACE,
1054 self.0.x_window,
1055 xproto::AtomEnum::WM_NAME,
1056 xproto::AtomEnum::STRING,
1057 title.as_bytes(),
1058 )
1059 .unwrap();
1060
1061 self.0
1062 .xcb_connection
1063 .change_property8(
1064 xproto::PropMode::REPLACE,
1065 self.0.x_window,
1066 self.0.state.borrow().atoms._NET_WM_NAME,
1067 self.0.state.borrow().atoms.UTF8_STRING,
1068 title.as_bytes(),
1069 )
1070 .unwrap();
1071 self.0.xcb_connection.flush().unwrap();
1072 }
1073
1074 fn set_app_id(&mut self, app_id: &str) {
1075 let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
1076 data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
1077 data.push(b'\0');
1078 data.extend(app_id.bytes()); // class
1079
1080 self.0
1081 .xcb_connection
1082 .change_property8(
1083 xproto::PropMode::REPLACE,
1084 self.0.x_window,
1085 xproto::AtomEnum::WM_CLASS,
1086 xproto::AtomEnum::STRING,
1087 &data,
1088 )
1089 .unwrap()
1090 .check()
1091 .unwrap();
1092 }
1093
1094 fn set_edited(&mut self, _edited: bool) {
1095 log::info!("ignoring macOS specific set_edited");
1096 }
1097
1098 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1099 let mut state = self.0.state.borrow_mut();
1100 state.background_appearance = background_appearance;
1101 let transparent = state.is_transparent();
1102 state.renderer.update_transparency(transparent);
1103 }
1104
1105 fn show_character_palette(&self) {
1106 log::info!("ignoring macOS specific show_character_palette");
1107 }
1108
1109 fn minimize(&self) {
1110 let state = self.0.state.borrow();
1111 const WINDOW_ICONIC_STATE: u32 = 3;
1112 let message = ClientMessageEvent::new(
1113 32,
1114 self.0.x_window,
1115 state.atoms.WM_CHANGE_STATE,
1116 [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
1117 );
1118 self.0
1119 .xcb_connection
1120 .send_event(
1121 false,
1122 state.x_root_window,
1123 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1124 message,
1125 )
1126 .unwrap()
1127 .check()
1128 .unwrap();
1129 }
1130
1131 fn zoom(&self) {
1132 let state = self.0.state.borrow();
1133 self.set_wm_hints(
1134 WmHintPropertyState::Toggle,
1135 state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
1136 state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
1137 );
1138 }
1139
1140 fn toggle_fullscreen(&self) {
1141 let state = self.0.state.borrow();
1142 self.set_wm_hints(
1143 WmHintPropertyState::Toggle,
1144 state.atoms._NET_WM_STATE_FULLSCREEN,
1145 xproto::AtomEnum::NONE.into(),
1146 );
1147 }
1148
1149 fn is_fullscreen(&self) -> bool {
1150 self.0.state.borrow().fullscreen
1151 }
1152
1153 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
1154 self.0.callbacks.borrow_mut().request_frame = Some(callback);
1155 }
1156
1157 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
1158 self.0.callbacks.borrow_mut().input = Some(callback);
1159 }
1160
1161 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1162 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
1163 }
1164
1165 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1166 self.0.callbacks.borrow_mut().resize = Some(callback);
1167 }
1168
1169 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1170 self.0.callbacks.borrow_mut().moved = Some(callback);
1171 }
1172
1173 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1174 self.0.callbacks.borrow_mut().should_close = Some(callback);
1175 }
1176
1177 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1178 self.0.callbacks.borrow_mut().close = Some(callback);
1179 }
1180
1181 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1182 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1183 }
1184
1185 fn draw(&self, scene: &Scene) {
1186 let mut inner = self.0.state.borrow_mut();
1187 inner.renderer.draw(scene);
1188 }
1189
1190 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1191 let inner = self.0.state.borrow();
1192 inner.renderer.sprite_atlas().clone()
1193 }
1194
1195 fn show_window_menu(&self, position: Point<Pixels>) {
1196 let state = self.0.state.borrow();
1197 let coords = self.get_root_position(position);
1198 let message = ClientMessageEvent::new(
1199 32,
1200 self.0.x_window,
1201 state.atoms._GTK_SHOW_WINDOW_MENU,
1202 [
1203 XINPUT_MASTER_DEVICE as u32,
1204 coords.dst_x as u32,
1205 coords.dst_y as u32,
1206 0,
1207 0,
1208 ],
1209 );
1210 self.0
1211 .xcb_connection
1212 .send_event(
1213 false,
1214 state.x_root_window,
1215 EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
1216 message,
1217 )
1218 .unwrap()
1219 .check()
1220 .unwrap();
1221 }
1222
1223 fn start_window_move(&self) {
1224 const MOVERESIZE_MOVE: u32 = 8;
1225 self.send_moveresize(MOVERESIZE_MOVE);
1226 }
1227
1228 fn start_window_resize(&self, edge: ResizeEdge) {
1229 self.send_moveresize(edge.to_moveresize());
1230 }
1231
1232 fn window_decorations(&self) -> crate::Decorations {
1233 let state = self.0.state.borrow();
1234
1235 // Client window decorations require compositor support
1236 if !state.client_side_decorations_supported {
1237 return Decorations::Server;
1238 }
1239
1240 match state.decorations {
1241 WindowDecorations::Server => Decorations::Server,
1242 WindowDecorations::Client => {
1243 let tiling = if state.fullscreen {
1244 Tiling::tiled()
1245 } else if let Some(edge_constraints) = &state.edge_constraints {
1246 edge_constraints.to_tiling()
1247 } else {
1248 // https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
1249 Tiling {
1250 top: state.maximized_vertical,
1251 bottom: state.maximized_vertical,
1252 left: state.maximized_horizontal,
1253 right: state.maximized_horizontal,
1254 }
1255 };
1256 Decorations::Client { tiling }
1257 }
1258 }
1259 }
1260
1261 fn set_client_inset(&self, inset: Pixels) {
1262 let mut state = self.0.state.borrow_mut();
1263
1264 let dp = (inset.0 * state.scale_factor) as u32;
1265
1266 let insets = if state.fullscreen {
1267 [0, 0, 0, 0]
1268 } else if let Some(edge_constraints) = &state.edge_constraints {
1269 let left = if edge_constraints.left_tiled { 0 } else { dp };
1270 let top = if edge_constraints.top_tiled { 0 } else { dp };
1271 let right = if edge_constraints.right_tiled { 0 } else { dp };
1272 let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
1273
1274 [left, right, top, bottom]
1275 } else {
1276 let (left, right) = if state.maximized_horizontal {
1277 (0, 0)
1278 } else {
1279 (dp, dp)
1280 };
1281 let (top, bottom) = if state.maximized_vertical {
1282 (0, 0)
1283 } else {
1284 (dp, dp)
1285 };
1286 [left, right, top, bottom]
1287 };
1288
1289 if state.last_insets != insets {
1290 state.last_insets = insets;
1291
1292 self.0
1293 .xcb_connection
1294 .change_property(
1295 xproto::PropMode::REPLACE,
1296 self.0.x_window,
1297 state.atoms._GTK_FRAME_EXTENTS,
1298 xproto::AtomEnum::CARDINAL,
1299 size_of::<u32>() as u8 * 8,
1300 4,
1301 bytemuck::cast_slice::<u32, u8>(&insets),
1302 )
1303 .unwrap()
1304 .check()
1305 .unwrap();
1306 }
1307 }
1308
1309 fn request_decorations(&self, mut decorations: crate::WindowDecorations) {
1310 let mut state = self.0.state.borrow_mut();
1311
1312 if matches!(decorations, crate::WindowDecorations::Client)
1313 && !state.client_side_decorations_supported
1314 {
1315 log::info!(
1316 "x11: no compositor present, falling back to server-side window decorations"
1317 );
1318 decorations = crate::WindowDecorations::Server;
1319 }
1320
1321 // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
1322 let hints_data: [u32; 5] = match decorations {
1323 WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
1324 WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
1325 };
1326
1327 self.0
1328 .xcb_connection
1329 .change_property(
1330 xproto::PropMode::REPLACE,
1331 self.0.x_window,
1332 state.atoms._MOTIF_WM_HINTS,
1333 state.atoms._MOTIF_WM_HINTS,
1334 std::mem::size_of::<u32>() as u8 * 8,
1335 5,
1336 bytemuck::cast_slice::<u32, u8>(&hints_data),
1337 )
1338 .unwrap()
1339 .check()
1340 .unwrap();
1341
1342 match decorations {
1343 WindowDecorations::Server => {
1344 state.decorations = WindowDecorations::Server;
1345 let is_transparent = state.is_transparent();
1346 state.renderer.update_transparency(is_transparent);
1347 }
1348 WindowDecorations::Client => {
1349 state.decorations = WindowDecorations::Client;
1350 let is_transparent = state.is_transparent();
1351 state.renderer.update_transparency(is_transparent);
1352 }
1353 }
1354
1355 drop(state);
1356 let mut callbacks = self.0.callbacks.borrow_mut();
1357 if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
1358 appearance_changed();
1359 }
1360 }
1361}