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 set_input_handler(&mut self, input_handler: PlatformInputHandler) {
464 self.0.state.borrow_mut().input_handler = Some(input_handler);
465 }
466
467 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
468 self.0.state.borrow_mut().input_handler.take()
469 }
470
471 fn prompt(
472 &self,
473 _level: PromptLevel,
474 _msg: &str,
475 _detail: Option<&str>,
476 _answers: &[&str],
477 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
478 None
479 }
480
481 fn activate(&self) {
482 let win_aux = xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE);
483 self.0
484 .xcb_connection
485 .configure_window(self.0.x_window, &win_aux)
486 .log_err();
487 }
488
489 // todo(linux)
490 fn is_active(&self) -> bool {
491 false
492 }
493
494 fn set_title(&mut self, title: &str) {
495 self.0
496 .xcb_connection
497 .change_property8(
498 xproto::PropMode::REPLACE,
499 self.0.x_window,
500 xproto::AtomEnum::WM_NAME,
501 xproto::AtomEnum::STRING,
502 title.as_bytes(),
503 )
504 .unwrap();
505
506 self.0
507 .xcb_connection
508 .change_property8(
509 xproto::PropMode::REPLACE,
510 self.0.x_window,
511 self.0.state.borrow().atoms._NET_WM_NAME,
512 self.0.state.borrow().atoms.UTF8_STRING,
513 title.as_bytes(),
514 )
515 .unwrap();
516 }
517
518 fn set_app_id(&mut self, app_id: &str) {
519 let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
520 data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
521 data.push(b'\0');
522 data.extend(app_id.bytes()); // class
523
524 self.0.xcb_connection.change_property8(
525 xproto::PropMode::REPLACE,
526 self.0.x_window,
527 xproto::AtomEnum::WM_CLASS,
528 xproto::AtomEnum::STRING,
529 &data,
530 );
531 }
532
533 // todo(linux)
534 fn set_edited(&mut self, edited: bool) {}
535
536 fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
537 // todo(linux)
538 }
539
540 // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
541 // but it looks like the equivalent for Linux is GTK specific:
542 //
543 // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
544 //
545 // This API might need to change, or we might need to build an emoji picker into GPUI
546 fn show_character_palette(&self) {
547 unimplemented!()
548 }
549
550 // todo(linux)
551 fn minimize(&self) {
552 unimplemented!()
553 }
554
555 // todo(linux)
556 fn zoom(&self) {
557 unimplemented!()
558 }
559
560 // todo(linux)
561 fn toggle_fullscreen(&self) {
562 unimplemented!()
563 }
564
565 // todo(linux)
566 fn is_fullscreen(&self) -> bool {
567 false
568 }
569
570 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
571 self.0.callbacks.borrow_mut().request_frame = Some(callback);
572 }
573
574 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> crate::DispatchEventResult>) {
575 self.0.callbacks.borrow_mut().input = Some(callback);
576 }
577
578 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
579 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
580 }
581
582 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
583 self.0.callbacks.borrow_mut().resize = Some(callback);
584 }
585
586 fn on_moved(&self, callback: Box<dyn FnMut()>) {
587 self.0.callbacks.borrow_mut().moved = Some(callback);
588 }
589
590 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
591 self.0.callbacks.borrow_mut().should_close = Some(callback);
592 }
593
594 fn on_close(&self, callback: Box<dyn FnOnce()>) {
595 self.0.callbacks.borrow_mut().close = Some(callback);
596 }
597
598 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
599 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
600 }
601
602 fn draw(&self, scene: &Scene) {
603 let mut inner = self.0.state.borrow_mut();
604 inner.renderer.draw(scene);
605 }
606
607 fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
608 let inner = self.0.state.borrow();
609 inner.renderer.sprite_atlas().clone()
610 }
611}