Skip to main content

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}