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