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 The project should have a live preview feature. It can be made by leveraging
265// slint's preview and moving the output of debug to spell_cli.
266// TODO linux's DNF Buffers needs to be used to improve rendering and avoid conversions
267// from CPU to GPU and vice versa.
268// TODO needs to have multi monitor support.
269// TO REMEMBER I removed dirty region from spellskiawinadapter but it can be added
270// if I want to make use of the dirty region information to strengthen my rendering.
271// TODO to check what will happen to my dbus network if windows with same layer name will be
272// present. To check causes for errors as well as before implenenting muliple layers in same
273// window.
274// TODO lock screen behaviour in a multi-monitor setup needs to be tested.
275// TODO merge cast_Spell with run_lock after implementing calloop in normal windows.
276// TODO t add tracing in following functions:
277// 1. secondary and primary services
278// TODO implement logiing for SpellLock.
279// TODO check if the dbus setup is working for more than 2 widgets when one is
280// primary and 2 are secondary.
281// Provide a method in the macro to disable tracing_subsriber completely for some project
282// which want's to do it themselves.