spell_framework/
lib.rs

1#![doc(
2    html_logo_url = "https://raw.githubusercontent.com/VimYoung/Spell/main/spell-framework/assets/spell_trans.png"
3)]
4#![doc(
5    html_favicon_url = "https://raw.githubusercontent.com/VimYoung/Spell/main/spell-framework/assets/spell_trans.ico"
6)]
7// #![doc(html_favicon_url = "https://github.com/VimYoung/Spell/blob/bb01ae94a365d237ebb0db1df1b6eb37aea25367/spell-framework/assets/Spell.png")]
8#![doc = include_str!("../docs/entry.md")]
9#[warn(missing_docs)]
10mod configure;
11mod dbus_window_state;
12#[cfg(docsrs)]
13mod dummy_skia_docs;
14pub mod forge;
15#[cfg(feature = "i-slint-renderer-skia")]
16// #[cfg(feature = "pam-client2")]
17#[cfg(not(docsrs))]
18#[doc(hidden)]
19mod skia_non_docs;
20pub mod slint_adapter;
21pub mod vault;
22pub mod wayland_adapter;
23/// It contains related enums and struct which are used to manage,
24/// define and update various properties of a widget(viz a viz layer). You can import necessary
25/// types from this module to implement relevant features. See docs of related objects for
26/// their overview.
27pub mod layer_properties {
28    pub use crate::{configure::WindowConf, dbus_window_state::DataType};
29    pub use smithay_client_toolkit::shell::wlr_layer::Anchor as LayerAnchor;
30    pub use smithay_client_toolkit::shell::wlr_layer::KeyboardInteractivity as BoardType;
31    pub use smithay_client_toolkit::shell::wlr_layer::Layer as LayerType;
32}
33use dbus_window_state::{DataType, InternalHandle, deploy_zbus_service};
34use smithay_client_toolkit::reexports::calloop::channel::{Channel, Event, channel};
35use std::{
36    any::Any,
37    error::Error,
38    sync::{Arc, RwLock},
39    time::Duration,
40};
41
42use tracing::{Level, error, info, instrument, span, trace, warn};
43use wayland_adapter::SpellWin;
44use zbus::Error as BusError;
45
46/// This a boilerplate trait for connection with CLI, it will be replaced by a procedural
47/// macro in the future.
48/// In the mean time, this function is implemented on a struct you would define in
49/// your `.slint` file. Then state of widgets should be stored as single property
50/// of that data type rather than on individual values.
51///
52/// ## Example
53///
54/// ```slint
55/// // Wrong Method is you want to handle on-close and is-expanded locally.
56/// export component MyWindow inherits Window {
57///   in-out property <bool> on-close: false;
58///   in-out property <bool> is-expanded: true;
59///   Rectangle {
60///      // Other widgets will come here.
61///   }
62/// }
63///
64/// // Correct method
65/// export component MyWindow inherits Window {
66///   in-out property <MyWinState> state: {on-close: false, is-expanded: true};
67///   Rectangle {
68///      // Other widgets will come here.
69///   }
70/// }
71/// export struct MyWinState {
72///   on-close: bool,
73///   is-expanded: true,
74/// }
75/// ```
76pub trait ForeignController: Send + Sync + std::fmt::Debug {
77    /// On calling `spell-cli -l layer_name look
78    /// var_name`, the cli calls `get_type` method of the trait with `var_name` as input.
79    fn get_type(&self, key: &str) -> DataType;
80    /// It is called on `spell-cli -l layer_name update key value`. `as_any` is for syncing the changes
81    /// internally for now and need not be implemented by the end user.
82    fn change_val(&mut self, key: &str, val: DataType);
83    /// It is a type needed internally, it's implementation should return `self` to
84    /// avoid undefined behaviour.
85    fn as_any(&self) -> &dyn Any;
86}
87
88type State = Arc<RwLock<dyn ForeignController>>;
89type States = Vec<Option<Box<dyn FnMut(State)>>>;
90/// This is the event loop which is to be called when initialising multiple windows through
91/// a single `main` file. It is important to remember that Each value of these vectors corresponds
92/// to the number on which a widget is initialised. So, this function will panic if the length of
93/// vectors of various types mentioned here are not equal.For more information on checking the
94/// arguments, view [cast_spell].
95pub fn enchant_spells(
96    mut waywindows: Vec<SpellWin>,
97    states: Vec<Option<State>>,
98    mut set_callbacks: States,
99) -> Result<(), Box<dyn Error>> {
100    if waywindows.len() == states.len() && waywindows.len() == set_callbacks.len() {
101        info!("Starting windows");
102        let spans: Vec<span::Span> = waywindows.iter().map(|win| win.span.clone()).collect();
103        let mut internal_recievers: Vec<Channel<InternalHandle>> = Vec::new();
104        states.iter().enumerate().for_each(|(index, state)| {
105            internal_recievers.push(helper_fn_for_deploy(
106                waywindows[index].layer_name.clone(),
107                state,
108                waywindows[index].span.clone(),
109            ));
110            trace!("{:?}", &waywindows[index]);
111        });
112        trace!("Grabbed Internal recievers");
113        states.into_iter().enumerate().for_each(|(index, state)| {
114            let _guard = spans[index].enter();
115            let set_call = set_callbacks.remove(0);
116            if let Some(mut callback) = set_call {
117                let event_loop = waywindows[index].event_loop.clone();
118                event_loop
119                    .borrow()
120                    .handle()
121                    .insert_source(
122                        internal_recievers.remove(0),
123                        move |event_msg, _, state_data| {
124                            match event_msg {
125                                Event::Msg(int_handle) => {
126                                    match int_handle {
127                                        InternalHandle::StateValChange((key, data_type)) => {
128                                            trace!("Internal variable change called");
129                                            //Glad I could think of this sub scope for RwLock.
130                                            {
131                                                let mut state_inst =
132                                                    state.as_ref().unwrap().write().unwrap();
133                                                state_inst.change_val(&key, data_type);
134                                            }
135                                            callback(state.as_ref().unwrap().clone());
136                                        }
137                                        InternalHandle::ShowWinAgain => {
138                                            trace!("Internal show Called");
139                                            state_data.show_again();
140                                        }
141                                        InternalHandle::HideWindow => {
142                                            trace!("Internal hide called");
143                                            state_data.hide();
144                                        }
145                                    }
146                                }
147                                // TODO have to handle it properly.
148                                Event::Closed => {
149                                    info!("Internal Channel closed");
150                                }
151                            }
152                        },
153                    )
154                    .unwrap();
155            }
156        });
157        trace!("Setting internal handles as events and calling event loop.");
158
159        loop {
160            for (index, waywindow) in waywindows.iter_mut().enumerate() {
161                spans[index].in_scope(|| -> Result<(), Box<dyn Error>> {
162                    let event_loop = waywindow.event_loop.clone();
163                    event_loop
164                        .borrow_mut()
165                        .dispatch(Duration::from_millis(1), waywindow)?;
166                    Ok(())
167                })?;
168            }
169        }
170    } else {
171        error!("Lengths are unequal");
172        panic!(
173            "The lengths of given vectors are not equal. \n Make sure that given vector lengths are equal"
174        );
175    }
176}
177
178/// This is the primary function used for starting the event loop after creating the widgets,
179/// setting values and initialising windows. Example of the use can be found [here](https://github.com/VimYoung/Young-Shell/tree/main/src/bin).
180/// The function takes in the following function arguments:-
181/// 1. Wayland side of widget corresponding to it's slint window.
182/// 2. A instance of struct implementing [ForeignController]. This will be wrapped in `Arc` and
183///    `RwLock` as it would be used across threads internally, if the widget is static in nature
184///    and doesn't need state that needs to be changed remotely via CLI. You can parse in None.
185/// 3. A callback which is called when a CLI command is invoked changing the value. The closure
186///    gets an updated value of your state struct. The common method is to take the updated value
187///    and replace your existing state with it to reflect back the changes in the slint code. If
188///    state is provided, then it is important for now to pass a callback corresponding to it too.
189///    You can use this callback for example.
190/// ```rust
191/// move |state_value| {
192///     let controller_val = state_value.read().unwrap();
193///     let val = controller_val
194///         .as_any()
195///         .downcast_ref::<State>()
196///         .unwrap()
197///         .clone();
198///     ui_clone.unwrap().set_state(val);
199/// }
200/// // here `ui_clone` is weak pointer to my slint window for setting back the `state` property.
201/// ```
202pub fn cast_spell<S: SpellAssociated + std::fmt::Debug>(
203    mut waywindow: S,
204    state: Option<State>,
205    set_callback: Option<Box<dyn FnMut(State)>>,
206) -> Result<(), Box<dyn Error>> {
207    let span = waywindow.get_span();
208    let s = span.clone();
209    span.in_scope(|| {
210        trace!("{:?}", &waywindow);
211        waywindow.on_call(state, set_callback, s)
212    })
213}
214
215#[instrument(skip(state))]
216fn helper_fn_for_deploy(
217    layer_name: String,
218    state: &Option<State>,
219    span_log: span::Span,
220) -> Channel<InternalHandle> {
221    let (tx, rx) = channel::<InternalHandle>();
222    if let Some(some_state) = state {
223        let state_clone = some_state.clone();
224        std::thread::spawn(move || {
225            span_log.in_scope(move || {
226                let span_bus = span!(Level::INFO, "Zbus Logs",);
227                let _guard = span_bus.enter();
228                let rt = tokio::runtime::Builder::new_current_thread()
229                    .enable_all()
230                    .build()
231                    .unwrap();
232                if let Err(error) = rt.block_on(async move {
233                    trace!("Started Zbus service in a thread");
234                    deploy_zbus_service(state_clone, tx, layer_name).await?;
235                    Ok::<_, BusError>(())
236                }) {
237                    error!("Zbus panicked with following error: {}", error);
238                    Err(error)
239                } else {
240                    Ok(())
241                }
242            })
243        });
244    }
245    rx
246}
247
248/// Internal function for running event loops, implemented by [SpellWin] and
249/// [SpellLock][`crate::wayland_adapter::SpellLock`].
250pub trait SpellAssociated {
251    fn on_call(
252        &mut self,
253        state: Option<State>,
254        set_callback: Option<Box<dyn FnMut(State)>>,
255        span_log: tracing::span::Span,
256    ) -> Result<(), Box<dyn Error>>;
257
258    fn get_span(&self) -> span::Span;
259}
260
261// TODO set logging values in Option so that only a single value reads or writes.
262// TODO it is necessary to call join unwrap on spawned threads to ensure
263// that they are closed when main thread closes.
264// TODO linux's DNF Buffers needs to be used to improve rendering and avoid conversions
265// from CPU to GPU and vice versa.
266// TODO needs to have multi monitor support.
267// TO REMEMBER I removed dirty region from spellskiawinadapter but it can be added
268// if I want to make use of the dirty region information to strengthen my rendering.
269// TODO to check what will happen to my dbus network if windows with same layer name will be
270// present. To check causes for errors as well as before implenenting muliple layers in same
271// window.
272// TODO lock screen behaviour in a multi-monitor setup needs to be tested.
273// TODO t add tracing in following functions:
274// 1. secondary and primary services
275// TODO implement logiing for SpellLock.
276// TODO check if the dbus setup is working for more than 2 widgets when one is
277// primary and 2 are secondary.
278// Provide a method in the macro to disable tracing_subsriber completely for some project
279// which want's to do it themselves.
280// cast spell macro should be having following values.
281// 1. Disable log: should disable setting subscriber, generally for the project to use or for
282// someone to set their own.
283// 2. forge: provide a forge instance to run independently.
284// 3. exclusive_zone: true or false or with specified value.
285// 4. it should have the option to take a window_conf or directly the window configurations
286// into the macro, removing the need to define it previously.
287// 5. monitor: Specify the monitor to show the widget in.
288//
289// Also, a procedural macro to mimic the functionalities of ForeignController.