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 title_height: i32,
35 border_width: i32,
36 scale_factor: f32,
37 renderer: BladeRenderer,
38}
39
40impl LinuxWindowInner {
41 fn render_extent(&self) -> gpu::Extent {
42 gpu::Extent {
43 width: (self.bounds.size.width - 2 * self.border_width) as u32,
44 height: (self.bounds.size.height - 2 * self.border_width - self.title_height) as u32,
45 depth: 1,
46 }
47 }
48 fn content_size(&self) -> Size<Pixels> {
49 let extent = self.render_extent();
50 Size {
51 width: extent.width.into(),
52 height: extent.height.into(),
53 }
54 }
55}
56
57struct RawWindow {
58 connection: *mut c_void,
59 screen_id: i32,
60 window_id: u32,
61 visual_id: u32,
62}
63
64pub(crate) struct LinuxWindowState {
65 xcb_connection: Arc<xcb::Connection>,
66 display: Rc<dyn PlatformDisplay>,
67 raw: RawWindow,
68 x_window: x::Window,
69 callbacks: Mutex<Callbacks>,
70 inner: Mutex<LinuxWindowInner>,
71}
72
73#[derive(Clone)]
74pub(crate) struct LinuxWindow(pub(crate) Arc<LinuxWindowState>);
75
76unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
77 fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
78 let mut wh = blade_rwh::XcbWindowHandle::empty();
79 wh.window = self.window_id;
80 wh.visual_id = self.visual_id;
81 wh.into()
82 }
83}
84unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
85 fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
86 let mut dh = blade_rwh::XcbDisplayHandle::empty();
87 dh.connection = self.connection;
88 dh.screen = self.screen_id;
89 dh.into()
90 }
91}
92
93impl rwh::HasWindowHandle for LinuxWindow {
94 fn window_handle(&self) -> Result<rwh::WindowHandle, rwh::HandleError> {
95 Ok(unsafe {
96 let non_zero = NonZeroU32::new(self.0.raw.window_id).unwrap();
97 let handle = rwh::XcbWindowHandle::new(non_zero);
98 rwh::WindowHandle::borrow_raw(handle.into())
99 })
100 }
101}
102impl rwh::HasDisplayHandle for LinuxWindow {
103 fn display_handle(&self) -> Result<rwh::DisplayHandle, rwh::HandleError> {
104 Ok(unsafe {
105 let non_zero = NonNull::new(self.0.raw.connection).unwrap();
106 let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.0.raw.screen_id);
107 rwh::DisplayHandle::borrow_raw(handle.into())
108 })
109 }
110}
111
112impl LinuxWindowState {
113 pub fn new(
114 options: WindowOptions,
115 xcb_connection: &Arc<xcb::Connection>,
116 x_main_screen_index: i32,
117 x_window: x::Window,
118 atoms: &XcbAtoms,
119 ) -> Self {
120 let x_screen_index = options
121 .display_id
122 .map_or(x_main_screen_index, |did| did.0 as i32);
123 let screen = xcb_connection
124 .get_setup()
125 .roots()
126 .nth(x_screen_index as usize)
127 .unwrap();
128
129 let xcb_values = [
130 x::Cw::BackPixel(screen.white_pixel()),
131 x::Cw::EventMask(
132 x::EventMask::EXPOSURE | x::EventMask::STRUCTURE_NOTIFY | x::EventMask::KEY_PRESS,
133 ),
134 ];
135
136 let bounds = match options.bounds {
137 WindowBounds::Fullscreen | WindowBounds::Maximized => Bounds {
138 origin: Point::default(),
139 size: Size {
140 width: screen.width_in_pixels() as i32,
141 height: screen.height_in_pixels() as i32,
142 },
143 },
144 WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32),
145 };
146 let border_width = 0i32;
147
148 xcb_connection.send_request(&x::CreateWindow {
149 depth: x::COPY_FROM_PARENT as u8,
150 wid: x_window,
151 parent: screen.root(),
152 x: bounds.origin.x as i16,
153 y: bounds.origin.y as i16,
154 width: bounds.size.width as u16,
155 height: bounds.size.height as u16,
156 border_width: border_width as u16,
157 class: x::WindowClass::InputOutput,
158 visual: screen.root_visual(),
159 value_list: &xcb_values,
160 });
161
162 if let Some(titlebar) = options.titlebar {
163 if let Some(title) = titlebar.title {
164 xcb_connection.send_request(&x::ChangeProperty {
165 mode: x::PropMode::Replace,
166 window: x_window,
167 property: x::ATOM_WM_NAME,
168 r#type: x::ATOM_STRING,
169 data: title.as_bytes(),
170 });
171 }
172 }
173 xcb_connection
174 .send_and_check_request(&x::ChangeProperty {
175 mode: x::PropMode::Replace,
176 window: x_window,
177 property: atoms.wm_protocols,
178 r#type: x::ATOM_ATOM,
179 data: &[atoms.wm_del_window],
180 })
181 .unwrap();
182
183 xcb_connection.send_request(&x::MapWindow { window: x_window });
184 xcb_connection.flush().unwrap();
185
186 let raw = RawWindow {
187 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
188 xcb_connection,
189 ) as *mut _,
190 screen_id: x_screen_index,
191 window_id: x_window.resource_id(),
192 visual_id: screen.root_visual(),
193 };
194 let gpu = Arc::new(
195 unsafe {
196 gpu::Context::init_windowed(
197 &raw,
198 gpu::ContextDesc {
199 validation: cfg!(debug_assertions),
200 capture: false,
201 },
202 )
203 }
204 .unwrap(),
205 );
206
207 let gpu_extent = gpu::Extent {
208 width: bounds.size.width as u32,
209 height: bounds.size.height as u32,
210 depth: 1,
211 };
212
213 Self {
214 xcb_connection: Arc::clone(xcb_connection),
215 display: Rc::new(LinuxDisplay::new(xcb_connection, x_screen_index)),
216 raw,
217 x_window,
218 callbacks: Mutex::new(Callbacks::default()),
219 inner: Mutex::new(LinuxWindowInner {
220 bounds,
221 title_height: 0, //TODO
222 border_width,
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 if old_bounds.size != bounds.size {
258 let extent = inner.render_extent();
259 inner.renderer.resize(extent);
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 fn titlebar_height(&self) -> Pixels {
292 unimplemented!()
293 }
294
295 fn appearance(&self) -> WindowAppearance {
296 unimplemented!()
297 }
298
299 fn display(&self) -> Rc<dyn PlatformDisplay> {
300 Rc::clone(&self.0.display)
301 }
302
303 fn mouse_position(&self) -> Point<Pixels> {
304 Point::default()
305 }
306
307 fn modifiers(&self) -> crate::Modifiers {
308 crate::Modifiers::default()
309 }
310
311 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
312 self
313 }
314
315 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {}
316
317 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
318 None
319 }
320
321 fn prompt(
322 &self,
323 _level: crate::PromptLevel,
324 _msg: &str,
325 _detail: Option<&str>,
326 _answers: &[&str],
327 ) -> futures::channel::oneshot::Receiver<usize> {
328 unimplemented!()
329 }
330
331 fn activate(&self) {}
332
333 fn set_title(&mut self, title: &str) {}
334
335 fn set_edited(&mut self, edited: bool) {}
336
337 fn show_character_palette(&self) {
338 unimplemented!()
339 }
340
341 fn minimize(&self) {
342 unimplemented!()
343 }
344
345 fn zoom(&self) {
346 unimplemented!()
347 }
348
349 fn toggle_full_screen(&self) {
350 unimplemented!()
351 }
352
353 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
354 self.0.callbacks.lock().request_frame = Some(callback);
355 }
356
357 fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
358 self.0.callbacks.lock().input = Some(callback);
359 }
360
361 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
362 self.0.callbacks.lock().active_status_change = Some(callback);
363 }
364
365 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
366 self.0.callbacks.lock().resize = Some(callback);
367 }
368
369 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
370 self.0.callbacks.lock().fullscreen = Some(callback);
371 }
372
373 fn on_moved(&self, callback: Box<dyn FnMut()>) {
374 self.0.callbacks.lock().moved = Some(callback);
375 }
376
377 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
378 self.0.callbacks.lock().should_close = Some(callback);
379 }
380
381 fn on_close(&self, callback: Box<dyn FnOnce()>) {
382 self.0.callbacks.lock().close = Some(callback);
383 }
384
385 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
386 self.0.callbacks.lock().appearance_changed = Some(callback);
387 }
388
389 fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
390 unimplemented!()
391 }
392
393 fn invalidate(&self) {}
394
395 fn draw(&self, scene: &crate::Scene) {
396 let mut inner = self.0.inner.lock();
397 inner.renderer.draw(scene);
398 }
399
400 fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
401 let inner = self.0.inner.lock();
402 inner.renderer.atlas().clone()
403 }
404}