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