1use std::{
2 any::Any,
3 collections::HashMap,
4 sync::{Mutex, OnceLock},
5};
6
7use abi_stable::{
8 sabi_trait::TD_Opaque,
9 std_types::{RBox, ROption, RResult, RStr, RString, RVec},
10};
11
12use crate::{
13 Args,
14 host::{Host, Host_TO},
15 payload::{PayloadBox, PayloadParser, PayloadParserBox},
16 surfaces::{
17 Component, Compositor, ConfigParser, ConfigParserBox, DeployableDesktopItem, DesktopItem,
18 ForegroundService, Payload, QuickSettings, Renderable, Service, Spotlight,
19 foreground_service_factory, service_factory,
20 },
21};
22
23pub use kdl;
24
25type ConfigValue = Box<dyn Any + Send + Sync>;
26
27pub(crate) fn config_values() -> &'static Mutex<HashMap<String, ConfigValue>> {
28 static VALUES: OnceLock<Mutex<HashMap<String, ConfigValue>>> = OnceLock::new();
29 VALUES.get_or_init(|| Mutex::new(HashMap::new()))
30}
31
32pub fn store_config_value<T: Send + Sync + 'static>(key: &str, value: T) {
33 config_values()
34 .lock()
35 .unwrap()
36 .insert(key.to_owned(), Box::new(value));
37}
38
39struct ParserFn<F> {
40 parse: F,
41}
42
43impl<F> ConfigParser for ParserFn<F>
44where
45 F: Fn(Option<&kdl::KdlNode>) -> Result<(), String> + Send + Sync + 'static,
46{
47 fn parse(&self, block: ROption<RStr<'_>>) -> RResult<(), RString> {
48 let node = match block {
49 ROption::RSome(text) => text
50 .parse::<kdl::KdlDocument>()
51 .ok()
52 .and_then(|document| document.nodes().first().cloned()),
53 ROption::RNone => None,
54 };
55
56 match (self.parse)(node.as_ref()) {
57 Ok(()) => RResult::ROk(()),
58 Err(message) => RResult::RErr(RString::from(message)),
59 }
60 }
61}
62
63pub fn config_parser<T: Send + Sync + 'static>(
64 name: &str,
65 parse: impl Fn(Option<&kdl::KdlNode>) -> T + Send + Sync + 'static,
66) -> ConfigParserBox {
67 let key = name.to_owned();
68 ConfigParserBox::from_value(
69 ParserFn {
70 parse: move |node: Option<&kdl::KdlNode>| {
71 let value = parse(node);
72 store_config_value(&key, value);
73 Ok(())
74 },
75 },
76 TD_Opaque,
77 )
78}
79
80pub fn config_parser_try<T: Send + Sync + 'static>(
81 name: &str,
82 parse: impl Fn(Option<&kdl::KdlNode>) -> Result<T, String> + Send + Sync + 'static,
83) -> ConfigParserBox {
84 let key = name.to_owned();
85 ConfigParserBox::from_value(
86 ParserFn {
87 parse: move |node: Option<&kdl::KdlNode>| {
88 let value = parse(node)?;
89 store_config_value(&key, value);
90 Ok(())
91 },
92 },
93 TD_Opaque,
94 )
95}
96
97struct PayloadParserFn<F> {
98 parse: F,
99}
100
101impl<F> PayloadParser for PayloadParserFn<F>
102where
103 F: Fn(&str, &Args) -> Option<PayloadBox> + Send + Sync + 'static,
104{
105 fn parse(&self, command: RStr<'_>, args: &Args) -> ROption<PayloadBox> {
106 (self.parse)(&command, args).into()
107 }
108}
109
110pub fn payload_parser<T: Clone + Send + Sync + 'static>(
111 parse: impl Fn(&str, &Args) -> Option<T> + Send + Sync + 'static,
112) -> PayloadParserBox {
113 PayloadParserBox::from_value(
114 PayloadParserFn {
115 parse: move |command: &str, args: &Args| parse(command, args).map(PayloadBox::new),
116 },
117 TD_Opaque,
118 )
119}
120
121pub trait HostExt {
122 fn component<T: Component>(&self, name: &str);
123 fn renderable<T: Renderable>(&self, name: &str);
124 fn payload<T: Payload>(&self, command: &str);
125 fn desktop_item<T: DesktopItem>(&self, name: &str);
126 fn desktop_item_deployer<T: DeployableDesktopItem>(&self, name: &str);
127 fn compositor<T: Compositor>(&self, name: &str);
128 fn spotlight<T: Spotlight>(&self, name: &str);
129 fn quick_settings<T: QuickSettings>(&self, name: &str);
130 fn config_parser<T: Send + Sync + 'static>(
131 &self,
132 name: &str,
133 parse: impl Fn(Option<&kdl::KdlNode>) -> T + Send + Sync + 'static,
134 );
135 fn config_parser_try<T: Send + Sync + 'static>(
136 &self,
137 name: &str,
138 parse: impl Fn(Option<&kdl::KdlNode>) -> Result<T, String> + Send + Sync + 'static,
139 );
140 fn service<T: Service>(&self, name: &str);
141 fn service_autostart<T: Service>(&self, name: &str);
142 fn foreground_service<T: ForegroundService>(&self, name: &str);
143 fn payload_parser<T: Clone + Send + Sync + 'static>(
144 &self,
145 commands: &[&str],
146 parse: impl Fn(&str, &Args) -> Option<T> + Send + Sync + 'static,
147 );
148 fn command(&self, name: &str, title: &str);
149 fn command_with(&self, name: &str, title: &str, description: &str);
150 fn style(&self, css: &str);
151}
152
153impl HostExt for Host_TO<RBox<()>> {
154 fn component<T: Component>(&self, name: &str) {
155 Host::register_component(
156 self,
157 RString::from(name),
158 crate::surfaces::component_factory::<T>(),
159 );
160 }
161
162 fn renderable<T: Renderable>(&self, name: &str) {
163 Host::register_renderable(
164 self,
165 RString::from(name),
166 crate::surfaces::renderable_factory::<T>(),
167 );
168 }
169
170 fn payload<T: Payload>(&self, command: &str) {
171 Host::register_payload(
172 self,
173 RString::from(command),
174 crate::surfaces::payload_factory::<T>(),
175 );
176 }
177
178 fn desktop_item<T: DesktopItem>(&self, name: &str) {
179 Host::register_desktop_item(
180 self,
181 RString::from(name),
182 crate::surfaces::desktop_item_factory::<T>(),
183 );
184 }
185
186 fn desktop_item_deployer<T: DeployableDesktopItem>(&self, name: &str) {
187 Host::register_desktop_item_deployer(
188 self,
189 RString::from(name),
190 crate::surfaces::deployable_desktop_item_factory::<T>(),
191 );
192 }
193
194 fn compositor<T: Compositor>(&self, name: &str) {
195 Host::register_compositor(
196 self,
197 RString::from(name),
198 crate::surfaces::compositor_factory::<T>(),
199 );
200 }
201
202 fn spotlight<T: Spotlight>(&self, name: &str) {
203 Host::register_spotlight(
204 self,
205 RString::from(name),
206 crate::surfaces::spotlight_factory::<T>(),
207 );
208 }
209
210 fn quick_settings<T: QuickSettings>(&self, name: &str) {
211 Host::register_quick_settings(
212 self,
213 RString::from(name),
214 crate::surfaces::quick_settings_factory::<T>(),
215 );
216 }
217
218 fn config_parser<T: Send + Sync + 'static>(
219 &self,
220 name: &str,
221 parse: impl Fn(Option<&kdl::KdlNode>) -> T + Send + Sync + 'static,
222 ) {
223 Host::register_config_parser(self, RString::from(name), config_parser(name, parse));
224 }
225
226 fn config_parser_try<T: Send + Sync + 'static>(
227 &self,
228 name: &str,
229 parse: impl Fn(Option<&kdl::KdlNode>) -> Result<T, String> + Send + Sync + 'static,
230 ) {
231 Host::register_config_parser(self, RString::from(name), config_parser_try(name, parse));
232 }
233
234 fn service<T: Service>(&self, name: &str) {
235 Host::register_service(self, RString::from(name), service_factory::<T>(), false);
236 }
237
238 fn service_autostart<T: Service>(&self, name: &str) {
239 Host::register_service(self, RString::from(name), service_factory::<T>(), true);
240 }
241
242 fn foreground_service<T: ForegroundService>(&self, name: &str) {
243 Host::register_foreground_service(self, RString::from(name), foreground_service_factory::<T>());
244 }
245
246 fn payload_parser<T: Clone + Send + Sync + 'static>(
247 &self,
248 commands: &[&str],
249 parse: impl Fn(&str, &Args) -> Option<T> + Send + Sync + 'static,
250 ) {
251 let commands: RVec<RString> = commands
252 .iter()
253 .map(|command| RString::from(*command))
254 .collect();
255 Host::register_payload_parser(self, commands, payload_parser(parse));
256 }
257
258 fn command(&self, name: &str, title: &str) {
259 Host::register_command(
260 self,
261 RString::from(name),
262 RString::from(title),
263 RString::new(),
264 );
265 }
266
267 fn command_with(&self, name: &str, title: &str, description: &str) {
268 Host::register_command(
269 self,
270 RString::from(name),
271 RString::from(title),
272 RString::from(description),
273 );
274 }
275
276 fn style(&self, css: &str) {
277 Host::register_style(self, RStr::from_str(css));
278 }
279}