Skip to main content

spell_framework/
slint_adapter.rs

1//! This module contains relevent structs for slint side backend configurations.
2//! All structs mentioned are either internal or not used anymore. Still their
3//! implementation is public because they had to be set by the user of library
4//! in intial iterations of spell_framework.
5use crate::configure::LayerConf;
6use slint::platform::{EventLoopProxy, Platform, WindowAdapter};
7use smithay_client_toolkit::reexports::calloop;
8use std::{cell::RefCell, io::Read, rc::Rc};
9use tracing::{Level, info, span, warn};
10use wl_clipboard_rs::{
11    copy::{MimeType as CopyMimeType, Options, Source},
12    paste::{ClipboardType, Error, MimeType as PasteMimeType, Seat, get_contents},
13};
14
15thread_local! {
16    pub(crate) static ADAPTERS: RefCell<Vec<Rc<SpellSkiaWinAdapter>>> = const { RefCell::new(Vec::new()) };
17}
18
19#[cfg(not(docsrs))]
20#[cfg(feature = "i-slint-renderer-skia")]
21use crate::skia_non_docs::SpellSkiaWinAdapterReal;
22
23/// It is the main struct handling the rendering of pixels in the wayland window. It implements slint's
24/// [WindowAdapter](https://docs.rs/slint/latest/slint/platform/trait.WindowAdapter.html) trait.
25/// It is used internally by [SpellMultiWinHandler] and previously by [SpellLayerShell]. This
26/// adapter internally uses [Skia](https://skia.org/) 2D graphics library for rendering.
27#[cfg(not(docsrs))]
28#[cfg(feature = "i-slint-renderer-skia")]
29pub type SpellSkiaWinAdapter = SpellSkiaWinAdapterReal;
30
31#[cfg(docsrs)]
32use crate::dummy_skia_docs::SpellSkiaWinAdapterDummy;
33
34/// It is the main struct handling the rendering of pixels in the wayland window. It implements slint's
35/// [WindowAdapter](https://docs.rs/slint/latest/slint/platform/trait.WindowAdapter.html) trait.
36/// It is used internally by [SpellMultiWinHandler] and previously by [SpellLayerShell]. This
37/// adapter internally uses [Skia](https://skia.org/) 2D graphics library for rendering.
38#[cfg(docsrs)]
39pub type SpellSkiaWinAdapter = SpellSkiaWinAdapterDummy;
40
41/// Previously needed to be implemented, now this struct is called and set internally
42/// when [`invoke_spell`](crate::wayland_adapter::SpellWin::invoke_spell) is called.
43pub struct SpellLayerShell {
44    /// Span storing the logging context for `debug`` statements of slint.
45    pub span: span::Span,
46    slint_event_sender: calloop::channel::Sender<Box<dyn FnOnce() + Send>>,
47}
48
49impl SpellLayerShell {
50    /// Creates an instance of this Platform implementation, for internal use.
51    pub(crate) fn new(
52        slint_event_sender: calloop::channel::Sender<Box<dyn FnOnce() + Send>>,
53    ) -> Self {
54        Self {
55            span: span!(Level::INFO, "slint-log",),
56            slint_event_sender,
57        }
58    }
59}
60
61// impl Default for SpellLayerShell {
62//     /// Creates an instance of this Platform implementation, for internal use.
63//     fn default() -> Self {
64//         SpellLayerShell {
65//             span: span!(Level::INFO, "slint-log",),
66//         }
67//     }
68// }
69
70impl Platform for SpellLayerShell {
71    fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, slint::PlatformError> {
72        let adapter = ADAPTERS.with(|v| v.borrow().last().unwrap().clone());
73        Ok(adapter)
74    }
75
76    fn debug_log(&self, arguments: core::fmt::Arguments) {
77        self.span.in_scope(|| {
78            if let Some(val) = arguments.as_str() {
79                info!(val);
80            } else {
81                info!("{}", arguments.to_string());
82            }
83        })
84    }
85
86    fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
87        Some(Box::new(SlintEventProxy(self.slint_event_sender.clone())))
88    }
89
90    // FIXME: this implementation should be by smithay-clipboard.
91    // fix after state management moved from window to platform.
92    fn set_clipboard_text(&self, text: &str, _clipboard: slint::platform::Clipboard) {
93        // if let DefaultClipboard = clipboard {
94        let opts = Options::new();
95        if let Err(err) = opts.copy(
96            Source::Bytes(text.to_string().into_bytes().into()),
97            CopyMimeType::Autodetect,
98        ) {
99            warn!("[Clipboard]: Error in setting clipboard value: {}", err);
100        } else {
101            info!("[Clipboard]: Successfully copied text");
102        }
103        //}
104    }
105
106    // FIXME: this implementation should be by smithay-clipboard.
107    // fix after state management moved from window to platform.
108    fn clipboard_text(&self, _clipboard: slint::platform::Clipboard) -> Option<String> {
109        let result = get_contents(
110            ClipboardType::Regular,
111            Seat::Unspecified,
112            PasteMimeType::Text,
113        );
114        match result {
115            Ok((mut pipe, _)) => {
116                let mut contents = vec![];
117                // TODO: handle the below unwrap properly.
118                pipe.read_to_end(&mut contents).unwrap();
119                let text = String::from_utf8_lossy(&contents).to_string();
120                info!("[Clipboard]: Successfully pasted text: {}", text);
121                Some(text)
122            }
123
124            // In this cases, an empty string is returned.
125            Err(Error::NoSeats) | Err(Error::ClipboardEmpty) | Err(Error::NoMimeType) => {
126                warn!("[Clipboard]: Clipboard was either empty or didn't have text type data");
127                Some("".to_string())
128            }
129
130            Err(err) => {
131                warn!("[Clipboard]: error getting clipboard text: {}", err);
132                None
133            }
134        }
135    }
136}
137
138/// This struct is responsible for handling, initialising, updating and maintaining
139/// of various widgets that are being rendered simultaneously across monitors for
140/// your lock. It uses [SpellSkiaWinAdapter] internally. This struct is made public
141/// for documentation purposes (and was previously used by end user of library) but
142/// it is now not to be used directly.
143pub struct SpellMultiWinHandler {
144    pub(crate) windows: Vec<(String, LayerConf)>,
145    pub(crate) adapter: Vec<Rc<SpellSkiaWinAdapter>>,
146    pub(crate) value_given: u32,
147}
148
149impl SpellMultiWinHandler {
150    pub(crate) fn new_lock(lock_outputs: Vec<(String, (u32, u32))>) -> Rc<RefCell<Self>> {
151        let new_locks: Vec<(String, LayerConf)> = lock_outputs
152            .iter()
153            .map(|(output_name, conf)| (output_name.clone(), LayerConf::Lock(conf.0, conf.1)))
154            .collect();
155
156        Rc::new(RefCell::new(SpellMultiWinHandler {
157            windows: new_locks,
158            adapter: Vec::new(),
159            value_given: 0,
160        }))
161    }
162
163    fn request_new_lock(&mut self) -> Rc<dyn WindowAdapter> {
164        self.value_given += 1;
165        let index = self.value_given - 1;
166        self.adapter[index as usize].clone()
167    }
168}
169
170/// Slint Platform implementation for lock screens. This struct is used internally
171/// and it is provided here just for reference.
172pub struct SpellLockShell {
173    /// An instance of [SpellMultiWinHandler].
174    pub window_manager: Rc<RefCell<SpellMultiWinHandler>>,
175    /// Channel to allow executing functions in the slint event loop immediately
176    pub slint_event_sender: calloop::channel::Sender<Box<dyn FnOnce() + Send>>,
177    /// Span storing the logging context for `debug`` statements of slint for
178    /// lock screens.
179    pub span: span::Span,
180}
181
182impl SpellLockShell {
183    /// Internal function that creates an instance of layer implementation given
184    /// [`SpellMultiWinHandler`] wrapped in smart pointers.
185    pub fn new(
186        window_manager: Rc<RefCell<SpellMultiWinHandler>>,
187        slint_event_sender: calloop::channel::Sender<Box<dyn FnOnce() + Send>>,
188    ) -> Self {
189        SpellLockShell {
190            slint_event_sender,
191            window_manager,
192            span: span!(Level::INFO, "slint-lock-log",),
193        }
194    }
195}
196
197impl Platform for SpellLockShell {
198    fn create_window_adapter(&self) -> Result<Rc<dyn WindowAdapter>, slint::PlatformError> {
199        let value = self.window_manager.borrow_mut().request_new_lock();
200        Ok(value)
201    }
202
203    fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
204        Some(Box::new(SlintEventProxy(self.slint_event_sender.clone())))
205    }
206
207    fn debug_log(&self, arguments: core::fmt::Arguments) {
208        self.span.in_scope(|| {
209            if let Some(val) = arguments.as_str() {
210                info!(val);
211            } else {
212                info!("{}", arguments.to_string());
213            }
214        })
215    }
216}
217
218struct SlintEventProxy(calloop::channel::Sender<Box<dyn FnOnce() + Send>>);
219
220impl EventLoopProxy for SlintEventProxy {
221    fn quit_event_loop(&self) -> Result<(), i_slint_core::api::EventLoopError> {
222        Ok(())
223    }
224
225    fn invoke_from_event_loop(
226        &self,
227        event: Box<dyn FnOnce() + Send>,
228    ) -> Result<(), i_slint_core::api::EventLoopError> {
229        self.0
230            .send(event)
231            .map_err(|_| i_slint_core::api::EventLoopError::EventLoopTerminated)
232    }
233}