1use crate::{
2 PopupSlint, SpellAssociatedNew,
3 configure::{Dimension, HomeHandle, PopupConf, WindowConf, set_up_tracing},
4 slint_adapter::{ADAPTERS, SpellLayerShell, SpellSkiaWinAdapter},
5 wayland_adapter::{
6 common::PointerState,
7 fractional_scaling::{FractionalScaleState, delegate_fractional_scale},
8 viewporter::{Viewport, ViewporterState, delegate_viewporter},
9 window,
10 },
11};
12use i_slint_core::items::MouseCursor;
13use smithay_client_toolkit::{
14 compositor::{CompositorState, Region},
15 delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer,
16 delegate_registry, delegate_seat, delegate_shm, delegate_touch, delegate_xdg_popup,
17 delegate_xdg_shell,
18 output::OutputState,
19 reexports::{
20 calloop::{self, EventLoop, LoopHandle},
21 calloop_wayland_source::WaylandSource,
22 client::{
23 Connection, QueueHandle,
24 globals::registry_queue_init,
25 protocol::{
26 wl_keyboard::WlKeyboard,
27 wl_output::{self, WlOutput},
28 wl_shm,
29 wl_surface::WlSurface,
30 wl_touch::WlTouch,
31 },
32 },
33 },
34 registry::RegistryState,
35 seat::{SeatState, pointer::cursor_shape::CursorShapeManager},
36 shell::{
37 WaylandSurface,
38 wlr_layer::{KeyboardInteractivity, LayerShell, LayerSurface},
39 xdg::XdgShell,
40 },
41 shm::{
42 Shm,
43 slot::{Buffer, SlotPool},
44 },
45};
46use std::{
47 cell::{Cell, RefCell},
48 collections::HashMap,
49 os::unix::net::UnixListener,
50 rc::Rc,
51 sync::{Once, OnceLock, RwLock},
52};
53use tracing::{Level, info, span, trace, warn};
54
55mod input;
56mod internal;
57mod popup;
58mod wayland;
59pub use popup::SpellXDGPopup;
60
61#[allow(clippy::type_complexity)]
62static AVAILABLE_MONITORS: OnceLock<RwLock<HashMap<String, (wl_output::WlOutput, i32, i32)>>> =
63 OnceLock::new();
64static SET_SLINT_PLATFORM: Once = Once::new();
65
66#[derive(Debug)]
67struct States {
68 registry_state: RegistryState,
69 seat_state: SeatState,
70 output_state: OutputState,
71 compositor_state: CompositorState,
72 pointer_state: PointerState,
73 keyboard_state: Option<WlKeyboard>,
74 touch_state: Option<WlTouch>,
75 shm: Shm,
76 viewporter_state: ViewporterState,
77 fractional_scale_state: FractionalScaleState,
78}
79
80pub struct SpellWin {
83 adapter: Option<Rc<SpellSkiaWinAdapter>>,
84 loop_handle: LoopHandle<'static, SpellWin>,
85 pub ipc_handler: Option<UnixListener>,
87 pub layer_name: String,
89 pub span: span::Span,
91 queue: QueueHandle<SpellWin>,
92 buffer: Option<Buffer>,
93 states: States,
94 layer: Option<LayerSurface>,
95 first_configure: Cell<bool>,
96 natural_scroll: bool,
97 is_hidden: Cell<bool>,
98 config: WindowConf,
99 input_region: Region,
100 opaque_region: Region,
101 viewport: Option<Viewport>,
102 xdg_shell: XdgShell,
103 popup_manager: window::popup::PopupManager,
104 event_loop: Rc<RefCell<EventLoop<'static, SpellWin>>>,
105}
106
107impl std::fmt::Debug for SpellWin {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("SpellWin")
110 .field("adapter", &self.adapter)
111 .field("first_configure", &self.first_configure)
112 .field("is_hidden", &self.is_hidden)
113 .field("config", &self.config)
114 .finish()
115 }
116}
117
118impl SpellWin {
119 fn create_window(
120 conn: &Connection,
121 mut window_conf: WindowConf,
122 layer_name: String,
123 handle: HomeHandle,
124 ) -> Self {
125 let (globals, mut event_queue) = registry_queue_init(conn).unwrap();
126 let qh: QueueHandle<SpellWin> = event_queue.handle();
127 let compositor =
128 CompositorState::bind(&globals, &qh).expect("wl_compositor is not available");
129 let event_loop: EventLoop<'static, SpellWin> =
130 EventLoop::try_new().expect("Failed to initialize the event loop!");
131 let layer_shell = LayerShell::bind(&globals, &qh).expect("layer shell is not available");
132 let shm = Shm::bind(&globals, &qh).expect("wl_shm is not available");
133 let cursor_manager =
134 CursorShapeManager::bind(&globals, &qh).expect("cursor shape is not available");
135 let surface = compositor.create_surface(&qh);
136 let viewporter_state =
137 ViewporterState::bind(&globals, &qh).expect("Couldn't set viewporter");
138 let fractional_scale_state: FractionalScaleState =
139 FractionalScaleState::bind(&globals, &qh).expect("Fractional Scale couldn't be set");
140 let xdg_shell = XdgShell::bind(&globals, &qh).expect("Couldn't bind xdg_shell");
141 let pointer_state = PointerState {
142 pointer: None,
143 pointer_data: None,
144 cursor_shape: cursor_manager,
145 current_wayland_cursor: MouseCursor::Default,
146 last_cursor_enter_serial: None,
147 };
148 let input_region = Region::new(&compositor).expect("Couldn't create region");
149 let opaque_region = Region::new(&compositor).expect("Couldn't create opaque region");
150
151 let mut win = SpellWin {
152 adapter: None,
153 loop_handle: event_loop.handle(),
154 ipc_handler: None,
155 queue: qh.clone(),
156 buffer: None,
157 states: States {
158 registry_state: RegistryState::new(&globals),
159 seat_state: SeatState::new(&globals, &qh),
160 output_state: OutputState::new(&globals, &qh),
161 compositor_state: compositor,
162 pointer_state,
163 keyboard_state: None,
164 touch_state: None,
165 shm,
166 viewporter_state,
167 fractional_scale_state,
168 },
169 layer: None,
170 first_configure: Cell::new(true),
171 natural_scroll: window_conf.natural_scroll,
172 is_hidden: Cell::new(false),
173 config: window_conf.clone(),
174 layer_name: layer_name.clone(),
175 input_region,
176 opaque_region,
177 viewport: None,
178 xdg_shell,
179 popup_manager: window::popup::PopupManager::new(),
180 event_loop: Rc::new(RefCell::new(event_loop)),
181 span: span!(Level::INFO, "widget", name = layer_name.as_str(),),
182 };
183
184 if AVAILABLE_MONITORS.get().is_none() {
185 match SpellWin::get_available_monitors(&mut event_queue, &mut win) {
186 Some(monitors) => {
187 let _ = AVAILABLE_MONITORS.get_or_init(|| RwLock::new(monitors));
188 }
189 None => warn!("Failed to get available monitors"),
190 }
191 }
192
193 let mut output_info: Option<(wl_output::WlOutput, i32, i32)> =
194 if let Some(name) = &window_conf.monitor_name {
195 let output = AVAILABLE_MONITORS
196 .get()
197 .and_then(|monitors| monitors.read().ok())
198 .and_then(|monitors| monitors.get(name).cloned());
199 if output.is_none() {
200 warn!("Monitor '{}' not found, using default monitor", name);
201 }
202 output
203 } else {
204 None
205 };
206
207 match window_conf.width {
208 Dimension::Pixel(x) => window_conf.evaluated_width = x,
209 Dimension::Full => {
210 window_conf.evaluated_width = output_info
211 .as_ref()
212 .expect("Output info couldn't be retrieved")
213 .1 as u32
214 }
215 Dimension::Percentage(y) => {
216 window_conf.evaluated_width = output_info
217 .as_mut()
218 .expect("Output info couldn't be retrieved")
219 .1 as u32
220 / y;
221 }
222 }
223
224 match window_conf.height {
225 Dimension::Pixel(x) => window_conf.evaluated_height = x,
226 Dimension::Full => {
227 window_conf.evaluated_height = output_info
228 .as_ref()
229 .expect("Output info couldn't be retrieved")
230 .1 as u32
231 }
232 Dimension::Percentage(y) => {
233 window_conf.evaluated_height = output_info
234 .as_ref()
235 .expect("Output info couldn't be retrieved")
236 .1 as u32
237 / y;
238 }
239 }
240 win.config = window_conf.clone();
241
242 info!(
243 "Evaluated width: {}, evaluated_height: {}",
244 window_conf.evaluated_width, window_conf.evaluated_height
245 );
246
247 let mut pool = SlotPool::new(
248 (window_conf.evaluated_width * window_conf.evaluated_height * 4) as usize,
249 &win.states.shm,
250 )
251 .expect("Failed to create pool");
252 win.input_region.add(
253 0,
254 0,
255 window_conf.evaluated_width as i32,
256 window_conf.evaluated_height as i32,
257 );
258
259 let stride = window_conf.evaluated_width as i32 * 4;
260 let (way_pri_buffer, _) = pool
261 .create_buffer(
262 window_conf.evaluated_width as i32,
263 window_conf.evaluated_height as i32,
264 stride,
265 wl_shm::Format::Argb8888,
266 )
267 .expect("Creating Buffer");
268
269 let primary_slot = way_pri_buffer.slot();
270 let adapter_value: Rc<SpellSkiaWinAdapter> = SpellSkiaWinAdapter::new(
271 Rc::new(RefCell::new(pool)),
272 RefCell::new(primary_slot),
273 window_conf.evaluated_width,
274 window_conf.evaluated_height,
275 );
276 win.adapter = Some(adapter_value.clone());
278 win.buffer = Some(way_pri_buffer);
279
280 let (slint_event_sender, slint_event_receiver) =
281 calloop::channel::channel::<Box<dyn FnOnce() + Send>>();
282
283 ADAPTERS.with_borrow_mut(|v| v.push(adapter_value.clone()));
284 SET_SLINT_PLATFORM.call_once(|| {
285 trace!("Slint platform set");
286 if let Err(err) =
287 slint::platform::set_platform(Box::new(SpellLayerShell::new(slint_event_sender)))
288 {
289 warn!("Error setting slint platform: {err}");
290 }
291 });
292 win.adapter = Some(adapter_value);
293 let target_output: Option<&WlOutput> = output_info.as_ref().map(|(a, _, _)| a);
294 let layer = layer_shell.create_layer_surface(
295 &qh,
296 surface,
297 window_conf.layer_type,
298 Some(layer_name.clone()),
299 target_output,
300 );
301
302 win.layer = Some(layer);
303 win.set_config_internal();
304
305 if let Err(err) = event_queue.roundtrip(&mut win) {
306 warn!("Received roundtrip error: {}", err);
307 }
308 let surface: &WlSurface = win.layer.as_ref().unwrap().wl_surface();
309
310 let fractional_scale = win.states.fractional_scale_state.get_scale(surface, &qh);
313 let viewport = win
314 .states
315 .viewporter_state
316 .get_viewport(surface, &qh, fractional_scale);
317 win.viewport = Some(viewport);
318
319 win.layer.as_ref().unwrap().commit();
320 win.set_event_sources(handle, slint_event_receiver);
321
322 info!("Win: {} layer created successfully.", layer_name);
323
324 WaylandSource::new(conn.clone(), event_queue)
325 .insert(win.loop_handle.clone())
326 .unwrap();
327 win
328 }
329
330 pub fn get_handler(&self) -> WinHandle {
332 info!("Win: Handle provided.");
333 WinHandle(self.loop_handle.clone())
334 }
335
336 pub fn invoke_spell(name: &str, window_conf: WindowConf) -> Self {
344 let handle = set_up_tracing(name);
345 let conn = Connection::connect_to_env().unwrap();
346 SpellWin::create_window(&conn, window_conf.clone(), name.to_string(), handle)
347 }
348
349 pub fn hide(&self) {
351 if !self.is_hidden.replace(true) {
352 info!("Win: Hiding window");
353 self.layer.as_ref().unwrap().wl_surface().attach(None, 0, 0);
354 }
355 }
356
357 pub fn show_again(&self) {
359 if self.is_hidden.replace(false) {
360 info!("Win: Showing window again");
361 self.set_config_internal();
362 self.first_configure.set(true);
363 self.layer.as_ref().unwrap().commit();
364 }
365 }
366
367 pub fn toggle(&self) {
369 info!("Win: view toggled");
370 if self.is_hidden.get() {
371 self.show_again();
372 } else {
373 self.hide();
374 }
375 }
376
377 pub fn add_input_region(&self, x: i32, y: i32, width: i32, height: i32) {
383 info!(
384 "Win: input region added: [x: {}, y: {}, width: {}, height: {}]",
385 x, y, width, height
386 );
387 self.input_region.add(x, y, width, height);
388 self.set_config_internal();
389 self.layer.as_ref().unwrap().commit();
390 }
391
392 pub fn subtract_input_region(&self, x: i32, y: i32, width: i32, height: i32) {
397 info!(
398 "Win: input region removed: [x: {}, y: {}, width: {}, height: {}]",
399 x, y, width, height
400 );
401 self.input_region.subtract(x, y, width, height);
402 self.set_config_internal();
403 self.layer.as_ref().unwrap().commit();
404 }
405
406 pub fn add_opaque_region(&self, x: i32, y: i32, width: i32, height: i32) {
412 info!(
413 "Win: opaque region added: [x: {}, y: {}, width: {}, height: {}]",
414 x, y, width, height
415 );
416 self.opaque_region.add(x, y, width, height);
417 self.set_config_internal();
418 self.layer.as_ref().unwrap().commit();
419 }
420
421 pub fn subtract_opaque_region(&self, x: i32, y: i32, width: i32, height: i32) {
426 info!(
427 "Win: opaque region removed: [x: {}, y: {}, width: {}, height: {}]",
428 x, y, width, height
429 );
430 self.opaque_region.subtract(x, y, width, height);
431 self.set_config_internal();
432 self.layer.as_ref().unwrap().commit();
433 }
434
435 pub fn grab_focus(&self) {
438 if !self.is_hidden.get()
439 && self.config.board_interactivity.get() != KeyboardInteractivity::Exclusive
440 {
441 self.config
442 .board_interactivity
443 .set(KeyboardInteractivity::Exclusive);
444 self.layer
445 .as_ref()
446 .unwrap()
447 .set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
448 self.layer.as_ref().unwrap().commit();
449 }
450 }
451
452 pub fn remove_focus(&self) {
454 if !self.is_hidden.get()
455 && self.config.board_interactivity.get() != KeyboardInteractivity::None
456 {
457 self.config
458 .board_interactivity
459 .set(KeyboardInteractivity::None);
460 self.layer
461 .as_ref()
462 .unwrap()
463 .set_keyboard_interactivity(KeyboardInteractivity::None);
464 self.layer.as_ref().unwrap().commit();
465 }
466 }
467
468 pub fn set_exclusive_zone(&mut self, val: i32) {
472 self.config.exclusive_zone = Some(val);
473 self.layer.as_ref().unwrap().set_exclusive_zone(val);
474 self.layer.as_ref().unwrap().commit();
475 }
476
477 pub fn open_popup<T: PopupSlint + 'static>(
481 &mut self,
482 popup_conf: PopupConf,
483 ) -> Result<u32, Box<dyn std::error::Error>> {
484 if let Some(core) = self.create_popup_core(popup_conf) {
485 let popup = T::create_new(core);
486 let id = self.popup_manager.add_popup(popup);
487 info!("Popup created with id: {}", id);
488 Ok(id)
489 } else {
490 warn!("couldn't create a popup");
491 Err("Couldn't create Popup".into())
492 }
493 }
494
495 pub fn open_popup_with_instance<T: PopupSlint + 'static>(
497 &mut self,
498 popup_conf: PopupConf,
499 ) -> Result<T, Box<dyn std::error::Error>> {
500 if let Some(core) = self.create_popup_core(popup_conf) {
501 info!("Popup created without id");
502 Ok(T::create_new(core))
503 } else {
504 warn!("couldn't create a popup");
505 Err("Couldn't create Popup".into())
506 }
507 }
508
509 pub fn add_popup<T: PopupSlint + 'static>(&mut self, popup_instance: T) -> u32 {
511 self.popup_manager.add_popup(popup_instance)
512 }
513
514 pub fn close_popup(&mut self, id: u32) {
516 self.popup_manager.close_popup(&id);
517 }
518}
519
520delegate_compositor!(SpellWin);
521delegate_xdg_shell!(SpellWin);
522delegate_xdg_popup!(SpellWin);
523delegate_registry!(SpellWin);
524delegate_output!(SpellWin);
525delegate_shm!(SpellWin);
526delegate_seat!(SpellWin);
527delegate_keyboard!(SpellWin);
528delegate_pointer!(SpellWin);
529delegate_touch!(SpellWin);
530delegate_layer!(SpellWin);
531delegate_fractional_scale!(SpellWin);
532delegate_viewporter!(SpellWin);
533
534impl SpellAssociatedNew for SpellWin {
535 fn on_call(&mut self) -> Result<(), Box<dyn std::error::Error>> {
536 let event_loop = self.event_loop.clone();
537 event_loop
538 .borrow_mut()
539 .dispatch(std::time::Duration::from_millis(1), self)?;
540 Ok(())
541 }
542
543 fn get_span(&self) -> tracing::span::Span {
544 self.span.clone()
545 }
546}
547
548#[derive(Clone, Debug)]
552pub struct WinHandle(pub LoopHandle<'static, SpellWin>);
553
554impl WinHandle {
555 pub fn hide(&self) {
557 self.0.insert_idle(|win| win.hide());
558 }
559
560 pub fn show_again(&self) {
562 self.0.insert_idle(|win| win.show_again());
563 }
564
565 pub fn toggle(&self) {
567 self.0.insert_idle(|win| win.toggle());
568 }
569
570 pub fn grab_focus(&self) {
572 self.0.insert_idle(|win| win.grab_focus());
573 }
574
575 pub fn remove_focus(&self) {
577 self.0.insert_idle(|win| win.remove_focus());
578 }
579
580 pub fn add_input_region(&self, x: i32, y: i32, width: i32, height: i32) {
582 self.0
583 .insert_idle(move |win| win.add_input_region(x, y, width, height));
584 }
585
586 pub fn subtract_input_region(&self, x: i32, y: i32, width: i32, height: i32) {
588 self.0
589 .insert_idle(move |win| win.subtract_input_region(x, y, width, height));
590 }
591
592 pub fn add_opaque_region(&self, x: i32, y: i32, width: i32, height: i32) {
594 self.0
595 .insert_idle(move |win| win.add_opaque_region(x, y, width, height));
596 }
597
598 pub fn subtract_opaque_region(&self, x: i32, y: i32, width: i32, height: i32) {
600 self.0
601 .insert_idle(move |win| win.subtract_opaque_region(x, y, width, height));
602 }
603
604 pub fn set_exclusive_zone(&self, val: i32) {
606 self.0.insert_idle(move |win| win.set_exclusive_zone(val));
607 }
608
609 pub fn open_popup<T: PopupSlint + 'static>(
614 &mut self,
615 popup_conf: PopupConf,
616 callback: Box<dyn FnOnce(u32)>,
617 ) -> Result<u32, Box<dyn std::error::Error>> {
618 self.0.insert_idle(|win| {
619 if let Ok(id) = win.open_popup::<T>(popup_conf) {
620 callback(id);
621 }
622 });
623 Ok(0)
624 }
625
626 pub fn close_popup(&self, id: u32) {
628 self.0.insert_idle(move |win| {
629 win.close_popup(id);
630 });
631 }
632}