1// todo(linux): remove
2#![allow(unused)]
3
4use crate::{
5 platform::blade::BladeRenderer, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformAtlas,
6 PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
7 Scene, Size, WindowAppearance, WindowBounds, WindowOptions,
8};
9use blade_graphics as gpu;
10use parking_lot::Mutex;
11use raw_window_handle as rwh;
12
13use xcb::{
14 x::{self, StackMode},
15 Xid as _,
16};
17
18use std::{
19 cell::RefCell,
20 ffi::c_void,
21 mem,
22 num::NonZeroU32,
23 ptr::NonNull,
24 rc::Rc,
25 sync::{self, Arc},
26};
27
28use super::X11Display;
29
30#[derive(Default)]
31struct Callbacks {
32 request_frame: Option<Box<dyn FnMut()>>,
33 input: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
34 active_status_change: Option<Box<dyn FnMut(bool)>>,
35 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
36 fullscreen: Option<Box<dyn FnMut(bool)>>,
37 moved: Option<Box<dyn FnMut()>>,
38 should_close: Option<Box<dyn FnMut() -> bool>>,
39 close: Option<Box<dyn FnOnce()>>,
40 appearance_changed: Option<Box<dyn FnMut()>>,
41}
42
43xcb::atoms_struct! {
44 #[derive(Debug)]
45 pub(crate) struct XcbAtoms {
46 pub wm_protocols => b"WM_PROTOCOLS",
47 pub wm_del_window => b"WM_DELETE_WINDOW",
48 wm_state => b"_NET_WM_STATE",
49 wm_state_maxv => b"_NET_WM_STATE_MAXIMIZED_VERT",
50 wm_state_maxh => b"_NET_WM_STATE_MAXIMIZED_HORZ",
51 }
52}
53
54struct LinuxWindowInner {
55 bounds: Bounds<i32>,
56 scale_factor: f32,
57 renderer: BladeRenderer,
58 input_handler: Option<PlatformInputHandler>,
59}
60
61impl LinuxWindowInner {
62 fn content_size(&self) -> Size<Pixels> {
63 let size = self.renderer.viewport_size();
64 Size {
65 width: size.width.into(),
66 height: size.height.into(),
67 }
68 }
69}
70
71fn query_render_extent(xcb_connection: &xcb::Connection, x_window: x::Window) -> gpu::Extent {
72 let cookie = xcb_connection.send_request(&x::GetGeometry {
73 drawable: x::Drawable::Window(x_window),
74 });
75 let reply = xcb_connection.wait_for_reply(cookie).unwrap();
76 gpu::Extent {
77 width: reply.width() as u32,
78 height: reply.height() as u32,
79 depth: 1,
80 }
81}
82
83struct RawWindow {
84 connection: *mut c_void,
85 screen_id: i32,
86 window_id: u32,
87 visual_id: u32,
88}
89
90pub(crate) struct X11WindowState {
91 xcb_connection: Rc<xcb::Connection>,
92 display: Rc<dyn PlatformDisplay>,
93 raw: RawWindow,
94 x_window: x::Window,
95 callbacks: RefCell<Callbacks>,
96 inner: RefCell<LinuxWindowInner>,
97}
98
99#[derive(Clone)]
100pub(crate) struct X11Window(pub(crate) Rc<X11WindowState>);
101
102// todo(linux): Remove other RawWindowHandle implementation
103unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
104 fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
105 let mut wh = blade_rwh::XcbWindowHandle::empty();
106 wh.window = self.window_id;
107 wh.visual_id = self.visual_id;
108 wh.into()
109 }
110}
111unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
112 fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
113 let mut dh = blade_rwh::XcbDisplayHandle::empty();
114 dh.connection = self.connection;
115 dh.screen = self.screen_id;
116 dh.into()
117 }
118}
119
120impl rwh::HasWindowHandle for X11Window {
121 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
122 Ok(unsafe {
123 let non_zero = NonZeroU32::new(self.0.raw.window_id).unwrap();
124 let handle = rwh::XcbWindowHandle::new(non_zero);
125 rwh::WindowHandle::borrow_raw(handle.into())
126 })
127 }
128}
129impl rwh::HasDisplayHandle for X11Window {
130 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
131 Ok(unsafe {
132 let non_zero = NonNull::new(self.0.raw.connection).unwrap();
133 let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.0.raw.screen_id);
134 rwh::DisplayHandle::borrow_raw(handle.into())
135 })
136 }
137}
138
139impl X11WindowState {
140 pub fn new(
141 options: WindowOptions,
142 xcb_connection: &Rc<xcb::Connection>,
143 x_main_screen_index: i32,
144 x_window: x::Window,
145 atoms: &XcbAtoms,
146 ) -> Self {
147 let x_screen_index = options
148 .display_id
149 .map_or(x_main_screen_index, |did| did.0 as i32);
150 let screen = xcb_connection
151 .get_setup()
152 .roots()
153 .nth(x_screen_index as usize)
154 .unwrap();
155
156 let xcb_values = [
157 x::Cw::BackPixel(screen.white_pixel()),
158 x::Cw::EventMask(
159 x::EventMask::EXPOSURE
160 | x::EventMask::STRUCTURE_NOTIFY
161 | x::EventMask::ENTER_WINDOW
162 | x::EventMask::LEAVE_WINDOW
163 | x::EventMask::FOCUS_CHANGE
164 | x::EventMask::KEY_PRESS
165 | x::EventMask::KEY_RELEASE
166 | x::EventMask::BUTTON_PRESS
167 | x::EventMask::BUTTON_RELEASE
168 | x::EventMask::POINTER_MOTION
169 | x::EventMask::BUTTON1_MOTION
170 | x::EventMask::BUTTON2_MOTION
171 | x::EventMask::BUTTON3_MOTION
172 | x::EventMask::BUTTON4_MOTION
173 | x::EventMask::BUTTON5_MOTION
174 | x::EventMask::BUTTON_MOTION,
175 ),
176 ];
177
178 let bounds = match options.bounds {
179 WindowBounds::Fullscreen | WindowBounds::Maximized => Bounds {
180 origin: Point::default(),
181 size: Size {
182 width: screen.width_in_pixels() as i32,
183 height: screen.height_in_pixels() as i32,
184 },
185 },
186 WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32),
187 };
188
189 xcb_connection.send_request(&x::CreateWindow {
190 depth: x::COPY_FROM_PARENT as u8,
191 wid: x_window,
192 parent: screen.root(),
193 x: bounds.origin.x as i16,
194 y: bounds.origin.y as i16,
195 width: bounds.size.width as u16,
196 height: bounds.size.height as u16,
197 border_width: 0,
198 class: x::WindowClass::InputOutput,
199 visual: screen.root_visual(),
200 value_list: &xcb_values,
201 });
202
203 if let Some(titlebar) = options.titlebar {
204 if let Some(title) = titlebar.title {
205 xcb_connection.send_request(&x::ChangeProperty {
206 mode: x::PropMode::Replace,
207 window: x_window,
208 property: x::ATOM_WM_NAME,
209 r#type: x::ATOM_STRING,
210 data: title.as_bytes(),
211 });
212 }
213 }
214 xcb_connection.send_request(&x::ChangeProperty {
215 mode: x::PropMode::Replace,
216 window: x_window,
217 property: atoms.wm_protocols,
218 r#type: x::ATOM_ATOM,
219 data: &[atoms.wm_del_window],
220 });
221
222 xcb_connection.send_request(&x::MapWindow { window: x_window });
223 xcb_connection.flush().unwrap();
224
225 let raw = RawWindow {
226 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
227 xcb_connection,
228 ) as *mut _,
229 screen_id: x_screen_index,
230 window_id: x_window.resource_id(),
231 visual_id: screen.root_visual(),
232 };
233 let gpu = Arc::new(
234 unsafe {
235 gpu::Context::init_windowed(
236 &raw,
237 gpu::ContextDesc {
238 validation: false,
239 capture: false,
240 },
241 )
242 }
243 .unwrap(),
244 );
245
246 // Note: this has to be done after the GPU init, or otherwise
247 // the sizes are immediately invalidated.
248 let gpu_extent = query_render_extent(xcb_connection, x_window);
249
250 Self {
251 xcb_connection: xcb_connection.clone(),
252 display: Rc::new(X11Display::new(xcb_connection, x_screen_index)),
253 raw,
254 x_window,
255 callbacks: RefCell::new(Callbacks::default()),
256 inner: RefCell::new(LinuxWindowInner {
257 bounds,
258 scale_factor: 1.0,
259 renderer: BladeRenderer::new(gpu, gpu_extent),
260 input_handler: None,
261 }),
262 }
263 }
264
265 pub fn destroy(&self) {
266 self.inner.borrow_mut().renderer.destroy();
267 self.xcb_connection.send_request(&x::UnmapWindow {
268 window: self.x_window,
269 });
270 self.xcb_connection.send_request(&x::DestroyWindow {
271 window: self.x_window,
272 });
273 if let Some(fun) = self.callbacks.borrow_mut().close.take() {
274 fun();
275 }
276 self.xcb_connection.flush().unwrap();
277 }
278
279 pub fn refresh(&self) {
280 let mut cb = self.callbacks.borrow_mut();
281 if let Some(ref mut fun) = cb.request_frame {
282 fun();
283 }
284 }
285
286 pub fn configure(&self, bounds: Bounds<i32>) {
287 let mut resize_args = None;
288 let do_move;
289 {
290 let mut inner = self.inner.borrow_mut();
291 let old_bounds = mem::replace(&mut inner.bounds, bounds);
292 do_move = old_bounds.origin != bounds.origin;
293 // todo(linux): use normal GPUI types here, refactor out the double
294 // viewport check and extra casts ( )
295 let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
296 if inner.renderer.viewport_size() != gpu_size {
297 inner
298 .renderer
299 .update_drawable_size(size(gpu_size.width as f64, gpu_size.height as f64));
300 resize_args = Some((inner.content_size(), inner.scale_factor));
301 }
302 }
303
304 let mut callbacks = self.callbacks.borrow_mut();
305 if let Some((content_size, scale_factor)) = resize_args {
306 if let Some(ref mut fun) = callbacks.resize {
307 fun(content_size, scale_factor)
308 }
309 }
310 if do_move {
311 if let Some(ref mut fun) = callbacks.moved {
312 fun()
313 }
314 }
315 }
316
317 pub fn handle_input(&self, input: PlatformInput) {
318 if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
319 if fun(input.clone()) {
320 return;
321 }
322 }
323 if let PlatformInput::KeyDown(event) = input {
324 let mut inner = self.inner.borrow_mut();
325 if let Some(ref mut input_handler) = inner.input_handler {
326 if let Some(ime_key) = &event.keystroke.ime_key {
327 input_handler.replace_text_in_range(None, ime_key);
328 }
329 }
330 }
331 }
332
333 pub fn set_focused(&self, focus: bool) {
334 if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
335 fun(focus);
336 }
337 }
338}
339
340impl PlatformWindow for X11Window {
341 fn bounds(&self) -> WindowBounds {
342 WindowBounds::Fixed(
343 self.0
344 .inner
345 .borrow_mut()
346 .bounds
347 .map(|v| GlobalPixels(v as f32)),
348 )
349 }
350
351 fn content_size(&self) -> Size<Pixels> {
352 self.0.inner.borrow_mut().content_size()
353 }
354
355 fn scale_factor(&self) -> f32 {
356 self.0.inner.borrow_mut().scale_factor
357 }
358
359 // todo(linux)
360 fn titlebar_height(&self) -> Pixels {
361 unimplemented!()
362 }
363
364 // todo(linux)
365 fn appearance(&self) -> WindowAppearance {
366 WindowAppearance::Light
367 }
368
369 fn display(&self) -> Rc<dyn PlatformDisplay> {
370 Rc::clone(&self.0.display)
371 }
372
373 fn mouse_position(&self) -> Point<Pixels> {
374 let cookie = self.0.xcb_connection.send_request(&x::QueryPointer {
375 window: self.0.x_window,
376 });
377 let reply: x::QueryPointerReply = self.0.xcb_connection.wait_for_reply(cookie).unwrap();
378 Point::new(
379 (reply.root_x() as u32).into(),
380 (reply.root_y() as u32).into(),
381 )
382 }
383
384 // todo(linux)
385 fn modifiers(&self) -> Modifiers {
386 Modifiers::default()
387 }
388
389 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
390 self
391 }
392
393 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
394 self.0.inner.borrow_mut().input_handler = Some(input_handler);
395 }
396
397 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
398 self.0.inner.borrow_mut().input_handler.take()
399 }
400
401 // todo(linux)
402 fn prompt(
403 &self,
404 _level: PromptLevel,
405 _msg: &str,
406 _detail: Option<&str>,
407 _answers: &[&str],
408 ) -> futures::channel::oneshot::Receiver<usize> {
409 unimplemented!()
410 }
411
412 fn activate(&self) {
413 self.0.xcb_connection.send_request(&x::ConfigureWindow {
414 window: self.0.x_window,
415 value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)],
416 });
417 }
418
419 fn set_title(&mut self, title: &str) {
420 self.0.xcb_connection.send_request(&x::ChangeProperty {
421 mode: x::PropMode::Replace,
422 window: self.0.x_window,
423 property: x::ATOM_WM_NAME,
424 r#type: x::ATOM_STRING,
425 data: title.as_bytes(),
426 });
427 }
428
429 // todo(linux)
430 fn set_edited(&mut self, edited: bool) {}
431
432 // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
433 // but it looks like the equivalent for Linux is GTK specific:
434 //
435 // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
436 //
437 // This API might need to change, or we might need to build an emoji picker into GPUI
438 fn show_character_palette(&self) {
439 unimplemented!()
440 }
441
442 // todo(linux)
443 fn minimize(&self) {
444 unimplemented!()
445 }
446
447 // todo(linux)
448 fn zoom(&self) {
449 unimplemented!()
450 }
451
452 // todo(linux)
453 fn toggle_full_screen(&self) {
454 unimplemented!()
455 }
456
457 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
458 self.0.callbacks.borrow_mut().request_frame = Some(callback);
459 }
460
461 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
462 self.0.callbacks.borrow_mut().input = Some(callback);
463 }
464
465 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
466 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
467 }
468
469 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
470 self.0.callbacks.borrow_mut().resize = Some(callback);
471 }
472
473 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
474 self.0.callbacks.borrow_mut().fullscreen = Some(callback);
475 }
476
477 fn on_moved(&self, callback: Box<dyn FnMut()>) {
478 self.0.callbacks.borrow_mut().moved = Some(callback);
479 }
480
481 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
482 self.0.callbacks.borrow_mut().should_close = Some(callback);
483 }
484
485 fn on_close(&self, callback: Box<dyn FnOnce()>) {
486 self.0.callbacks.borrow_mut().close = Some(callback);
487 }
488
489 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
490 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
491 }
492
493 // todo(linux)
494 fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
495 unimplemented!()
496 }
497
498 fn draw(&self, scene: &Scene) {
499 let mut inner = self.0.inner.borrow_mut();
500 inner.renderer.draw(scene);
501 }
502
503 fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
504 let inner = self.0.inner.borrow_mut();
505 inner.renderer.sprite_atlas().clone()
506 }
507}