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