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