spell_framework/configure.rs
1use crate::layer_properties::popup::{PopupAnchor, PopupGravity};
2use smithay_client_toolkit::{
3 shell::{
4 wlr_layer::{Anchor, KeyboardInteractivity, Layer},
5 xdg::popup::Popup,
6 },
7 shm::slot::{Buffer, SlotPool},
8};
9use std::{
10 cell::{Cell, RefCell},
11 fs,
12 io::Write,
13 os::unix::net::UnixDatagram,
14 path::Path,
15 rc::Rc,
16 sync::Mutex,
17};
18use tracing_appender::rolling::{RollingFileAppender, Rotation};
19use tracing_subscriber::{
20 EnvFilter, Layer as TracingTraitLayer,
21 filter::Filtered,
22 fmt::{self, format::DefaultFields},
23 layer::{Layered, SubscriberExt},
24 registry::Registry,
25 reload::Layer as LoadLayer,
26};
27
28/// It is an object required to create a [`SpellXDGPopup`](crate::wayland_adapter::SpellXDGPopup)
29/// instance.
30pub struct PopupCore {
31 pub(crate) pool: Rc<RefCell<SlotPool>>,
32 pub(crate) popup: Popup,
33 pub(crate) popup_conf: PopupConf,
34 pub(crate) buffer: Buffer,
35 // pub(crate) viewport: Viewport,
36}
37
38/// Configure type used to determine the properties of a XDG poup.
39pub struct PopupConf {
40 /// width of popup in pixels.
41 pub width: u32,
42 /// height of popup in pixels.
43 pub height: u32,
44 /// Anchor corner/edge of popup.
45 pub anchor: PopupAnchor,
46 /// Gravity direction of popup.
47 pub gravity: PopupGravity,
48 // FIXME: setting width and height zero creates protocol errors, create a
49 // builder method for it.
50 /// Anchor rectangle for popup.
51 pub anchor_rect: (i32, i32, i32, i32),
52}
53
54impl From<u32> for Dimension {
55 fn from(value: u32) -> Self {
56 Dimension::Pixel(value)
57 }
58}
59
60/// This enum provides multiple ways for defininf the Dimensions of a widget. Thus
61/// making the prcess dynamic rather than being anchored to just pixels. if `Full`
62/// or `Percentage` is provided in Dimension, output name is compulsary to be defined.
63/// To maintain backward compatibility, Dimension implements into from `u32` in which
64/// case it simply returns an instant of [`Dimension::Pixel`].
65#[derive(Debug, Clone, Default)]
66pub enum Dimension {
67 /// Full screen Dimension of the selected monitor.
68 #[default]
69 Full,
70 /// Whole number percentage Dimension of width/height, relative to the selected monitor.
71 Percentage(u32),
72 /// Definition of widgets in static pixels.
73 Pixel(u32),
74}
75
76/// WindowConf is an essential struct passed on to widget constructor functions (like invoke_spell
77/// of generated code) for defining the specifications of the widget.
78///
79/// ## Panics
80///
81/// 1. Event loops ([cast_spell](crate::cast_spell)) will panic if 0 is provided as width or height.
82/// 2. Builder will also panic if percentage or full is used without specifying the monitor explicitly.
83#[derive(Debug, Clone)]
84pub struct WindowConf {
85 pub(super) width: Dimension,
86 pub(super) height: Dimension,
87 /// width calculated from provided Dimension of width. Not intended for external use.
88 pub(super) evaluated_width: u32,
89 /// height provided from evaluated Dimension of height. Not intended for external use.
90 pub(super) evaluated_height: u32,
91 pub(super) anchor: [Option<Anchor>; 4],
92 pub(super) margin: (i32, i32, i32, i32),
93 pub(super) layer_type: Layer,
94 pub(super) board_interactivity: Cell<KeyboardInteractivity>,
95 pub(super) exclusive_zone: Option<i32>,
96 pub(super) monitor_name: Option<String>,
97 pub(super) natural_scroll: bool,
98}
99
100impl WindowConf {
101 /// Creates a builder instance for creation of WindowConf, to view defaults
102 /// head over to documentation of [`WindowConf`]'s parameters.
103 pub fn builder() -> WindowConfBuilder {
104 WindowConfBuilder::default()
105 }
106}
107
108/// A builder method for [`WindowConf`].
109#[derive(Default)]
110pub struct WindowConfBuilder {
111 max_width: Dimension,
112 max_height: Dimension,
113 anchor: [Option<Anchor>; 4],
114 margin: (i32, i32, i32, i32),
115 layer_type: Option<Layer>,
116 board_interactivity: KeyboardInteractivity,
117 exclusive_zone: Option<i32>,
118 monitor_name: Option<String>,
119 natural_scroll: bool,
120}
121
122impl WindowConfBuilder {
123 /// Defines the widget width in pixels, fullscreen width or pecentage width
124 /// of full screen. On setting values greater than the provided pixels of
125 /// monitor, the widget offsets from monitor's rectangular monitor space.
126 /// It is important to note that the value should be the maximum width the
127 /// widget will ever attain, not the current width in case of resizeable widgets.
128 /// This value has full screen width as its default.
129 pub fn width<I: Into<Dimension>>(&mut self, width: I) -> &mut Self {
130 let new = self;
131 new.max_width = width.into();
132 new
133 }
134
135 /// Defines the widget height in pixels, fullscreen width or pecentage height
136 /// of full screen. On setting values greater than the provided pixels of
137 /// monitor, the widget offsets from monitor's rectangular monitor space. It
138 /// is important to note that the value should be the maximum height the widget
139 /// will ever attain, not the current height in case of resizeable widgets.
140 /// This value has full screen height as its default.
141 pub fn height<I: Into<Dimension>>(&mut self, height: I) -> &mut Self {
142 let x = self;
143 x.max_height = height.into();
144 x
145 }
146
147 /// Defines the first anchor to which the window needs to be attached. View
148 /// [`Anchor`] for related explaination of usage. If all values are None,
149 /// then widget is displayed in the center of screen.
150 pub fn anchor_1(&mut self, anchor: Anchor) -> &mut Self {
151 let x = self;
152 x.anchor[0] = Some(anchor);
153 x
154 }
155
156 /// Defines the second anchor to which the window needs to be attached. View
157 /// [`Anchor`] for related explaination of usage. If all values are None,
158 /// then widget is displayed in the center of screen.
159 pub fn anchor_2(&mut self, anchor: Anchor) -> &mut Self {
160 let x = self;
161 x.anchor[1] = Some(anchor);
162 x
163 }
164
165 /// Defines the third anchor to which the window needs to be attached. View
166 /// [`Anchor`] for related explaination of usage. If all values are None,
167 /// then widget is displayed in the center of screen.
168 pub fn anchor_3(&mut self, anchor: Anchor) -> &mut Self {
169 let x = self;
170 x.anchor[2] = Some(anchor);
171 x
172 }
173
174 /// Defines the fourth anchor to which the window needs to be attached. View
175 /// [`Anchor`] for related explaination of usage. If all values are None,
176 /// then widget is displayed in the center of screen.
177 pub fn anchor_4(&mut self, anchor: Anchor) -> &mut Self {
178 let x = self;
179 x.anchor[3] = Some(anchor);
180 x
181 }
182
183 /// Defines the margin of widget from monitor edges, negative values make the
184 /// widget go outside of monitor pixels if anchored to some edge(s). Otherwise,
185 /// the widget moves to the opposite direction to the given pixels. Defaults to
186 /// `0` for all sides.
187 pub fn margins(&mut self, top: i32, right: i32, bottom: i32, left: i32) -> &mut Self {
188 let x = self;
189 x.margin = (top, right, bottom, left);
190 x
191 }
192
193 /// Defines the possible layer on which to define the widget. View [`Layer`]
194 /// for more details. Defaults to [`Layer::Top`].
195 pub fn layer_type(&mut self, layer: Layer) -> &mut Self {
196 let x = self;
197 x.layer_type = Some(layer);
198 x
199 }
200
201 /// Defines the relation of widget with Keyboard. View [`KeyboardInteractivity`]
202 /// for more details. Defauts to [`KeyboardInteractivity::None`]
203 pub fn board_interactivity(&mut self, board: KeyboardInteractivity) -> &mut Self {
204 let x = self;
205 x.board_interactivity = board;
206 x
207 }
208
209 /// Defines if the widget is exclusive of not, if not set, defaults to None,
210 /// else sets to number of pixels to set as exclusive zone as i32.
211 /// Defaults to no exclusive zone (i.e. None as mentioned).
212 pub fn exclusive_zone(&mut self, dimension: i32) -> &mut Self {
213 let x = self;
214 x.exclusive_zone = Some(dimension);
215 x
216 }
217
218 /// Defines the monitor name on which to spawn the window. It is necessary
219 /// to set this value if a percentage dimention is given to either width or
220 /// height; When no monitor is provided, the window is spawned on the
221 /// default monitor.
222 pub fn monitor(&mut self, name: String) -> &mut Self {
223 let x = self;
224 x.monitor_name = Some(name);
225 x
226 }
227
228 /// Defines if the method of scrolling for the widget should be natural or
229 /// reverse. Defaults to reverse scrolling. Learn more about scrolling types
230 /// [here](https://blog.logrocket.com/ux-design/natural-vs-reverse-scrolling/).
231 pub fn natural_scroll(&mut self, scroll: bool) -> &mut Self {
232 let x = self;
233 x.natural_scroll = scroll;
234 x
235 }
236
237 /// Creates an instnce of [`WindowConf`] with the provided configurations.
238 /// This function result in an error if width and height are not set or they
239 /// are set to zero or monitor is not specified when full or percentage dimension is used.
240 pub fn build(&self) -> Result<WindowConf, Box<dyn std::error::Error>> {
241 Ok(WindowConf {
242 width: if let Dimension::Percentage(x) = self.max_width
243 && x == 0
244 {
245 return Err("width is zero in percentage".into());
246 } else if let Dimension::Pixel(y) = self.max_width
247 && y == 0
248 {
249 return Err("width is zero in pixel".into());
250 } else {
251 self.max_width.clone()
252 },
253 height: if let Dimension::Percentage(x) = self.max_height
254 && x == 0
255 {
256 return Err("height is zero in percentage".into());
257 } else if let Dimension::Pixel(y) = self.max_height
258 && y == 0
259 {
260 return Err("height is zero in pixel".into());
261 } else {
262 self.max_height.clone()
263 },
264 evaluated_width: 0,
265 evaluated_height: 0,
266 anchor: self.anchor,
267 margin: self.margin,
268 layer_type: match self.layer_type {
269 None => Layer::Top,
270 Some(val) => val,
271 },
272 board_interactivity: Cell::new(self.board_interactivity),
273 exclusive_zone: self.exclusive_zone,
274 monitor_name: {
275 let needs_monitor =
276 matches!(self.max_width, Dimension::Full | Dimension::Percentage(_))
277 || matches!(self.max_height, Dimension::Full | Dimension::Percentage(_));
278
279 if needs_monitor && self.monitor_name.is_none() {
280 return Err(
281 "Provide explicit monitor name if using Full or Percentage dimensions"
282 .into(),
283 );
284 } else {
285 self.monitor_name.clone()
286 }
287 },
288 natural_scroll: self.natural_scroll,
289 })
290 }
291}
292
293pub(crate) type HomeHandle = tracing_subscriber::reload::Handle<
294 Filtered<
295 tracing_subscriber::fmt::Layer<
296 Layered<
297 Filtered<
298 tracing_subscriber::fmt::Layer<
299 Layered<
300 Filtered<
301 tracing_subscriber::fmt::Layer<
302 Registry,
303 DefaultFields,
304 tracing_subscriber::fmt::format::Format<
305 tracing_subscriber::fmt::format::Full,
306 (),
307 >,
308 >,
309 EnvFilter,
310 Registry,
311 >,
312 Registry,
313 >,
314 DefaultFields,
315 tracing_subscriber::fmt::format::Format<
316 tracing_subscriber::fmt::format::Full,
317 (),
318 >,
319 RollingFileAppender,
320 >,
321 EnvFilter,
322 Layered<
323 Filtered<
324 tracing_subscriber::fmt::Layer<
325 Registry,
326 DefaultFields,
327 tracing_subscriber::fmt::format::Format<
328 tracing_subscriber::fmt::format::Full,
329 (),
330 >,
331 >,
332 EnvFilter,
333 Registry,
334 >,
335 Registry,
336 >,
337 >,
338 Layered<
339 Filtered<
340 tracing_subscriber::fmt::Layer<
341 Registry,
342 DefaultFields,
343 tracing_subscriber::fmt::format::Format<
344 tracing_subscriber::fmt::format::Full,
345 (),
346 >,
347 >,
348 EnvFilter,
349 Registry,
350 >,
351 Registry,
352 >,
353 >,
354 DefaultFields,
355 tracing_subscriber::fmt::format::Format<tracing_subscriber::fmt::format::Full, ()>,
356 std::sync::Mutex<SocketWriter>,
357 >,
358 EnvFilter,
359 Layered<
360 Filtered<
361 tracing_subscriber::fmt::Layer<
362 Layered<
363 Filtered<
364 tracing_subscriber::fmt::Layer<
365 Registry,
366 DefaultFields,
367 tracing_subscriber::fmt::format::Format<
368 tracing_subscriber::fmt::format::Full,
369 (),
370 >,
371 >,
372 EnvFilter,
373 Registry,
374 >,
375 Registry,
376 >,
377 DefaultFields,
378 tracing_subscriber::fmt::format::Format<
379 tracing_subscriber::fmt::format::Full,
380 (),
381 >,
382 RollingFileAppender,
383 >,
384 EnvFilter,
385 Layered<
386 Filtered<
387 tracing_subscriber::fmt::Layer<
388 Registry,
389 DefaultFields,
390 tracing_subscriber::fmt::format::Format<
391 tracing_subscriber::fmt::format::Full,
392 (),
393 >,
394 >,
395 EnvFilter,
396 Registry,
397 >,
398 Registry,
399 >,
400 >,
401 Layered<
402 Filtered<
403 tracing_subscriber::fmt::Layer<
404 Registry,
405 DefaultFields,
406 tracing_subscriber::fmt::format::Format<
407 tracing_subscriber::fmt::format::Full,
408 (),
409 >,
410 >,
411 EnvFilter,
412 Registry,
413 >,
414 Registry,
415 >,
416 >,
417 >,
418 Layered<
419 Filtered<
420 tracing_subscriber::fmt::Layer<
421 Layered<
422 Filtered<
423 tracing_subscriber::fmt::Layer<
424 Registry,
425 DefaultFields,
426 tracing_subscriber::fmt::format::Format<
427 tracing_subscriber::fmt::format::Full,
428 (),
429 >,
430 >,
431 EnvFilter,
432 Registry,
433 >,
434 Registry,
435 >,
436 DefaultFields,
437 tracing_subscriber::fmt::format::Format<tracing_subscriber::fmt::format::Full, ()>,
438 RollingFileAppender,
439 >,
440 EnvFilter,
441 Layered<
442 Filtered<
443 tracing_subscriber::fmt::Layer<
444 Registry,
445 DefaultFields,
446 tracing_subscriber::fmt::format::Format<
447 tracing_subscriber::fmt::format::Full,
448 (),
449 >,
450 >,
451 EnvFilter,
452 Registry,
453 >,
454 Registry,
455 >,
456 >,
457 Layered<
458 Filtered<
459 tracing_subscriber::fmt::Layer<
460 Registry,
461 DefaultFields,
462 tracing_subscriber::fmt::format::Format<
463 tracing_subscriber::fmt::format::Full,
464 (),
465 >,
466 >,
467 EnvFilter,
468 Registry,
469 >,
470 Registry,
471 >,
472 >,
473>;
474pub(crate) fn set_up_tracing(widget_name: &str) -> HomeHandle {
475 let runtime_dir = std::env::var("XDG_RUNTIME_DIR").expect("runtime dir is not set");
476 let logging_dir = runtime_dir + "/spell/";
477 let socket_dir = logging_dir.clone() + "/spell.sock";
478 // let socket_cli_dir = logging_dir.clone() + "/spell_cli";
479
480 let _ = fs::create_dir(Path::new(&logging_dir));
481 let _ = fs::remove_file(&socket_dir);
482 // let _ = fs::File::create(&socket_cli_dir);
483
484 let stream = UnixDatagram::unbound().unwrap();
485 stream
486 .set_nonblocking(true)
487 .expect("Non blocking couldn't be set");
488
489 let writer = RollingFileAppender::builder()
490 .rotation(Rotation::HOURLY) // rotate log files once every hour
491 .filename_prefix(widget_name) // log file names will be prefixed with `myapp.`
492 .filename_suffix("log") // log file names will be suffixed with `.log`
493 .build(&logging_dir) // try to build an appender that stores log files in `/var/log`
494 .expect("initializing rolling file appender failed");
495
496 // Logs to be stored in case of debugging.
497 let layer_writer = fmt::layer()
498 .without_time()
499 .with_target(false)
500 .with_writer(writer)
501 .with_ansi(false)
502 .with_filter(EnvFilter::new("spell_framework=trace,info"));
503
504 // Logs on socket read by cli.
505 let layer_socket = fmt::Layer::default()
506 .without_time()
507 .with_target(false)
508 .with_writer(Mutex::new(SocketWriter::new(stream)))
509 .with_filter(EnvFilter::new("spell_framework=info, warn"));
510
511 let (layer_env, handle) = LoadLayer::new(layer_socket);
512 let subs = tracing_subscriber::registry()
513 // Logs shown in stdout when program runs.
514 .with(
515 fmt::layer()
516 .without_time()
517 .with_target(false)
518 .with_filter(EnvFilter::new("spell_framework=info, warn")),
519 )
520 // Logs for file.
521 .with(layer_writer)
522 // Logs for cli
523 .with(layer_env);
524 let _ = tracing::subscriber::set_global_default(subs);
525 handle
526}
527
528pub(crate) struct SocketWriter {
529 socket: UnixDatagram,
530 // formatter: Format<DefaultFields>,
531}
532
533impl SocketWriter {
534 fn new(socket: UnixDatagram) -> Self {
535 SocketWriter { socket }
536 }
537}
538
539impl Write for SocketWriter {
540 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
541 let runtime_dir = std::env::var("XDG_RUNTIME_DIR").expect("runtime dir is not set");
542 let logging_dir = runtime_dir + "/spell/";
543 let socket_dir = logging_dir.clone() + "/spell.sock";
544
545 self.socket.send_to(buf, Path::new(&socket_dir))
546 }
547
548 fn flush(&mut self) -> std::io::Result<()> {
549 Ok(())
550 }
551}
552
553// TODO this will be made public when multiple widgets in the same layer is supported.
554// Likely it will be easy after the resize action is implemented
555#[allow(dead_code)]
556pub enum LayerConf {
557 Window(WindowConf),
558 Windows(Vec<WindowConf>),
559 Lock(u32, u32),
560}