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