miracle-plugin 0.0.9

Rust bindings for the miracle-wm plugin API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
//! Configuration types for the plugin `configure()` hook.
//!
//! Return a [`Configuration`] from your [`crate::plugin::Plugin::configure`] implementation
//! to override compositor configuration values on every config reload. Any field
//! left as `None` is ignored; the compositor keeps its own value for that field.
//!
//! The `plugins` and `includes` keys cannot be set by plugins.
//!
//! # Example
//! ```rust,ignore
//! use miracle_plugin::config::{BindingAction, Configuration, CustomKeyAction, Gaps, Key, Modifier};
//!
//! fn configure(&mut self) -> Option<Configuration> {
//!     Some(Configuration {
//!         primary_modifier: Some(Modifier::Meta),
//!         custom_key_actions: Some(vec![CustomKeyAction {
//!             action: BindingAction::Down,
//!             modifiers: vec![Modifier::Primary],
//!             key: Key::new("Return"),
//!             command: "kitty".to_string(),
//!         }]),
//!         inner_gaps: Some(Gaps { x: 5, y: 5 }),
//!         ..Default::default()
//!     })
//! }
//! ```

use serde::Serialize;

// ─── Modifier ────────────────────────────────────────────────────────────────

/// A keyboard modifier key for use in configuration bindings.
///
/// These names correspond exactly to the lowercase strings accepted by
/// miracle-wm's configuration parser. [`Modifier::Primary`] is a special
/// sentinel meaning "use whatever the user has configured as their primary
/// modifier key" — it is the recommended value for plugins that want to
/// integrate naturally with the user's keybinding preferences.
///
/// # Relationship to `input::InputEventModifiers`
/// At runtime, `Modifier::Meta` corresponds to `InputEventModifiers::META`,
/// `Modifier::Shift` to `InputEventModifiers::SHIFT`, etc. Config uses a
/// simple enum because the set of recognised modifiers is fixed and small.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Modifier {
    /// Either Alt key. Serializes as `"alt"`.
    Alt,
    /// Left Alt key. Serializes as `"alt_left"`.
    AltLeft,
    /// Right Alt key. Serializes as `"alt_right"`.
    AltRight,
    /// Either Shift key. Serializes as `"shift"`.
    Shift,
    /// Left Shift key. Serializes as `"shift_left"`.
    ShiftLeft,
    /// Right Shift key. Serializes as `"shift_right"`.
    ShiftRight,
    /// Sym key. Serializes as `"sym"`.
    Sym,
    /// Function key. Serializes as `"function"`.
    Function,
    /// Either Ctrl key. Serializes as `"ctrl"`.
    Ctrl,
    /// Left Ctrl key. Serializes as `"ctrl_left"`.
    CtrlLeft,
    /// Right Ctrl key. Serializes as `"ctrl_right"`.
    CtrlRight,
    /// Either Meta/Super/Windows key. Serializes as `"meta"`.
    /// This is the most common choice for compositor bindings.
    Meta,
    /// Left Meta key. Serializes as `"meta_left"`.
    MetaLeft,
    /// Right Meta key. Serializes as `"meta_right"`.
    MetaRight,
    /// Caps Lock. Serializes as `"caps_lock"`.
    CapsLock,
    /// Num Lock. Serializes as `"num_lock"`.
    NumLock,
    /// Scroll Lock. Serializes as `"scroll_lock"`.
    ScrollLock,
    /// Sentinel: "use the user's configured primary modifier key".
    /// Serializes as `"primary"`. Recommended for plugins that should
    /// respect the user's own modifier preference.
    Primary,
}

impl Modifier {
    /// Returns the string representation expected by the compositor.
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Alt => "alt",
            Self::AltLeft => "alt_left",
            Self::AltRight => "alt_right",
            Self::Shift => "shift",
            Self::ShiftLeft => "shift_left",
            Self::ShiftRight => "shift_right",
            Self::Sym => "sym",
            Self::Function => "function",
            Self::Ctrl => "ctrl",
            Self::CtrlLeft => "ctrl_left",
            Self::CtrlRight => "ctrl_right",
            Self::Meta => "meta",
            Self::MetaLeft => "meta_left",
            Self::MetaRight => "meta_right",
            Self::CapsLock => "caps_lock",
            Self::NumLock => "num_lock",
            Self::ScrollLock => "scroll_lock",
            Self::Primary => "primary",
        }
    }
}

impl Serialize for Modifier {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        s.serialize_str(self.as_str())
    }
}

// ─── BindingAction ───────────────────────────────────────────────────────────

/// The keyboard event phase that triggers a key binding.
///
/// Named `BindingAction` (rather than `KeyboardAction`) to avoid confusion
/// with [`crate::input::KeyboardAction`], which carries additional runtime
/// variants that have no meaning in a configuration context.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum BindingAction {
    /// Key was pressed. This is the most common trigger for bindings.
    #[default]
    Down,
    /// Key was released.
    Up,
    /// Key is being held and auto-repeating.
    Repeat,
}

impl BindingAction {
    /// Returns the string representation expected by the compositor.
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Down => "down",
            Self::Up => "up",
            Self::Repeat => "repeat",
        }
    }
}

impl Serialize for BindingAction {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        s.serialize_str(self.as_str())
    }
}

// ─── Key ─────────────────────────────────────────────────────────────────────

/// An XKB keysym name for use in configuration bindings.
///
/// Examples: `Key::new("Return")`, `Key::new("a")`, `Key::new("Up")`,
/// `Key::new("F5")`.
///
/// The compositor validates the name using `xkb_keysym_from_name`. A full
/// list of valid names is available at:
/// <https://xkbcommon.org/doc/current/xkbcommon-keysyms_8h.html>
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct Key(pub String);

impl Key {
    /// Create a key from an XKB keysym name.
    pub fn new(name: impl Into<String>) -> Self {
        Self(name.into())
    }
}

impl<S: Into<String>> From<S> for Key {
    fn from(s: S) -> Self {
        Self(s.into())
    }
}

impl Serialize for Key {
    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        s.serialize_str(&self.0)
    }
}

// ─── Handedness ──────────────────────────────────────────────────────────────

/// Mouse button handedness.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Handedness {
    #[default]
    Right,
    Left,
}

// ─── PointerAcceleration ─────────────────────────────────────────────────────

/// Pointer acceleration profile.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PointerAcceleration {
    Adaptive,
    #[default]
    None,
}

// ─── CursorFocusMode ─────────────────────────────────────────────────────────

/// Whether focus follows the pointer or requires a click.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum CursorFocusMode {
    #[default]
    Hover,
    Click,
}

// ─── TouchpadClickMode ───────────────────────────────────────────────────────

/// Touchpad click emulation mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TouchpadClickMode {
    #[default]
    None,
    AreaToClick,
    FingerCount,
}

// ─── TouchpadScrollMode ──────────────────────────────────────────────────────

/// Touchpad scroll method.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TouchpadScrollMode {
    #[default]
    None,
    TwoFingerScroll,
    EdgeScroll,
    ButtonDownScroll,
}

// ─── AnimationPartType ───────────────────────────────────────────────────────

/// Built-in animation visual effect for one phase of an animation sequence.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AnimationPartType {
    #[default]
    Disabled,
    Slide,
    Grow,
    Shrink,
    Fade,
}

// ─── EasingFunction ──────────────────────────────────────────────────────────

/// Easing function for animation timing.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EasingFunction {
    #[default]
    Linear,
    EaseInSine,
    EaseOutSine,
    EaseInOutSine,
    EaseInQuad,
    EaseOutQuad,
    EaseInOutQuad,
    EaseInCubic,
    EaseOutCubic,
    EaseInOutCubic,
    EaseInQuart,
    EaseOutQuart,
    EaseInOutQuart,
    EaseInQuint,
    EaseOutQuint,
    EaseInOutQuint,
    EaseInExpo,
    EaseOutExpo,
    EaseInOutExpo,
    EaseInCirc,
    EaseOutCirc,
    EaseInOutCirc,
    EaseInBack,
    EaseOutBack,
    EaseInOutBack,
    EaseInElastic,
    EaseOutElastic,
    EaseInOutElastic,
    EaseInBounce,
    EaseOutBounce,
    EaseInOutBounce,
}

// ─── AnimationEvent ──────────────────────────────────────────────────────────

/// The compositor event that an animation definition applies to.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AnimationEvent {
    #[default]
    WindowOpen,
    WindowMove,
    WindowClose,
    WorkspaceSwitch,
}

// ─── AnimationKind ───────────────────────────────────────────────────────────

/// Whether an animation is driven by a built-in effect or a plugin callback.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AnimationKind {
    #[default]
    BuiltIn,
    Plugin,
}

// ─── Config structs ──────────────────────────────────────────────────────────

/// Gaps configuration. Both `x` (left/right) and `y` (top/bottom) are in pixels.
#[derive(Debug, Clone, Default, Serialize)]
pub struct Gaps {
    pub x: i32,
    pub y: i32,
}

/// A custom key binding that runs a shell command.
#[derive(Debug, Clone, Serialize)]
pub struct CustomKeyAction {
    /// The keyboard event phase that triggers this binding.
    pub action: BindingAction,
    /// The modifier keys required for this binding.
    pub modifiers: Vec<Modifier>,
    /// The XKB keysym name (e.g. `Key::new("Return")`, `Key::new("a")`).
    pub key: Key,
    /// The shell command to execute.
    pub command: String,
}

/// Override the key binding for a built-in compositor action.
#[derive(Debug, Clone, Serialize)]
pub struct BuiltInKeyCommandOverride {
    /// Name of the built-in action (e.g. `"terminal"`, `"close_window"`).
    pub name: String,
    /// The keyboard event phase that triggers this binding.
    pub action: BindingAction,
    /// The modifier keys required for this binding.
    pub modifiers: Vec<Modifier>,
    /// The XKB keysym name.
    pub key: Key,
}

/// An application to start on compositor launch.
#[derive(Debug, Clone, Default, Serialize)]
pub struct StartupApp {
    pub command: String,
    #[serde(skip_serializing_if = "is_false")]
    pub restart_on_death: bool,
    #[serde(skip_serializing_if = "is_false")]
    pub no_startup_id: bool,
    #[serde(skip_serializing_if = "is_false")]
    pub should_halt_compositor_on_death: bool,
    #[serde(skip_serializing_if = "is_false")]
    pub in_systemd_scope: bool,
}

/// An environment variable to set in the compositor's environment.
#[derive(Debug, Clone, Default, Serialize)]
pub struct EnvironmentVariable {
    pub key: String,
    pub value: String,
}

/// Window border appearance.
#[derive(Debug, Clone, Default, Serialize)]
pub struct BorderConfig {
    pub size: i32,
    pub radius: f32,
    /// Color as a hex string (`"RRGGBBAA"`) or an RGBA array `[r, g, b, a]` (0–255).
    pub color: String,
    /// Focused-window color as a hex string or RGBA array.
    pub focus_color: String,
}

/// Workspace configuration entry.
#[derive(Debug, Clone, Default, Serialize)]
pub struct WorkspaceConfig {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub number: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
}

/// Drag-and-drop behaviour.
#[derive(Debug, Clone, Serialize)]
pub struct DragAndDropConfiguration {
    pub enabled: bool,
    /// The modifier keys required to initiate a drag-and-drop operation.
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub modifiers: Vec<Modifier>,
}

impl Default for DragAndDropConfiguration {
    fn default() -> Self {
        Self {
            enabled: true,
            modifiers: Vec::new(),
        }
    }
}

/// A single built-in animation (one phase of an easing sequence).
#[derive(Debug, Clone, Default, Serialize)]
pub struct BuiltInAnimationPart {
    /// The visual effect for this animation phase.
    #[serde(rename = "type")]
    pub type_: AnimationPartType,
    /// The easing function that controls timing.
    pub function: EasingFunction,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c1: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c2: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c3: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub c4: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub n1: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub d1: Option<f32>,
}

/// An animation definition for one animatable event.
#[derive(Debug, Clone, Default, Serialize)]
pub struct AnimationDefinition {
    /// The compositor event to animate.
    pub event: AnimationEvent,
    /// Whether to use a built-in animation effect or a plugin callback.
    #[serde(rename = "type")]
    pub type_: AnimationKind,
    /// Duration in seconds.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration: Option<f32>,
    /// The list of animation phases (required when `type_` is `BuiltIn`).
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub parts: Vec<BuiltInAnimationPart>,
}

/// Mouse pointer configuration.
#[derive(Debug, Clone, Default, Serialize)]
pub struct MouseConfiguration {
    /// Swap left and right buttons.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub handedness: Option<Handedness>,
    /// Pointer acceleration profile.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acceleration: Option<PointerAcceleration>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acceleration_bias: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub vscroll_speed: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hscroll_speed: Option<f64>,
}

/// Keymap (keyboard layout) configuration.
#[derive(Debug, Clone, Default, Serialize)]
pub struct KeymapConfiguration {
    pub language: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub variant: Option<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub options: Vec<String>,
}

/// Keyboard repeat and layout configuration.
#[derive(Debug, Clone, Default, Serialize)]
pub struct KeyboardConfiguration {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub repeat_delay: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub repeat_rate: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub keymap: Option<KeymapConfiguration>,
}

/// Hover-click (dwell click) configuration.
#[derive(Debug, Clone, Default, Serialize)]
pub struct HoverClickConfiguration {
    pub enabled: bool,
    /// How long (ms) the pointer must hover before a click is generated.
    #[serde(skip_serializing_if = "Option::is_none", rename = "hover_duration")]
    pub hover_duration_ms: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cancel_displacement_threshold: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reclick_displacement_threshold: Option<i32>,
}

/// Simulated secondary (right) click via long-press.
#[derive(Debug, Clone, Default, Serialize)]
pub struct SimulatedSecondaryClickConfiguration {
    pub enabled: bool,
    /// How long (ms) to hold before the secondary click is generated.
    #[serde(skip_serializing_if = "Option::is_none", rename = "hold_duration")]
    pub hold_duration_ms: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub displacement_threshold: Option<i32>,
}

/// Output (display) filter shader.
#[derive(Debug, Clone, Default, Serialize)]
pub struct OutputFilterConfiguration {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub shader_path: Option<String>,
}

/// Cursor appearance and focus behaviour.
#[derive(Debug, Clone, Default, Serialize)]
pub struct CursorConfiguration {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scale: Option<f32>,
    /// Whether focus follows hover or requires a click.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub focus_mode: Option<CursorFocusMode>,
}

/// Slow keys (accessibility) configuration.
#[derive(Debug, Clone, Default, Serialize)]
pub struct SlowKeysConfiguration {
    pub enabled: bool,
    /// How long (ms) a key must be held before it registers.
    #[serde(skip_serializing_if = "Option::is_none", rename = "hold_delay")]
    pub hold_delay_ms: Option<u32>,
}

/// Sticky keys (accessibility) configuration.
#[derive(Debug, Clone, Default, Serialize)]
pub struct StickyKeysConfiguration {
    pub enabled: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub should_disable_if_two_keys_are_pressed_together: Option<bool>,
}

/// Touchpad configuration.
#[derive(Debug, Clone, Default, Serialize)]
pub struct TouchpadConfiguration {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub disable_while_typing: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub disable_with_external_mouse: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub acceleration_bias: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub vscroll_speed: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hscroll_speed: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tap_to_click: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub middle_mouse_button_emulation: Option<bool>,
    /// Touchpad click emulation mode.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub click_mode: Option<TouchpadClickMode>,
    /// Touchpad scroll method.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scroll_mode: Option<TouchpadScrollMode>,
}

/// Screen magnifier configuration.
#[derive(Debug, Clone, Default, Serialize)]
pub struct MagnifierConfiguration {
    pub enabled: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scale: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scale_increment: Option<f32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub width: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub height: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub size_increment: Option<i32>,
}

/// Configuration overrides that a plugin may return from [`Plugin::configure`].
///
/// Every field is optional. `None` means "do not override this value". The
/// compositor merges all loaded plugins' results and then merges the combined
/// result with the file-based configuration (plugin values win on conflict).
///
/// The `plugins` and `includes` keys of the compositor config cannot be set
/// by plugins and are intentionally absent from this struct.
#[derive(Debug, Clone, Default, Serialize)]
pub struct Configuration {
    /// The primary modifier key (e.g. `Modifier::Meta` for the Super/Windows key).
    #[serde(skip_serializing_if = "Option::is_none", rename = "action_key")]
    pub primary_modifier: Option<Modifier>,

    /// Custom key bindings that run shell commands.
    #[serde(skip_serializing_if = "Option::is_none", rename = "custom_actions")]
    pub custom_key_actions: Option<Vec<CustomKeyAction>>,

    /// Overrides for built-in compositor key bindings.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default_action_overrides: Option<Vec<BuiltInKeyCommandOverride>>,

    /// Inner (between windows) gap size.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub inner_gaps: Option<Gaps>,

    /// Outer (screen edge) gap size.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub outer_gaps: Option<Gaps>,

    /// Applications to launch on startup.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub startup_apps: Option<Vec<StartupApp>>,

    /// Override the default terminal emulator command.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub terminal: Option<String>,

    /// Pixel amount to jump when resizing with keyboard shortcuts.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub resize_jump: Option<i32>,

    /// Extra environment variables to set in the compositor process.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub environment_variables: Option<Vec<EnvironmentVariable>>,

    /// Window border appearance.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub border: Option<BorderConfig>,

    /// Workspace layout definitions.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub workspaces: Option<Vec<WorkspaceConfig>>,

    /// Animation definitions per event. Each entry names an `event` plus the definition.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub animations: Option<Vec<AnimationDefinition>>,

    /// Whether animations are globally enabled.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub enable_animations: Option<bool>,

    /// The modifier keys used for window move operations.
    /// Use `vec![Modifier::Primary]` to follow the user's primary modifier.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub move_modifier: Option<Vec<Modifier>>,

    /// Drag-and-drop behaviour.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub drag_and_drop: Option<DragAndDropConfiguration>,

    /// Mouse pointer settings.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub mouse: Option<MouseConfiguration>,

    /// Touchpad settings.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub touchpad: Option<TouchpadConfiguration>,

    /// Keyboard repeat rate, delay, and keymap.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub keyboard: Option<KeyboardConfiguration>,

    /// Hover-click (dwell click) accessibility feature.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hover_click: Option<HoverClickConfiguration>,

    /// Simulated secondary click accessibility feature.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub simulated_secondary_click: Option<SimulatedSecondaryClickConfiguration>,

    /// Output (display) post-processing filter.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output_filter: Option<OutputFilterConfiguration>,

    /// Cursor appearance and focus behaviour.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cursor: Option<CursorConfiguration>,

    /// Slow keys accessibility feature.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub slow_keys: Option<SlowKeysConfiguration>,

    /// Sticky keys accessibility feature.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sticky_keys: Option<StickyKeysConfiguration>,

    /// Screen magnifier.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub magnifier: Option<MagnifierConfiguration>,

    /// Whether switching to the current workspace goes back to the previous one.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub workspace_back_and_forth: Option<bool>,
}

fn is_false(v: &bool) -> bool {
    !v
}