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