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