miracle_plugin/
config.rs

1//! Configuration types for the plugin `configure()` hook.
2//!
3//! Return a [`Configuration`] from your [`crate::plugin::Plugin::configure`] implementation
4//! to override compositor configuration values on every config reload. Any field
5//! left as `None` is ignored; the compositor keeps its own value for that field.
6//!
7//! The `plugins` and `includes` keys cannot be set by plugins.
8//!
9//! # Example
10//! ```rust,ignore
11//! use miracle_plugin::config::{BindingAction, Configuration, CustomKeyAction, Gaps, Key, Modifier};
12//!
13//! fn configure(&mut self) -> Option<Configuration> {
14//!     Some(Configuration {
15//!         primary_modifier: Some(Modifier::Meta),
16//!         custom_key_actions: Some(vec![CustomKeyAction {
17//!             action: BindingAction::Down,
18//!             modifiers: vec![Modifier::Primary],
19//!             key: Key::new("Return"),
20//!             command: "kitty".to_string(),
21//!         }]),
22//!         inner_gaps: Some(Gaps { x: 5, y: 5 }),
23//!         ..Default::default()
24//!     })
25//! }
26//! ```
27
28use serde::Serialize;
29
30// ─── Modifier ────────────────────────────────────────────────────────────────
31
32/// A keyboard modifier key for use in configuration bindings.
33///
34/// These names correspond exactly to the lowercase strings accepted by
35/// miracle-wm's configuration parser. [`Modifier::Primary`] is a special
36/// sentinel meaning "use whatever the user has configured as their primary
37/// modifier key" — it is the recommended value for plugins that want to
38/// integrate naturally with the user's keybinding preferences.
39///
40/// # Relationship to `input::InputEventModifiers`
41/// At runtime, `Modifier::Meta` corresponds to `InputEventModifiers::META`,
42/// `Modifier::Shift` to `InputEventModifiers::SHIFT`, etc. Config uses a
43/// simple enum because the set of recognised modifiers is fixed and small.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub enum Modifier {
46    /// Either Alt key. Serializes as `"alt"`.
47    Alt,
48    /// Left Alt key. Serializes as `"alt_left"`.
49    AltLeft,
50    /// Right Alt key. Serializes as `"alt_right"`.
51    AltRight,
52    /// Either Shift key. Serializes as `"shift"`.
53    Shift,
54    /// Left Shift key. Serializes as `"shift_left"`.
55    ShiftLeft,
56    /// Right Shift key. Serializes as `"shift_right"`.
57    ShiftRight,
58    /// Sym key. Serializes as `"sym"`.
59    Sym,
60    /// Function key. Serializes as `"function"`.
61    Function,
62    /// Either Ctrl key. Serializes as `"ctrl"`.
63    Ctrl,
64    /// Left Ctrl key. Serializes as `"ctrl_left"`.
65    CtrlLeft,
66    /// Right Ctrl key. Serializes as `"ctrl_right"`.
67    CtrlRight,
68    /// Either Meta/Super/Windows key. Serializes as `"meta"`.
69    /// This is the most common choice for compositor bindings.
70    Meta,
71    /// Left Meta key. Serializes as `"meta_left"`.
72    MetaLeft,
73    /// Right Meta key. Serializes as `"meta_right"`.
74    MetaRight,
75    /// Caps Lock. Serializes as `"caps_lock"`.
76    CapsLock,
77    /// Num Lock. Serializes as `"num_lock"`.
78    NumLock,
79    /// Scroll Lock. Serializes as `"scroll_lock"`.
80    ScrollLock,
81    /// Sentinel: "use the user's configured primary modifier key".
82    /// Serializes as `"primary"`. Recommended for plugins that should
83    /// respect the user's own modifier preference.
84    Primary,
85}
86
87impl Modifier {
88    /// Returns the string representation expected by the compositor.
89    pub fn as_str(self) -> &'static str {
90        match self {
91            Self::Alt => "alt",
92            Self::AltLeft => "alt_left",
93            Self::AltRight => "alt_right",
94            Self::Shift => "shift",
95            Self::ShiftLeft => "shift_left",
96            Self::ShiftRight => "shift_right",
97            Self::Sym => "sym",
98            Self::Function => "function",
99            Self::Ctrl => "ctrl",
100            Self::CtrlLeft => "ctrl_left",
101            Self::CtrlRight => "ctrl_right",
102            Self::Meta => "meta",
103            Self::MetaLeft => "meta_left",
104            Self::MetaRight => "meta_right",
105            Self::CapsLock => "caps_lock",
106            Self::NumLock => "num_lock",
107            Self::ScrollLock => "scroll_lock",
108            Self::Primary => "primary",
109        }
110    }
111}
112
113impl Serialize for Modifier {
114    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
115        s.serialize_str(self.as_str())
116    }
117}
118
119// ─── BindingAction ───────────────────────────────────────────────────────────
120
121/// The keyboard event phase that triggers a key binding.
122///
123/// Named `BindingAction` (rather than `KeyboardAction`) to avoid confusion
124/// with [`crate::input::KeyboardAction`], which carries additional runtime
125/// variants that have no meaning in a configuration context.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
127pub enum BindingAction {
128    /// Key was pressed. This is the most common trigger for bindings.
129    #[default]
130    Down,
131    /// Key was released.
132    Up,
133    /// Key is being held and auto-repeating.
134    Repeat,
135}
136
137impl BindingAction {
138    /// Returns the string representation expected by the compositor.
139    pub fn as_str(self) -> &'static str {
140        match self {
141            Self::Down => "down",
142            Self::Up => "up",
143            Self::Repeat => "repeat",
144        }
145    }
146}
147
148impl Serialize for BindingAction {
149    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
150        s.serialize_str(self.as_str())
151    }
152}
153
154// ─── Key ─────────────────────────────────────────────────────────────────────
155
156/// An XKB keysym name for use in configuration bindings.
157///
158/// Examples: `Key::new("Return")`, `Key::new("a")`, `Key::new("Up")`,
159/// `Key::new("F5")`.
160///
161/// The compositor validates the name using `xkb_keysym_from_name`. A full
162/// list of valid names is available at:
163/// <https://xkbcommon.org/doc/current/xkbcommon-keysyms_8h.html>
164#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
165pub struct Key(pub String);
166
167impl Key {
168    /// Create a key from an XKB keysym name.
169    pub fn new(name: impl Into<String>) -> Self {
170        Self(name.into())
171    }
172}
173
174impl<S: Into<String>> From<S> for Key {
175    fn from(s: S) -> Self {
176        Self(s.into())
177    }
178}
179
180impl Serialize for Key {
181    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
182        s.serialize_str(&self.0)
183    }
184}
185
186// ─── Handedness ──────────────────────────────────────────────────────────────
187
188/// Mouse button handedness.
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
190#[serde(rename_all = "snake_case")]
191pub enum Handedness {
192    #[default]
193    Right,
194    Left,
195}
196
197// ─── PointerAcceleration ─────────────────────────────────────────────────────
198
199/// Pointer acceleration profile.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
201#[serde(rename_all = "snake_case")]
202pub enum PointerAcceleration {
203    Adaptive,
204    #[default]
205    None,
206}
207
208// ─── CursorFocusMode ─────────────────────────────────────────────────────────
209
210/// Whether focus follows the pointer or requires a click.
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
212#[serde(rename_all = "snake_case")]
213pub enum CursorFocusMode {
214    #[default]
215    Hover,
216    Click,
217}
218
219// ─── TouchpadClickMode ───────────────────────────────────────────────────────
220
221/// Touchpad click emulation mode.
222#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
223#[serde(rename_all = "snake_case")]
224pub enum TouchpadClickMode {
225    #[default]
226    None,
227    AreaToClick,
228    FingerCount,
229}
230
231// ─── TouchpadScrollMode ──────────────────────────────────────────────────────
232
233/// Touchpad scroll method.
234#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
235#[serde(rename_all = "snake_case")]
236pub enum TouchpadScrollMode {
237    #[default]
238    None,
239    TwoFingerScroll,
240    EdgeScroll,
241    ButtonDownScroll,
242}
243
244// ─── AnimationPartType ───────────────────────────────────────────────────────
245
246/// Built-in animation visual effect for one phase of an animation sequence.
247#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
248#[serde(rename_all = "snake_case")]
249pub enum AnimationPartType {
250    #[default]
251    Disabled,
252    Slide,
253    Grow,
254    Shrink,
255    Fade,
256}
257
258// ─── EasingFunction ──────────────────────────────────────────────────────────
259
260/// Easing function for animation timing.
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
262#[serde(rename_all = "snake_case")]
263pub enum EasingFunction {
264    #[default]
265    Linear,
266    EaseInSine,
267    EaseOutSine,
268    EaseInOutSine,
269    EaseInQuad,
270    EaseOutQuad,
271    EaseInOutQuad,
272    EaseInCubic,
273    EaseOutCubic,
274    EaseInOutCubic,
275    EaseInQuart,
276    EaseOutQuart,
277    EaseInOutQuart,
278    EaseInQuint,
279    EaseOutQuint,
280    EaseInOutQuint,
281    EaseInExpo,
282    EaseOutExpo,
283    EaseInOutExpo,
284    EaseInCirc,
285    EaseOutCirc,
286    EaseInOutCirc,
287    EaseInBack,
288    EaseOutBack,
289    EaseInOutBack,
290    EaseInElastic,
291    EaseOutElastic,
292    EaseInOutElastic,
293    EaseInBounce,
294    EaseOutBounce,
295    EaseInOutBounce,
296}
297
298// ─── AnimationEvent ──────────────────────────────────────────────────────────
299
300/// The compositor event that an animation definition applies to.
301#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
302#[serde(rename_all = "snake_case")]
303pub enum AnimationEvent {
304    #[default]
305    WindowOpen,
306    WindowMove,
307    WindowClose,
308    WorkspaceSwitch,
309}
310
311// ─── AnimationKind ───────────────────────────────────────────────────────────
312
313/// Whether an animation is driven by a built-in effect or a plugin callback.
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
315#[serde(rename_all = "snake_case")]
316pub enum AnimationKind {
317    #[default]
318    BuiltIn,
319    Plugin,
320}
321
322// ─── Config structs ──────────────────────────────────────────────────────────
323
324/// Gaps configuration. Both `x` (left/right) and `y` (top/bottom) are in pixels.
325#[derive(Debug, Clone, Default, Serialize)]
326pub struct Gaps {
327    pub x: i32,
328    pub y: i32,
329}
330
331/// A custom key binding that runs a shell command.
332#[derive(Debug, Clone, Serialize)]
333pub struct CustomKeyAction {
334    /// The keyboard event phase that triggers this binding.
335    pub action: BindingAction,
336    /// The modifier keys required for this binding.
337    pub modifiers: Vec<Modifier>,
338    /// The XKB keysym name (e.g. `Key::new("Return")`, `Key::new("a")`).
339    pub key: Key,
340    /// The shell command to execute.
341    pub command: String,
342}
343
344/// Override the key binding for a built-in compositor action.
345#[derive(Debug, Clone, Serialize)]
346pub struct BuiltInKeyCommandOverride {
347    /// Name of the built-in action (e.g. `"terminal"`, `"close_window"`).
348    pub name: String,
349    /// The keyboard event phase that triggers this binding.
350    pub action: BindingAction,
351    /// The modifier keys required for this binding.
352    pub modifiers: Vec<Modifier>,
353    /// The XKB keysym name.
354    pub key: Key,
355}
356
357/// An application to start on compositor launch.
358#[derive(Debug, Clone, Default, Serialize)]
359pub struct StartupApp {
360    pub command: String,
361    #[serde(skip_serializing_if = "is_false")]
362    pub restart_on_death: bool,
363    #[serde(skip_serializing_if = "is_false")]
364    pub no_startup_id: bool,
365    #[serde(skip_serializing_if = "is_false")]
366    pub should_halt_compositor_on_death: bool,
367    #[serde(skip_serializing_if = "is_false")]
368    pub in_systemd_scope: bool,
369}
370
371/// An environment variable to set in the compositor's environment.
372#[derive(Debug, Clone, Default, Serialize)]
373pub struct EnvironmentVariable {
374    pub key: String,
375    pub value: String,
376}
377
378/// Window border appearance.
379#[derive(Debug, Clone, Default, Serialize)]
380pub struct BorderConfig {
381    pub size: i32,
382    pub radius: f32,
383    /// Color as a hex string (`"RRGGBBAA"`) or an RGBA array `[r, g, b, a]` (0–255).
384    pub color: String,
385    /// Focused-window color as a hex string or RGBA array.
386    pub focus_color: String,
387}
388
389/// Workspace configuration entry.
390#[derive(Debug, Clone, Default, Serialize)]
391pub struct WorkspaceConfig {
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub number: Option<i32>,
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub name: Option<String>,
396}
397
398/// Drag-and-drop behaviour.
399#[derive(Debug, Clone, Serialize)]
400pub struct DragAndDropConfiguration {
401    pub enabled: bool,
402    /// The modifier keys required to initiate a drag-and-drop operation.
403    #[serde(skip_serializing_if = "Vec::is_empty")]
404    pub modifiers: Vec<Modifier>,
405}
406
407impl Default for DragAndDropConfiguration {
408    fn default() -> Self {
409        Self {
410            enabled: true,
411            modifiers: Vec::new(),
412        }
413    }
414}
415
416/// A single built-in animation (one phase of an easing sequence).
417#[derive(Debug, Clone, Default, Serialize)]
418pub struct BuiltInAnimationPart {
419    /// The visual effect for this animation phase.
420    #[serde(rename = "type")]
421    pub type_: AnimationPartType,
422    /// The easing function that controls timing.
423    pub function: EasingFunction,
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub c1: Option<f32>,
426    #[serde(skip_serializing_if = "Option::is_none")]
427    pub c2: Option<f32>,
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub c3: Option<f32>,
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub c4: Option<f32>,
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub n1: Option<f32>,
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub d1: Option<f32>,
436}
437
438/// An animation definition for one animatable event.
439#[derive(Debug, Clone, Default, Serialize)]
440pub struct AnimationDefinition {
441    /// The compositor event to animate.
442    pub event: AnimationEvent,
443    /// Whether to use a built-in animation effect or a plugin callback.
444    #[serde(rename = "type")]
445    pub type_: AnimationKind,
446    /// Duration in seconds.
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub duration: Option<f32>,
449    /// The list of animation phases (required when `type_` is `BuiltIn`).
450    #[serde(skip_serializing_if = "Vec::is_empty")]
451    pub parts: Vec<BuiltInAnimationPart>,
452}
453
454/// Mouse pointer configuration.
455#[derive(Debug, Clone, Default, Serialize)]
456pub struct MouseConfiguration {
457    /// Swap left and right buttons.
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub handedness: Option<Handedness>,
460    /// Pointer acceleration profile.
461    #[serde(skip_serializing_if = "Option::is_none")]
462    pub acceleration: Option<PointerAcceleration>,
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub acceleration_bias: Option<f64>,
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub vscroll_speed: Option<f64>,
467    #[serde(skip_serializing_if = "Option::is_none")]
468    pub hscroll_speed: Option<f64>,
469}
470
471/// Keymap (keyboard layout) configuration.
472#[derive(Debug, Clone, Default, Serialize)]
473pub struct KeymapConfiguration {
474    pub language: String,
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub variant: Option<String>,
477    #[serde(skip_serializing_if = "Vec::is_empty")]
478    pub options: Vec<String>,
479}
480
481/// Keyboard repeat and layout configuration.
482#[derive(Debug, Clone, Default, Serialize)]
483pub struct KeyboardConfiguration {
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub repeat_delay: Option<i32>,
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub repeat_rate: Option<i32>,
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub keymap: Option<KeymapConfiguration>,
490}
491
492/// Hover-click (dwell click) configuration.
493#[derive(Debug, Clone, Default, Serialize)]
494pub struct HoverClickConfiguration {
495    pub enabled: bool,
496    /// How long (ms) the pointer must hover before a click is generated.
497    #[serde(skip_serializing_if = "Option::is_none", rename = "hover_duration")]
498    pub hover_duration_ms: Option<u32>,
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub cancel_displacement_threshold: Option<i32>,
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub reclick_displacement_threshold: Option<i32>,
503}
504
505/// Simulated secondary (right) click via long-press.
506#[derive(Debug, Clone, Default, Serialize)]
507pub struct SimulatedSecondaryClickConfiguration {
508    pub enabled: bool,
509    /// How long (ms) to hold before the secondary click is generated.
510    #[serde(skip_serializing_if = "Option::is_none", rename = "hold_duration")]
511    pub hold_duration_ms: Option<u32>,
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub displacement_threshold: Option<i32>,
514}
515
516/// Output (display) filter shader.
517#[derive(Debug, Clone, Default, Serialize)]
518pub struct OutputFilterConfiguration {
519    #[serde(skip_serializing_if = "Option::is_none")]
520    pub shader_path: Option<String>,
521}
522
523/// Cursor appearance and focus behaviour.
524#[derive(Debug, Clone, Default, Serialize)]
525pub struct CursorConfiguration {
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub scale: Option<f32>,
528    /// Whether focus follows hover or requires a click.
529    #[serde(skip_serializing_if = "Option::is_none")]
530    pub focus_mode: Option<CursorFocusMode>,
531}
532
533/// Slow keys (accessibility) configuration.
534#[derive(Debug, Clone, Default, Serialize)]
535pub struct SlowKeysConfiguration {
536    pub enabled: bool,
537    /// How long (ms) a key must be held before it registers.
538    #[serde(skip_serializing_if = "Option::is_none", rename = "hold_delay")]
539    pub hold_delay_ms: Option<u32>,
540}
541
542/// Sticky keys (accessibility) configuration.
543#[derive(Debug, Clone, Default, Serialize)]
544pub struct StickyKeysConfiguration {
545    pub enabled: bool,
546    #[serde(skip_serializing_if = "Option::is_none")]
547    pub should_disable_if_two_keys_are_pressed_together: Option<bool>,
548}
549
550/// Touchpad configuration.
551#[derive(Debug, Clone, Default, Serialize)]
552pub struct TouchpadConfiguration {
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub disable_while_typing: Option<bool>,
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub disable_with_external_mouse: Option<bool>,
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub acceleration_bias: Option<f64>,
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub vscroll_speed: Option<f64>,
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub hscroll_speed: Option<f64>,
563    #[serde(skip_serializing_if = "Option::is_none")]
564    pub tap_to_click: Option<bool>,
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub middle_mouse_button_emulation: Option<bool>,
567    /// Touchpad click emulation mode.
568    #[serde(skip_serializing_if = "Option::is_none")]
569    pub click_mode: Option<TouchpadClickMode>,
570    /// Touchpad scroll method.
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub scroll_mode: Option<TouchpadScrollMode>,
573}
574
575/// Screen magnifier configuration.
576#[derive(Debug, Clone, Default, Serialize)]
577pub struct MagnifierConfiguration {
578    pub enabled: bool,
579    #[serde(skip_serializing_if = "Option::is_none")]
580    pub scale: Option<f32>,
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub scale_increment: Option<f32>,
583    #[serde(skip_serializing_if = "Option::is_none")]
584    pub width: Option<i32>,
585    #[serde(skip_serializing_if = "Option::is_none")]
586    pub height: Option<i32>,
587    #[serde(skip_serializing_if = "Option::is_none")]
588    pub size_increment: Option<i32>,
589}
590
591/// Configuration overrides that a plugin may return from [`Plugin::configure`].
592///
593/// Every field is optional. `None` means "do not override this value". The
594/// compositor merges all loaded plugins' results and then merges the combined
595/// result with the file-based configuration (plugin values win on conflict).
596///
597/// The `plugins` and `includes` keys of the compositor config cannot be set
598/// by plugins and are intentionally absent from this struct.
599#[derive(Debug, Clone, Default, Serialize)]
600pub struct Configuration {
601    /// The primary modifier key (e.g. `Modifier::Meta` for the Super/Windows key).
602    #[serde(skip_serializing_if = "Option::is_none", rename = "action_key")]
603    pub primary_modifier: Option<Modifier>,
604
605    /// Custom key bindings that run shell commands.
606    #[serde(skip_serializing_if = "Option::is_none", rename = "custom_actions")]
607    pub custom_key_actions: Option<Vec<CustomKeyAction>>,
608
609    /// Overrides for built-in compositor key bindings.
610    #[serde(skip_serializing_if = "Option::is_none")]
611    pub default_action_overrides: Option<Vec<BuiltInKeyCommandOverride>>,
612
613    /// Inner (between windows) gap size.
614    #[serde(skip_serializing_if = "Option::is_none")]
615    pub inner_gaps: Option<Gaps>,
616
617    /// Outer (screen edge) gap size.
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub outer_gaps: Option<Gaps>,
620
621    /// Applications to launch on startup.
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub startup_apps: Option<Vec<StartupApp>>,
624
625    /// Override the default terminal emulator command.
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub terminal: Option<String>,
628
629    /// Pixel amount to jump when resizing with keyboard shortcuts.
630    #[serde(skip_serializing_if = "Option::is_none")]
631    pub resize_jump: Option<i32>,
632
633    /// Extra environment variables to set in the compositor process.
634    #[serde(skip_serializing_if = "Option::is_none")]
635    pub environment_variables: Option<Vec<EnvironmentVariable>>,
636
637    /// Window border appearance.
638    #[serde(skip_serializing_if = "Option::is_none")]
639    pub border: Option<BorderConfig>,
640
641    /// Workspace layout definitions.
642    #[serde(skip_serializing_if = "Option::is_none")]
643    pub workspaces: Option<Vec<WorkspaceConfig>>,
644
645    /// Animation definitions per event. Each entry names an `event` plus the definition.
646    #[serde(skip_serializing_if = "Option::is_none")]
647    pub animations: Option<Vec<AnimationDefinition>>,
648
649    /// Whether animations are globally enabled.
650    #[serde(skip_serializing_if = "Option::is_none")]
651    pub enable_animations: Option<bool>,
652
653    /// The modifier keys used for window move operations.
654    /// Use `vec![Modifier::Primary]` to follow the user's primary modifier.
655    #[serde(skip_serializing_if = "Option::is_none")]
656    pub move_modifier: Option<Vec<Modifier>>,
657
658    /// Drag-and-drop behaviour.
659    #[serde(skip_serializing_if = "Option::is_none")]
660    pub drag_and_drop: Option<DragAndDropConfiguration>,
661
662    /// Mouse pointer settings.
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub mouse: Option<MouseConfiguration>,
665
666    /// Touchpad settings.
667    #[serde(skip_serializing_if = "Option::is_none")]
668    pub touchpad: Option<TouchpadConfiguration>,
669
670    /// Keyboard repeat rate, delay, and keymap.
671    #[serde(skip_serializing_if = "Option::is_none")]
672    pub keyboard: Option<KeyboardConfiguration>,
673
674    /// Hover-click (dwell click) accessibility feature.
675    #[serde(skip_serializing_if = "Option::is_none")]
676    pub hover_click: Option<HoverClickConfiguration>,
677
678    /// Simulated secondary click accessibility feature.
679    #[serde(skip_serializing_if = "Option::is_none")]
680    pub simulated_secondary_click: Option<SimulatedSecondaryClickConfiguration>,
681
682    /// Output (display) post-processing filter.
683    #[serde(skip_serializing_if = "Option::is_none")]
684    pub output_filter: Option<OutputFilterConfiguration>,
685
686    /// Cursor appearance and focus behaviour.
687    #[serde(skip_serializing_if = "Option::is_none")]
688    pub cursor: Option<CursorConfiguration>,
689
690    /// Slow keys accessibility feature.
691    #[serde(skip_serializing_if = "Option::is_none")]
692    pub slow_keys: Option<SlowKeysConfiguration>,
693
694    /// Sticky keys accessibility feature.
695    #[serde(skip_serializing_if = "Option::is_none")]
696    pub sticky_keys: Option<StickyKeysConfiguration>,
697
698    /// Screen magnifier.
699    #[serde(skip_serializing_if = "Option::is_none")]
700    pub magnifier: Option<MagnifierConfiguration>,
701
702    /// Whether switching to the current workspace goes back to the previous one.
703    #[serde(skip_serializing_if = "Option::is_none")]
704    pub workspace_back_and_forth: Option<bool>,
705}
706
707fn is_false(v: &bool) -> bool {
708    !v
709}