Skip to main content

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