Skip to main content

spell_framework/wayland_adapter/
window.rs

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
80/// `SpellWin` is the main type for implementing widgets, it covers various properties
81/// and trait implementation, thus providing various features.
82pub struct SpellWin {
83    adapter: Option<Rc<SpellSkiaWinAdapter>>,
84    loop_handle: LoopHandle<'static, SpellWin>,
85    /// UnixListener storing remote instructions from CLI.
86    pub ipc_handler: Option<UnixListener>,
87    /// Name of widget's layer.
88    pub layer_name: String,
89    /// Span required for proper logging.
90    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.popup_manager.set_pool(pool_mut.clone());
277        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        // This needs to occur after layer creation so as to ensure that layer
311        // used in window is not null during use to scale. Details in issue 34.
312        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    /// Returns a handle of [`WinHandle`] to invoke wayland specific features.
331    pub fn get_handler(&self) -> WinHandle {
332        info!("Win: Handle provided.");
333        WinHandle(self.loop_handle.clone())
334    }
335
336    /// This function is called to create a instance of window. This window is then
337    /// finally called by [`cast_spell`](crate::cast_spell) event loop.
338    ///
339    /// # Panics
340    ///
341    /// This function needs to be called "before" initialising your slint window to avoid
342    /// panicing of this function.
343    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    /// Hides the layer (aka the widget) if it is visible in screen.
350    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    /// Brings back the layer (aka the widget) back on screen if it is hidden.
358    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    /// Hides the widget if visible or shows the widget back if hidden.
368    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    /// This function adds specific rectangular regions of your complete layer to receive
378    /// input events from pointer and/or touch. The coordinates are in surface local
379    /// format from top left corener. By default, The whole layer is considered for input
380    /// events. Adding existing areas again as input region has no effect. This function
381    /// combined with transparent base widgets can be used to mimic resizable widgets.
382    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    /// This function subtracts specific rectangular regions of your complete layer from receiving
393    /// input events from pointer and/or touch. The coordinates are in surface local
394    /// format from top left corener. By default, The whole layer is considered for input
395    /// events. Substracting input areas which are already not input regions has no effect.
396    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    /// This function marks specific rectangular regions of your complete layer as opaque.
407    /// This can result in specific optimisations from your wayland compositor, setting
408    /// this property is optional. The coordinates are in surface local format from top
409    /// left corener. Not adding opaque regions in it has no isuues but adding transparent
410    /// regions of layer as opaque can cause weird behaviour and glitches.
411    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    /// This function removes specific rectangular regions of your complete layer from being opaque.
422    /// This can result in specific optimisations from your wayland compositor, setting
423    /// this property is optional. The coordinates are in surface local format from top
424    /// left corener.
425    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    /// Grabs the focus of keyboard. Can be used in combination with other functions
436    /// to make the widgets keyboard navigable.
437    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    /// Removes the focus of keyboard from window if it currently has it.
453    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    /// This method is used to set exclusive zone. Generally, useful when
469    /// dimensions of width are different than exclusive zone you want.
470    // self.set_config_internal();
471    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    /// Opens a popup given the [`PopupConf`]. It returns the ID of the popup if
478    /// created successfully. The method fails if the concerned compositor fails
479    /// to create a popup instance or doesn't support the protocol.
480    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    /// WIP method not to be used.
496    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    /// WIP method not to be used.
510    pub fn add_popup<T: PopupSlint + 'static>(&mut self, popup_instance: T) -> u32 {
511        self.popup_manager.add_popup(popup_instance)
512    }
513
514    /// Closes a popup given its ID.
515    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/// This is a wrapper around calloop's [loop_handle](https://docs.rs/calloop/latest/calloop/struct.LoopHandle.html)
549/// for calling wayland specific features of `SpellWin`. It can be accessed from
550/// [`crate::wayland_adapter::SpellWin::get_handler`].
551#[derive(Clone, Debug)]
552pub struct WinHandle(pub LoopHandle<'static, SpellWin>);
553
554impl WinHandle {
555    /// Internally calls [`crate::wayland_adapter::SpellWin::hide`]
556    pub fn hide(&self) {
557        self.0.insert_idle(|win| win.hide());
558    }
559
560    /// Internally calls [`crate::wayland_adapter::SpellWin::show_again`]
561    pub fn show_again(&self) {
562        self.0.insert_idle(|win| win.show_again());
563    }
564
565    /// Internally calls [`crate::wayland_adapter::SpellWin::toggle`]
566    pub fn toggle(&self) {
567        self.0.insert_idle(|win| win.toggle());
568    }
569
570    /// Internally calls [`crate::wayland_adapter::SpellWin::grab_focus`]
571    pub fn grab_focus(&self) {
572        self.0.insert_idle(|win| win.grab_focus());
573    }
574
575    /// Internally calls [`crate::wayland_adapter::SpellWin::remove_focus`]
576    pub fn remove_focus(&self) {
577        self.0.insert_idle(|win| win.remove_focus());
578    }
579
580    /// Internally calls [`crate::wayland_adapter::SpellWin::add_input_region`]
581    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    /// Internally calls [`crate::wayland_adapter::SpellWin::subtract_input_region`]
587    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    /// Internally calls [`crate::wayland_adapter::SpellWin::add_opaque_region`]
593    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    /// Internally calls [`crate::wayland_adapter::SpellWin::subtract_opaque_region`]
599    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    /// Internally calls [`crate::wayland_adapter::SpellWin::set_exclusive_zone`]
605    pub fn set_exclusive_zone(&self, val: i32) {
606        self.0.insert_idle(move |win| win.set_exclusive_zone(val));
607    }
608
609    /// Internally calls [`crate::wayland_adapter::SpellWin::open_popup`]. Since,
610    /// the handler can't be tuned to return anything(in this case the id), a callback
611    /// is instead taken with ID as input, this is called after receiving the ID.
612    /// It can be used to used to save the ID and perform actions with it.
613    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    /// Internally calls [`crate::wayland_adapter::SpellWin::close_popup`].
627    pub fn close_popup(&self, id: u32) {
628        self.0.insert_idle(move |win| {
629            win.close_popup(id);
630        });
631    }
632}