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::{self, ConnectionExt as _},
20 xinput::{self, ConnectionExt as _},
21 xproto::{self, ConnectionExt as _, CreateWindowAux},
22 },
23 resource_manager::Database,
24 wrapper::ConnectionExt as _,
25 xcb_ffi::XCBConnection,
26};
27
28use std::{
29 cell::{Ref, RefCell, RefMut},
30 collections::HashMap,
31 ffi::c_void,
32 iter::Zip,
33 mem,
34 num::NonZeroU32,
35 ops::Div,
36 ptr::NonNull,
37 rc::Rc,
38 sync::{self, Arc},
39};
40
41use super::X11Display;
42
43x11rb::atom_manager! {
44 pub XcbAtoms: AtomsCookie {
45 UTF8_STRING,
46 WM_PROTOCOLS,
47 WM_DELETE_WINDOW,
48 _NET_WM_NAME,
49 _NET_WM_STATE,
50 _NET_WM_STATE_MAXIMIZED_VERT,
51 _NET_WM_STATE_MAXIMIZED_HORZ,
52 }
53}
54
55fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent {
56 let reply = xcb_connection
57 .get_geometry(x_window)
58 .unwrap()
59 .reply()
60 .unwrap();
61 gpu::Extent {
62 width: reply.width as u32,
63 height: reply.height as u32,
64 depth: 1,
65 }
66}
67
68#[derive(Debug)]
69struct Visual {
70 id: xproto::Visualid,
71 colormap: u32,
72 depth: u8,
73}
74
75struct VisualSet {
76 inherit: Visual,
77 opaque: Option<Visual>,
78 transparent: Option<Visual>,
79 root: u32,
80 black_pixel: u32,
81}
82
83fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet {
84 let screen = &xcb_connection.setup().roots[screen_index];
85 let mut set = VisualSet {
86 inherit: Visual {
87 id: screen.root_visual,
88 colormap: screen.default_colormap,
89 depth: screen.root_depth,
90 },
91 opaque: None,
92 transparent: None,
93 root: screen.root,
94 black_pixel: screen.black_pixel,
95 };
96
97 for depth_info in screen.allowed_depths.iter() {
98 for visual_type in depth_info.visuals.iter() {
99 let visual = Visual {
100 id: visual_type.visual_id,
101 colormap: 0,
102 depth: depth_info.depth,
103 };
104 log::debug!("Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
105 visual_type.visual_id,
106 visual_type.class,
107 depth_info.depth,
108 visual_type.bits_per_rgb_value,
109 visual_type.red_mask, visual_type.green_mask, visual_type.blue_mask,
110 );
111
112 if (
113 visual_type.red_mask,
114 visual_type.green_mask,
115 visual_type.blue_mask,
116 ) != (0xFF0000, 0xFF00, 0xFF)
117 {
118 continue;
119 }
120 let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
121 let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
122
123 if alpha_mask == 0 {
124 if set.opaque.is_none() {
125 set.opaque = Some(visual);
126 }
127 } else {
128 if set.transparent.is_none() {
129 set.transparent = Some(visual);
130 }
131 }
132 }
133 }
134
135 set
136}
137
138struct RawWindow {
139 connection: *mut c_void,
140 screen_id: usize,
141 window_id: u32,
142 visual_id: u32,
143}
144
145#[derive(Default)]
146pub struct Callbacks {
147 request_frame: Option<Box<dyn FnMut()>>,
148 input: Option<Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>>,
149 active_status_change: Option<Box<dyn FnMut(bool)>>,
150 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
151 moved: Option<Box<dyn FnMut()>>,
152 should_close: Option<Box<dyn FnMut() -> bool>>,
153 close: Option<Box<dyn FnOnce()>>,
154 appearance_changed: Option<Box<dyn FnMut()>>,
155}
156
157pub(crate) struct X11WindowState {
158 client: X11ClientStatePtr,
159 executor: ForegroundExecutor,
160 atoms: XcbAtoms,
161 raw: RawWindow,
162 bounds: Bounds<i32>,
163 scale_factor: f32,
164 renderer: BladeRenderer,
165 display: Rc<dyn PlatformDisplay>,
166 input_handler: Option<PlatformInputHandler>,
167}
168
169#[derive(Clone)]
170pub(crate) struct X11WindowStatePtr {
171 pub(crate) state: Rc<RefCell<X11WindowState>>,
172 pub(crate) callbacks: Rc<RefCell<Callbacks>>,
173 xcb_connection: Rc<XCBConnection>,
174 x_window: xproto::Window,
175}
176
177// todo(linux): Remove other RawWindowHandle implementation
178impl rwh::HasWindowHandle for RawWindow {
179 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
180 let non_zero = NonZeroU32::new(self.window_id).unwrap();
181 let mut handle = rwh::XcbWindowHandle::new(non_zero);
182 handle.visual_id = NonZeroU32::new(self.visual_id);
183 Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
184 }
185}
186impl rwh::HasDisplayHandle for RawWindow {
187 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
188 let non_zero = NonNull::new(self.connection).unwrap();
189 let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
190 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
191 }
192}
193
194impl rwh::HasWindowHandle for X11Window {
195 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
196 unimplemented!()
197 }
198}
199impl rwh::HasDisplayHandle for X11Window {
200 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
201 unimplemented!()
202 }
203}
204
205impl X11WindowState {
206 #[allow(clippy::too_many_arguments)]
207 pub fn new(
208 client: X11ClientStatePtr,
209 executor: ForegroundExecutor,
210 params: WindowParams,
211 xcb_connection: &Rc<XCBConnection>,
212 x_main_screen_index: usize,
213 x_window: xproto::Window,
214 atoms: &XcbAtoms,
215 scale_factor: f32,
216 ) -> Self {
217 let x_screen_index = params
218 .display_id
219 .map_or(x_main_screen_index, |did| did.0 as usize);
220
221 let visual_set = find_visuals(&xcb_connection, x_screen_index);
222 let visual_maybe = match params.window_background {
223 WindowBackgroundAppearance::Opaque => visual_set.opaque,
224 WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
225 visual_set.transparent
226 }
227 };
228 let visual = match visual_maybe {
229 Some(visual) => visual,
230 None => {
231 log::warn!(
232 "Unable to find a matching visual for {:?}",
233 params.window_background
234 );
235 visual_set.inherit
236 }
237 };
238 log::info!("Using {:?}", visual);
239
240 let colormap = if visual.colormap != 0 {
241 visual.colormap
242 } else {
243 let id = xcb_connection.generate_id().unwrap();
244 log::info!("Creating colormap {}", id);
245 xcb_connection
246 .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
247 .unwrap()
248 .check()
249 .unwrap();
250 id
251 };
252
253 let win_aux = xproto::CreateWindowAux::new()
254 .background_pixel(x11rb::NONE)
255 // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
256 .border_pixel(visual_set.black_pixel)
257 .colormap(colormap)
258 .event_mask(
259 xproto::EventMask::EXPOSURE
260 | xproto::EventMask::STRUCTURE_NOTIFY
261 | xproto::EventMask::ENTER_WINDOW
262 | xproto::EventMask::LEAVE_WINDOW
263 | xproto::EventMask::FOCUS_CHANGE
264 | xproto::EventMask::KEY_PRESS
265 | xproto::EventMask::KEY_RELEASE,
266 );
267
268 xcb_connection
269 .create_window(
270 visual.depth,
271 x_window,
272 visual_set.root,
273 params.bounds.origin.x.0 as i16,
274 params.bounds.origin.y.0 as i16,
275 params.bounds.size.width.0 as u16,
276 params.bounds.size.height.0 as u16,
277 0,
278 xproto::WindowClass::INPUT_OUTPUT,
279 visual.id,
280 &win_aux,
281 )
282 .unwrap()
283 .check()
284 .unwrap();
285
286 if let Some(titlebar) = params.titlebar {
287 if let Some(title) = titlebar.title {
288 xcb_connection
289 .change_property8(
290 xproto::PropMode::REPLACE,
291 x_window,
292 xproto::AtomEnum::WM_NAME,
293 xproto::AtomEnum::STRING,
294 title.as_bytes(),
295 )
296 .unwrap();
297 }
298 }
299
300 xcb_connection
301 .change_property32(
302 xproto::PropMode::REPLACE,
303 x_window,
304 atoms.WM_PROTOCOLS,
305 xproto::AtomEnum::ATOM,
306 &[atoms.WM_DELETE_WINDOW],
307 )
308 .unwrap();
309
310 xcb_connection
311 .xinput_xi_select_events(
312 x_window,
313 &[xinput::EventMask {
314 deviceid: 1,
315 mask: vec![
316 xinput::XIEventMask::MOTION
317 | xinput::XIEventMask::BUTTON_PRESS
318 | xinput::XIEventMask::BUTTON_RELEASE
319 | xinput::XIEventMask::LEAVE,
320 ],
321 }],
322 )
323 .unwrap();
324
325 xcb_connection.map_window(x_window).unwrap();
326 xcb_connection.flush().unwrap();
327
328 let raw = RawWindow {
329 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
330 xcb_connection,
331 ) as *mut _,
332 screen_id: x_screen_index,
333 window_id: x_window,
334 visual_id: visual.id,
335 };
336 let gpu = Arc::new(
337 unsafe {
338 gpu::Context::init_windowed(
339 &raw,
340 gpu::ContextDesc {
341 validation: false,
342 capture: false,
343 overlay: false,
344 },
345 )
346 }
347 .unwrap(),
348 );
349
350 let config = BladeSurfaceConfig {
351 // Note: this has to be done after the GPU init, or otherwise
352 // the sizes are immediately invalidated.
353 size: query_render_extent(xcb_connection, x_window),
354 transparent: params.window_background != WindowBackgroundAppearance::Opaque,
355 };
356
357 Self {
358 client,
359 executor,
360 display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
361 raw,
362 bounds: params.bounds.map(|v| v.0),
363 scale_factor,
364 renderer: BladeRenderer::new(gpu, config),
365 atoms: *atoms,
366 input_handler: None,
367 }
368 }
369
370 fn content_size(&self) -> Size<Pixels> {
371 let size = self.renderer.viewport_size();
372 Size {
373 width: size.width.into(),
374 height: size.height.into(),
375 }
376 }
377}
378
379pub(crate) struct X11Window(pub X11WindowStatePtr);
380
381impl Drop for X11Window {
382 fn drop(&mut self) {
383 let mut state = self.0.state.borrow_mut();
384 state.renderer.destroy();
385
386 self.0.xcb_connection.unmap_window(self.0.x_window).unwrap();
387 self.0
388 .xcb_connection
389 .destroy_window(self.0.x_window)
390 .unwrap();
391 self.0.xcb_connection.flush().unwrap();
392
393 let this_ptr = self.0.clone();
394 let client_ptr = state.client.clone();
395 state
396 .executor
397 .spawn(async move {
398 this_ptr.close();
399 client_ptr.drop_window(this_ptr.x_window);
400 })
401 .detach();
402 drop(state);
403 }
404}
405
406impl X11Window {
407 #[allow(clippy::too_many_arguments)]
408 pub fn new(
409 client: X11ClientStatePtr,
410 executor: ForegroundExecutor,
411 params: WindowParams,
412 xcb_connection: &Rc<XCBConnection>,
413 x_main_screen_index: usize,
414 x_window: xproto::Window,
415 atoms: &XcbAtoms,
416 scale_factor: f32,
417 ) -> Self {
418 Self(X11WindowStatePtr {
419 state: Rc::new(RefCell::new(X11WindowState::new(
420 client,
421 executor,
422 params,
423 xcb_connection,
424 x_main_screen_index,
425 x_window,
426 atoms,
427 scale_factor,
428 ))),
429 callbacks: Rc::new(RefCell::new(Callbacks::default())),
430 xcb_connection: xcb_connection.clone(),
431 x_window,
432 })
433 }
434}
435
436impl X11WindowStatePtr {
437 pub fn should_close(&self) -> bool {
438 let mut cb = self.callbacks.borrow_mut();
439 if let Some(mut should_close) = cb.should_close.take() {
440 let result = (should_close)();
441 cb.should_close = Some(should_close);
442 result
443 } else {
444 true
445 }
446 }
447
448 pub fn close(&self) {
449 let mut callbacks = self.callbacks.borrow_mut();
450 if let Some(fun) = callbacks.close.take() {
451 fun()
452 }
453 }
454
455 pub fn refresh(&self) {
456 let mut cb = self.callbacks.borrow_mut();
457 if let Some(ref mut fun) = cb.request_frame {
458 fun();
459 }
460 }
461
462 pub fn handle_input(&self, input: PlatformInput) {
463 if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
464 if !fun(input.clone()).propagate {
465 return;
466 }
467 }
468 if let PlatformInput::KeyDown(event) = input {
469 let mut state = self.state.borrow_mut();
470 if let Some(mut input_handler) = state.input_handler.take() {
471 if let Some(ime_key) = &event.keystroke.ime_key {
472 drop(state);
473 input_handler.replace_text_in_range(None, ime_key);
474 state = self.state.borrow_mut();
475 }
476 state.input_handler = Some(input_handler);
477 }
478 }
479 }
480
481 pub fn configure(&self, bounds: Bounds<i32>) {
482 let mut resize_args = None;
483 let do_move;
484 {
485 let mut state = self.state.borrow_mut();
486 let old_bounds = mem::replace(&mut state.bounds, bounds);
487 do_move = old_bounds.origin != bounds.origin;
488 // todo(linux): use normal GPUI types here, refactor out the double
489 // viewport check and extra casts ( )
490 let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
491 if state.renderer.viewport_size() != gpu_size {
492 state
493 .renderer
494 .update_drawable_size(size(gpu_size.width as f64, gpu_size.height as f64));
495 resize_args = Some((state.content_size(), state.scale_factor));
496 }
497 }
498
499 let mut callbacks = self.callbacks.borrow_mut();
500 if let Some((content_size, scale_factor)) = resize_args {
501 if let Some(ref mut fun) = callbacks.resize {
502 fun(content_size, scale_factor)
503 }
504 }
505 if do_move {
506 if let Some(ref mut fun) = callbacks.moved {
507 fun()
508 }
509 }
510 }
511
512 pub fn set_focused(&self, focus: bool) {
513 if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
514 fun(focus);
515 }
516 }
517}
518
519impl PlatformWindow for X11Window {
520 fn bounds(&self) -> Bounds<DevicePixels> {
521 self.0.state.borrow().bounds.map(|v| v.into())
522 }
523
524 // todo(linux)
525 fn is_maximized(&self) -> bool {
526 false
527 }
528
529 // todo(linux)
530 fn window_bounds(&self) -> WindowBounds {
531 let state = self.0.state.borrow();
532 WindowBounds::Windowed(state.bounds.map(|p| DevicePixels(p)))
533 }
534
535 fn content_size(&self) -> Size<Pixels> {
536 // We divide by the scale factor here because this value is queried to determine how much to draw,
537 // but it will be multiplied later by the scale to adjust for scaling.
538 let state = self.0.state.borrow();
539 state
540 .content_size()
541 .map(|size| size.div(state.scale_factor))
542 }
543
544 fn scale_factor(&self) -> f32 {
545 self.0.state.borrow().scale_factor
546 }
547
548 // todo(linux)
549 fn appearance(&self) -> WindowAppearance {
550 WindowAppearance::Light
551 }
552
553 fn display(&self) -> Rc<dyn PlatformDisplay> {
554 self.0.state.borrow().display.clone()
555 }
556
557 fn mouse_position(&self) -> Point<Pixels> {
558 let reply = self
559 .0
560 .xcb_connection
561 .query_pointer(self.0.x_window)
562 .unwrap()
563 .reply()
564 .unwrap();
565 Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
566 }
567
568 // todo(linux)
569 fn modifiers(&self) -> Modifiers {
570 Modifiers::default()
571 }
572
573 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
574 self.0.state.borrow_mut().input_handler = Some(input_handler);
575 }
576
577 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
578 self.0.state.borrow_mut().input_handler.take()
579 }
580
581 fn prompt(
582 &self,
583 _level: PromptLevel,
584 _msg: &str,
585 _detail: Option<&str>,
586 _answers: &[&str],
587 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
588 None
589 }
590
591 fn activate(&self) {
592 let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
593 self.0
594 .xcb_connection
595 .configure_window(self.0.x_window, &win_aux)
596 .log_err();
597 }
598
599 // todo(linux)
600 fn is_active(&self) -> bool {
601 false
602 }
603
604 fn set_title(&mut self, title: &str) {
605 self.0
606 .xcb_connection
607 .change_property8(
608 xproto::PropMode::REPLACE,
609 self.0.x_window,
610 xproto::AtomEnum::WM_NAME,
611 xproto::AtomEnum::STRING,
612 title.as_bytes(),
613 )
614 .unwrap();
615
616 self.0
617 .xcb_connection
618 .change_property8(
619 xproto::PropMode::REPLACE,
620 self.0.x_window,
621 self.0.state.borrow().atoms._NET_WM_NAME,
622 self.0.state.borrow().atoms.UTF8_STRING,
623 title.as_bytes(),
624 )
625 .unwrap();
626 }
627
628 fn set_app_id(&mut self, app_id: &str) {
629 let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
630 data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
631 data.push(b'\0');
632 data.extend(app_id.bytes()); // class
633
634 self.0.xcb_connection.change_property8(
635 xproto::PropMode::REPLACE,
636 self.0.x_window,
637 xproto::AtomEnum::WM_CLASS,
638 xproto::AtomEnum::STRING,
639 &data,
640 );
641 }
642
643 // todo(linux)
644 fn set_edited(&mut self, edited: bool) {}
645
646 fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
647 let mut inner = self.0.state.borrow_mut();
648 let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
649 inner.renderer.update_transparency(transparent);
650 }
651
652 // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
653 // but it looks like the equivalent for Linux is GTK specific:
654 //
655 // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
656 //
657 // This API might need to change, or we might need to build an emoji picker into GPUI
658 fn show_character_palette(&self) {
659 unimplemented!()
660 }
661
662 // todo(linux)
663 fn minimize(&self) {
664 unimplemented!()
665 }
666
667 // todo(linux)
668 fn zoom(&self) {
669 unimplemented!()
670 }
671
672 // todo(linux)
673 fn toggle_fullscreen(&self) {
674 unimplemented!()
675 }
676
677 // todo(linux)
678 fn is_fullscreen(&self) -> bool {
679 false
680 }
681
682 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
683 self.0.callbacks.borrow_mut().request_frame = Some(callback);
684 }
685
686 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
687 self.0.callbacks.borrow_mut().input = Some(callback);
688 }
689
690 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
691 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
692 }
693
694 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
695 self.0.callbacks.borrow_mut().resize = Some(callback);
696 }
697
698 fn on_moved(&self, callback: Box<dyn FnMut()>) {
699 self.0.callbacks.borrow_mut().moved = Some(callback);
700 }
701
702 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
703 self.0.callbacks.borrow_mut().should_close = Some(callback);
704 }
705
706 fn on_close(&self, callback: Box<dyn FnOnce()>) {
707 self.0.callbacks.borrow_mut().close = Some(callback);
708 }
709
710 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
711 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
712 }
713
714 fn draw(&self, scene: &Scene) {
715 let mut inner = self.0.state.borrow_mut();
716 inner.renderer.draw(scene);
717 }
718
719 fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
720 let inner = self.0.state.borrow();
721 inner.renderer.sprite_atlas().clone()
722 }
723
724 // todo(linux)
725 fn show_window_menu(&self, _position: Point<Pixels>) {}
726
727 // todo(linux)
728 fn start_system_move(&self) {}
729
730 fn should_render_window_controls(&self) -> bool {
731 false
732 }
733}