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 overlay: false,
241 },
242 )
243 }
244 .unwrap(),
245 );
246
247 // Note: this has to be done after the GPU init, or otherwise
248 // the sizes are immediately invalidated.
249 let gpu_extent = query_render_extent(xcb_connection, x_window);
250
251 Self {
252 xcb_connection: xcb_connection.clone(),
253 display: Rc::new(X11Display::new(xcb_connection, x_screen_index)),
254 raw,
255 x_window,
256 callbacks: RefCell::new(Callbacks::default()),
257 inner: RefCell::new(LinuxWindowInner {
258 bounds,
259 scale_factor: 1.0,
260 renderer: BladeRenderer::new(gpu, gpu_extent),
261 input_handler: None,
262 }),
263 }
264 }
265
266 pub fn destroy(&self) {
267 self.inner.borrow_mut().renderer.destroy();
268 self.xcb_connection.send_request(&x::UnmapWindow {
269 window: self.x_window,
270 });
271 self.xcb_connection.send_request(&x::DestroyWindow {
272 window: self.x_window,
273 });
274 if let Some(fun) = self.callbacks.borrow_mut().close.take() {
275 fun();
276 }
277 self.xcb_connection.flush().unwrap();
278 }
279
280 pub fn refresh(&self) {
281 let mut cb = self.callbacks.borrow_mut();
282 if let Some(ref mut fun) = cb.request_frame {
283 fun();
284 }
285 }
286
287 pub fn configure(&self, bounds: Bounds<i32>) {
288 let mut resize_args = None;
289 let do_move;
290 {
291 let mut inner = self.inner.borrow_mut();
292 let old_bounds = mem::replace(&mut inner.bounds, bounds);
293 do_move = old_bounds.origin != bounds.origin;
294 // todo(linux): use normal GPUI types here, refactor out the double
295 // viewport check and extra casts ( )
296 let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
297 if inner.renderer.viewport_size() != gpu_size {
298 inner
299 .renderer
300 .update_drawable_size(size(gpu_size.width as f64, gpu_size.height as f64));
301 resize_args = Some((inner.content_size(), inner.scale_factor));
302 }
303 }
304
305 let mut callbacks = self.callbacks.borrow_mut();
306 if let Some((content_size, scale_factor)) = resize_args {
307 if let Some(ref mut fun) = callbacks.resize {
308 fun(content_size, scale_factor)
309 }
310 }
311 if do_move {
312 if let Some(ref mut fun) = callbacks.moved {
313 fun()
314 }
315 }
316 }
317
318 pub fn handle_input(&self, input: PlatformInput) {
319 if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
320 if fun(input.clone()) {
321 return;
322 }
323 }
324 if let PlatformInput::KeyDown(event) = input {
325 let mut inner = self.inner.borrow_mut();
326 if let Some(ref mut input_handler) = inner.input_handler {
327 if let Some(ime_key) = &event.keystroke.ime_key {
328 input_handler.replace_text_in_range(None, ime_key);
329 }
330 }
331 }
332 }
333
334 pub fn set_focused(&self, focus: bool) {
335 if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
336 fun(focus);
337 }
338 }
339}
340
341impl PlatformWindow for X11Window {
342 fn bounds(&self) -> WindowBounds {
343 WindowBounds::Fixed(
344 self.0
345 .inner
346 .borrow_mut()
347 .bounds
348 .map(|v| GlobalPixels(v as f32)),
349 )
350 }
351
352 fn content_size(&self) -> Size<Pixels> {
353 self.0.inner.borrow_mut().content_size()
354 }
355
356 fn scale_factor(&self) -> f32 {
357 self.0.inner.borrow_mut().scale_factor
358 }
359
360 // todo(linux)
361 fn titlebar_height(&self) -> Pixels {
362 unimplemented!()
363 }
364
365 // todo(linux)
366 fn appearance(&self) -> WindowAppearance {
367 WindowAppearance::Light
368 }
369
370 fn display(&self) -> Rc<dyn PlatformDisplay> {
371 Rc::clone(&self.0.display)
372 }
373
374 fn mouse_position(&self) -> Point<Pixels> {
375 let cookie = self.0.xcb_connection.send_request(&x::QueryPointer {
376 window: self.0.x_window,
377 });
378 let reply: x::QueryPointerReply = self.0.xcb_connection.wait_for_reply(cookie).unwrap();
379 Point::new(
380 (reply.root_x() as u32).into(),
381 (reply.root_y() as u32).into(),
382 )
383 }
384
385 // todo(linux)
386 fn modifiers(&self) -> Modifiers {
387 Modifiers::default()
388 }
389
390 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
391 self
392 }
393
394 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
395 self.0.inner.borrow_mut().input_handler = Some(input_handler);
396 }
397
398 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
399 self.0.inner.borrow_mut().input_handler.take()
400 }
401
402 // todo(linux)
403 fn prompt(
404 &self,
405 _level: PromptLevel,
406 _msg: &str,
407 _detail: Option<&str>,
408 _answers: &[&str],
409 ) -> futures::channel::oneshot::Receiver<usize> {
410 unimplemented!()
411 }
412
413 fn activate(&self) {
414 self.0.xcb_connection.send_request(&x::ConfigureWindow {
415 window: self.0.x_window,
416 value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)],
417 });
418 }
419
420 fn set_title(&mut self, title: &str) {
421 self.0.xcb_connection.send_request(&x::ChangeProperty {
422 mode: x::PropMode::Replace,
423 window: self.0.x_window,
424 property: x::ATOM_WM_NAME,
425 r#type: x::ATOM_STRING,
426 data: title.as_bytes(),
427 });
428 }
429
430 // todo(linux)
431 fn set_edited(&mut self, edited: bool) {}
432
433 // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
434 // but it looks like the equivalent for Linux is GTK specific:
435 //
436 // https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
437 //
438 // This API might need to change, or we might need to build an emoji picker into GPUI
439 fn show_character_palette(&self) {
440 unimplemented!()
441 }
442
443 // todo(linux)
444 fn minimize(&self) {
445 unimplemented!()
446 }
447
448 // todo(linux)
449 fn zoom(&self) {
450 unimplemented!()
451 }
452
453 // todo(linux)
454 fn toggle_full_screen(&self) {
455 unimplemented!()
456 }
457
458 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
459 self.0.callbacks.borrow_mut().request_frame = Some(callback);
460 }
461
462 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
463 self.0.callbacks.borrow_mut().input = Some(callback);
464 }
465
466 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
467 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
468 }
469
470 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
471 self.0.callbacks.borrow_mut().resize = Some(callback);
472 }
473
474 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
475 self.0.callbacks.borrow_mut().fullscreen = Some(callback);
476 }
477
478 fn on_moved(&self, callback: Box<dyn FnMut()>) {
479 self.0.callbacks.borrow_mut().moved = Some(callback);
480 }
481
482 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
483 self.0.callbacks.borrow_mut().should_close = Some(callback);
484 }
485
486 fn on_close(&self, callback: Box<dyn FnOnce()>) {
487 self.0.callbacks.borrow_mut().close = Some(callback);
488 }
489
490 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
491 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
492 }
493
494 // todo(linux)
495 fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
496 unimplemented!()
497 }
498
499 fn draw(&self, scene: &Scene) {
500 let mut inner = self.0.inner.borrow_mut();
501 inner.renderer.draw(scene);
502 }
503
504 fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
505 let inner = self.0.inner.borrow_mut();
506 inner.renderer.sprite_atlas().clone()
507 }
508}