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