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