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