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