Skip to main content

jay_toml_config/
lib.rs

1#![allow(
2    clippy::len_zero,
3    clippy::single_char_pattern,
4    clippy::collapsible_if,
5    clippy::collapsible_else_if
6)]
7
8mod config;
9mod rules;
10mod shortcuts;
11mod toml;
12
13use {
14    crate::{
15        config::{
16            Action, ClientRule, Config, ConfigConnector, ConfigDrmDevice, ConfigKeymap,
17            ConnectorMatch, DrmDeviceMatch, Exec, Input, InputMatch, Output, OutputMatch,
18            SimpleCommand, Status, Theme, WindowRule, parse_config,
19        },
20        rules::{MatcherTemp, RuleMapper},
21        shortcuts::ModeState,
22    },
23    ahash::{AHashMap, AHashSet},
24    error_reporter::Report,
25    jay_config::{
26        client::Client,
27        config, config_dir,
28        exec::{Command, set_env, unset_env},
29        get_workspace,
30        input::{
31            FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, capability::CAP_SWITCH,
32            get_seat, input_devices, on_input_device_removed, on_new_input_device,
33            set_libei_socket_enabled,
34        },
35        io::Async,
36        is_reload,
37        keyboard::Keymap,
38        logging::set_log_level,
39        on_devices_enumerated, on_idle, on_unload, open_control_center, quit, reload,
40        set_color_management_enabled, set_default_workspace_capture, set_explicit_sync_enabled,
41        set_float_above_fullscreen, set_idle, set_idle_grace_period,
42        set_middle_click_paste_enabled, set_show_bar, set_show_float_pin_icon, set_show_titles,
43        set_ui_drag_enabled, set_ui_drag_threshold,
44        status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
45        switch_to_vt,
46        tasks::{self, JoinHandle},
47        theme::{
48            reset_colors, reset_font, reset_sizes, set_bar_font, set_bar_position,
49            set_egui_monospace_fonts, set_egui_proportional_fonts, set_font, set_title_font,
50        },
51        toggle_float_above_fullscreen, toggle_show_bar, toggle_show_titles,
52        video::{
53            ColorSpace, Connector, DrmDevice, Eotf, connectors, drm_devices,
54            on_connector_connected, on_connector_disconnected, on_graphics_initialized,
55            on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api,
56            set_tearing_mode, set_vrr_cursor_hz, set_vrr_mode,
57        },
58        window::Window,
59        workspace::set_workspace_display_order,
60        xwayland::{set_x_scaling_mode, set_x_wayland_enabled},
61    },
62    run_on_drop::on_drop,
63    std::{
64        cell::{Cell, RefCell},
65        ffi::OsStr,
66        io::ErrorKind,
67        os::{fd::AsRawFd, unix::ffi::OsStrExt},
68        path::{Path, PathBuf},
69        rc::Rc,
70        time::Duration,
71    },
72    uapi::{
73        Errno,
74        c::{
75            self, CLOCK_MONOTONIC, IN_ATTRIB, IN_CLOEXEC, IN_CLOSE_WRITE, IN_CREATE, IN_DELETE,
76            IN_EXCL_UNLINK, IN_MOVED_FROM, IN_MOVED_TO, IN_NONBLOCK, IN_ONLYDIR, TFD_CLOEXEC,
77            TFD_NONBLOCK, timespec,
78        },
79    },
80};
81
82fn default_seat() -> Seat {
83    get_seat("default")
84}
85
86trait FnBuilder: Sized {
87    type Output;
88
89    #[expect(clippy::wrong_self_convention)]
90    fn new<F: Fn() + 'static>(&self, f: F) -> Self::Output;
91}
92
93struct BoxFnBuilder;
94
95impl FnBuilder for BoxFnBuilder {
96    type Output = Box<dyn Fn()>;
97
98    fn new<F: Fn() + 'static>(&self, f: F) -> Self::Output {
99        Box::new(f)
100    }
101}
102
103struct RcFnBuilder;
104
105impl FnBuilder for RcFnBuilder {
106    type Output = Rc<dyn Fn()>;
107
108    fn new<F: Fn() + 'static>(&self, f: F) -> Self::Output {
109        Rc::new(f)
110    }
111}
112
113struct ShortcutFnBuilder<'a>(&'a Rc<State>);
114
115impl FnBuilder for ShortcutFnBuilder<'_> {
116    type Output = Rc<dyn Fn()>;
117
118    fn new<F: Fn() + 'static>(&self, f: F) -> Self::Output {
119        let state = self.0.clone();
120        Rc::new(move || {
121            state.cancel_mode_latch();
122            f();
123        })
124    }
125}
126
127impl Action {
128    fn into_fn(self, state: &Rc<State>) -> Box<dyn Fn()> {
129        self.into_fn_impl(&BoxFnBuilder, state)
130    }
131
132    fn into_rc_fn(self, state: &Rc<State>) -> Rc<dyn Fn()> {
133        self.into_fn_impl(&RcFnBuilder, state)
134    }
135
136    fn into_shortcut_fn(self, state: &Rc<State>) -> Rc<dyn Fn()> {
137        self.into_fn_impl(&ShortcutFnBuilder(state), state)
138    }
139
140    fn into_fn_impl<B: FnBuilder>(self, b: &B, state: &Rc<State>) -> B::Output {
141        macro_rules! client_action {
142            ($name:ident, $opt:expr) => {{
143                let state = state.clone();
144                b.new(move || {
145                    if let Some($name) = state.client.get() {
146                        $opt
147                    }
148                })
149            }};
150        }
151        let s = state.persistent.seat;
152        macro_rules! window_or_seat {
153            ($name:ident, $expr:expr) => {{
154                let state = state.clone();
155                b.new(move || {
156                    if let Some($name) = state.window.get() {
157                        if let Some($name) = $name {
158                            $expr;
159                        }
160                    } else {
161                        let $name = s;
162                        $expr;
163                    }
164                })
165            }};
166        }
167        match self {
168            Action::SimpleCommand { cmd } => match cmd {
169                SimpleCommand::Focus(dir) => b.new(move || s.focus(dir)),
170                SimpleCommand::Move(dir) => window_or_seat!(s, s.move_(dir)),
171                SimpleCommand::Split(axis) => window_or_seat!(s, s.create_split(axis)),
172                SimpleCommand::ToggleSplit => window_or_seat!(s, s.toggle_split()),
173                SimpleCommand::SetSplit(b) => window_or_seat!(s, s.set_split(b)),
174                SimpleCommand::ToggleMono => window_or_seat!(s, s.toggle_mono()),
175                SimpleCommand::SetMono(b) => window_or_seat!(s, s.set_mono(b)),
176                SimpleCommand::ToggleFullscreen => window_or_seat!(s, s.toggle_fullscreen()),
177                SimpleCommand::SetFullscreen(b) => window_or_seat!(s, s.set_fullscreen(b)),
178                SimpleCommand::FocusParent => b.new(move || s.focus_parent()),
179                SimpleCommand::Close => window_or_seat!(s, s.close()),
180                SimpleCommand::DisablePointerConstraint => {
181                    b.new(move || s.disable_pointer_constraint())
182                }
183                SimpleCommand::ToggleFloating => window_or_seat!(s, s.toggle_floating()),
184                SimpleCommand::SetFloating(b) => window_or_seat!(s, s.set_floating(b)),
185                SimpleCommand::Quit => b.new(quit),
186                SimpleCommand::ReloadConfigToml => {
187                    let persistent = state.persistent.clone();
188                    b.new(move || load_config(false, false, &persistent))
189                }
190                SimpleCommand::ReloadConfigSo => b.new(reload),
191                SimpleCommand::None => b.new(|| ()),
192                SimpleCommand::Forward(bool) => b.new(move || s.set_forward(bool)),
193                SimpleCommand::EnableWindowManagement(bool) => {
194                    b.new(move || s.set_window_management_enabled(bool))
195                }
196                SimpleCommand::SetFloatAboveFullscreen(bool) => {
197                    b.new(move || set_float_above_fullscreen(bool))
198                }
199                SimpleCommand::ToggleFloatAboveFullscreen => b.new(toggle_float_above_fullscreen),
200                SimpleCommand::SetFloatPinned(pinned) => {
201                    window_or_seat!(s, s.set_float_pinned(pinned))
202                }
203                SimpleCommand::ToggleFloatPinned => window_or_seat!(s, s.toggle_float_pinned()),
204                SimpleCommand::KillClient => client_action!(c, c.kill()),
205                SimpleCommand::ShowBar(show) => b.new(move || set_show_bar(show)),
206                SimpleCommand::ToggleBar => b.new(toggle_show_bar),
207                SimpleCommand::ShowTitles(show) => b.new(move || set_show_titles(show)),
208                SimpleCommand::ToggleTitles => b.new(toggle_show_titles),
209                SimpleCommand::FocusHistory(timeline) => {
210                    let persistent = state.persistent.clone();
211                    b.new(move || persistent.seat.focus_history(timeline))
212                }
213                SimpleCommand::FocusLayerRel(direction) => {
214                    let persistent = state.persistent.clone();
215                    b.new(move || persistent.seat.focus_layer_rel(direction))
216                }
217                SimpleCommand::FocusTiles => {
218                    let persistent = state.persistent.clone();
219                    b.new(move || persistent.seat.focus_tiles())
220                }
221                SimpleCommand::CreateMark => {
222                    let persistent = state.persistent.clone();
223                    b.new(move || persistent.seat.create_mark(None))
224                }
225                SimpleCommand::JumpToMark => {
226                    let persistent = state.persistent.clone();
227                    b.new(move || persistent.seat.jump_to_mark(None))
228                }
229                SimpleCommand::PopMode(pop) => {
230                    let state = state.clone();
231                    b.new(move || state.pop_mode(pop))
232                }
233                SimpleCommand::EnableSimpleIm(v) => {
234                    let persistent = state.persistent.clone();
235                    b.new(move || persistent.seat.set_simple_im_enabled(v))
236                }
237                SimpleCommand::ToggleSimpleImEnabled => {
238                    let persistent = state.persistent.clone();
239                    b.new(move || persistent.seat.toggle_simple_im_enabled())
240                }
241                SimpleCommand::ReloadSimpleIm => {
242                    let persistent = state.persistent.clone();
243                    b.new(move || persistent.seat.reload_simple_im())
244                }
245                SimpleCommand::EnableUnicodeInput => {
246                    let persistent = state.persistent.clone();
247                    b.new(move || persistent.seat.enable_unicode_input())
248                }
249                SimpleCommand::OpenControlCenter => b.new(open_control_center),
250            },
251            Action::Multi { actions } => {
252                let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
253                b.new(move || {
254                    for action in &actions {
255                        action();
256                    }
257                })
258            }
259            Action::Exec { exec } => b.new(move || create_command(&exec).spawn()),
260            Action::SwitchToVt { num } => b.new(move || switch_to_vt(num)),
261            Action::ShowWorkspace { name, output } => {
262                let workspace = get_workspace(&name);
263                let state = state.clone();
264                b.new(move || {
265                    let output = 'get_output: {
266                        let Some(output) = &output else {
267                            break 'get_output None;
268                        };
269                        for connector in connectors() {
270                            if connector.connected() && output.matches(connector, &state) {
271                                break 'get_output Some(connector);
272                            }
273                        }
274                        None
275                    };
276                    match output {
277                        Some(o) => s.show_workspace_on(workspace, o),
278                        _ => s.show_workspace(workspace),
279                    }
280                })
281            }
282            Action::MoveToWorkspace { name } => {
283                let workspace = get_workspace(&name);
284                window_or_seat!(s, s.set_workspace(workspace))
285            }
286            Action::ConfigureConnector { con } => b.new(move || {
287                for c in connectors() {
288                    if con.match_.matches(c) {
289                        con.apply(c);
290                    }
291                }
292            }),
293            Action::ConfigureInput { input } => {
294                let state = state.clone();
295                b.new(move || {
296                    for c in input_devices() {
297                        if input.match_.matches(c, &state) {
298                            input.apply(c, &state);
299                        }
300                    }
301                })
302            }
303            Action::ConfigureOutput { out } => {
304                let state = state.clone();
305                b.new(move || {
306                    for c in connectors() {
307                        if out.match_.matches(c, &state) {
308                            out.apply(c);
309                        }
310                    }
311                })
312            }
313            Action::SetEnv { env } => b.new(move || {
314                for (k, v) in &env {
315                    set_env(k, v);
316                }
317            }),
318            Action::UnsetEnv { env } => b.new(move || {
319                for k in &env {
320                    unset_env(k);
321                }
322            }),
323            Action::SetKeymap { map } => {
324                let state = state.clone();
325                b.new(move || state.set_keymap(&map))
326            }
327            Action::SetStatus { status } => {
328                let state = state.clone();
329                b.new(move || state.set_status(&status))
330            }
331            Action::SetTheme { theme } => {
332                let state = state.clone();
333                b.new(move || state.apply_theme(&theme))
334            }
335            Action::SetLogLevel { level } => b.new(move || set_log_level(level)),
336            Action::SetGfxApi { api } => b.new(move || set_gfx_api(api)),
337            Action::ConfigureDirectScanout { enabled } => {
338                b.new(move || set_direct_scanout_enabled(enabled))
339            }
340            Action::ConfigureDrmDevice { dev } => {
341                let state = state.clone();
342                b.new(move || {
343                    for d in drm_devices() {
344                        if dev.match_.matches(d, &state) {
345                            dev.apply(d);
346                        }
347                    }
348                })
349            }
350            Action::SetRenderDevice { dev } => {
351                let state = state.clone();
352                b.new(move || {
353                    for d in drm_devices() {
354                        if dev.matches(d, &state) {
355                            d.make_render_device();
356                        }
357                    }
358                })
359            }
360            Action::ConfigureIdle { idle, grace_period } => b.new(move || {
361                if let Some(idle) = idle {
362                    set_idle(Some(idle))
363                }
364                if let Some(period) = grace_period {
365                    set_idle_grace_period(period)
366                }
367            }),
368            Action::MoveToOutput {
369                output,
370                workspace,
371                direction,
372            } => {
373                let state = state.clone();
374                b.new(move || {
375                    let target_output = {
376                        // Handle directional output selection
377                        if let Some(direction) = direction {
378                            // Get the current workspace to determine the source output
379                            let current_ws = match workspace {
380                                Some(ws) => ws,
381                                None => s.get_workspace(),
382                            };
383                            if !current_ws.exists() {
384                                return;
385                            }
386                            // Get the connector that currently has this workspace
387                            let source_connector = current_ws.connector();
388                            if !source_connector.exists() {
389                                return;
390                            }
391                            // Find the connector in the given direction
392                            let target = source_connector.connector_in_direction(direction);
393                            if !target.exists() {
394                                return;
395                            }
396                            target
397                        } else if let Some(output) = &output {
398                            // Handle normal output matching
399                            'match_output: {
400                                for connector in connectors() {
401                                    if connector.connected() && output.matches(connector, &state) {
402                                        break 'match_output connector;
403                                    }
404                                }
405                                return;
406                            }
407                        } else {
408                            return;
409                        }
410                    };
411                    match workspace {
412                        Some(ws) => ws.move_to_output(target_output),
413                        None => s.move_to_output(target_output),
414                    }
415                })
416            }
417            Action::SetRepeatRate { rate } => {
418                b.new(move || s.set_repeat_rate(rate.rate, rate.delay))
419            }
420            Action::DefineAction { name, action } => {
421                let state = state.clone();
422                let action = action.into_rc_fn(&state);
423                let name = Rc::new(name);
424                b.new(move || {
425                    state
426                        .persistent
427                        .actions
428                        .borrow_mut()
429                        .insert(name.clone(), action.clone());
430                })
431            }
432            Action::UndefineAction { name } => {
433                let state = state.clone();
434                b.new(move || {
435                    state.persistent.actions.borrow_mut().remove(&name);
436                })
437            }
438            Action::NamedAction { name } => {
439                let state = state.clone();
440                b.new(move || {
441                    let depth = state.action_depth.get();
442                    if depth >= state.action_depth_max {
443                        log::error!("Maximum action depth reached");
444                        return;
445                    }
446                    state.action_depth.set(depth + 1);
447                    let _reset = on_drop(|| state.action_depth.set(depth));
448                    let Some(action) = state.persistent.actions.borrow().get(&name).cloned() else {
449                        log::error!("There is no action named {name}");
450                        return;
451                    };
452                    action();
453                })
454            }
455            Action::CreateMark(m) => {
456                let persistent = state.persistent.clone();
457                b.new(move || persistent.seat.create_mark(Some(m)))
458            }
459            Action::JumpToMark(m) => {
460                let persistent = state.persistent.clone();
461                b.new(move || persistent.seat.jump_to_mark(Some(m)))
462            }
463            Action::CopyMark(s, d) => {
464                let persistent = state.persistent.clone();
465                b.new(move || persistent.seat.copy_mark(s, d))
466            }
467            Action::SetMode { name, latch } => {
468                let state = state.clone();
469                let new = state.get_mode_slot(&name);
470                b.new(move || {
471                    let new = new.mode.borrow();
472                    let Some(new) = new.as_ref() else {
473                        log::warn!("Input mode {name} does not exist");
474                        return;
475                    };
476                    state.set_mode(new, latch);
477                })
478            }
479        }
480    }
481}
482
483fn apply_recursive_match<'a, U>(
484    type_name: &str,
485    list: &'a AHashMap<String, U>,
486    active: &mut AHashSet<&'a str>,
487    name: &'a str,
488    matches: impl FnOnce(&'a U, &mut AHashSet<&'a str>) -> bool,
489) -> bool {
490    match list.get(name) {
491        None => {
492            log::warn!("{type_name} with name {name} does not exist");
493            false
494        }
495        Some(m) => {
496            if active.insert(name) {
497                let matches = matches(m, active);
498                active.remove(name);
499                matches
500            } else {
501                log::warn!("Recursion while evaluating match for {type_name} {name}");
502                false
503            }
504        }
505    }
506}
507
508impl ConfigDrmDevice {
509    fn apply(&self, d: DrmDevice) {
510        if let Some(api) = self.gfx_api {
511            d.set_gfx_api(api);
512        }
513        if let Some(dse) = self.direct_scanout_enabled {
514            d.set_direct_scanout_enabled(dse);
515        }
516        if let Some(fm) = self.flip_margin_ms {
517            d.set_flip_margin(Duration::from_nanos((fm * 1_000_000.0) as _));
518        }
519    }
520}
521
522impl DrmDeviceMatch {
523    fn matches(&self, d: DrmDevice, state: &State) -> bool {
524        self.matches_(d, state, &mut AHashSet::new())
525    }
526
527    fn matches_<'a>(
528        &'a self,
529        d: DrmDevice,
530        state: &'a State,
531        active: &mut AHashSet<&'a str>,
532    ) -> bool {
533        match self {
534            DrmDeviceMatch::Any(m) => m.iter().any(|m| m.matches_(d, state, active)),
535            DrmDeviceMatch::All {
536                name,
537                syspath,
538                vendor,
539                vendor_name,
540                model,
541                model_name,
542                devnode,
543            } => {
544                if let Some(name) = name {
545                    let matches = apply_recursive_match(
546                        "drm device",
547                        &state.drm_devices,
548                        active,
549                        name,
550                        |m, active| m.matches_(d, state, active),
551                    );
552                    if !matches {
553                        return false;
554                    }
555                }
556                if let Some(syspath) = syspath
557                    && d.syspath() != *syspath
558                {
559                    return false;
560                }
561                if let Some(devnode) = devnode
562                    && d.devnode() != *devnode
563                {
564                    return false;
565                }
566                if let Some(model) = model_name
567                    && d.model() != *model
568                {
569                    return false;
570                }
571                if let Some(vendor) = vendor_name
572                    && d.vendor() != *vendor
573                {
574                    return false;
575                }
576                if let Some(vendor) = vendor
577                    && d.pci_id().vendor != *vendor
578                {
579                    return false;
580                }
581                if let Some(model) = model
582                    && d.pci_id().model != *model
583                {
584                    return false;
585                }
586                true
587            }
588        }
589    }
590}
591
592impl InputMatch {
593    fn matches(&self, d: InputDevice, state: &State) -> bool {
594        self.matches_(d, state, &mut AHashSet::new())
595    }
596
597    fn matches_<'a>(
598        &'a self,
599        d: InputDevice,
600        state: &'a State,
601        active: &mut AHashSet<&'a str>,
602    ) -> bool {
603        match self {
604            InputMatch::Any(m) => m.iter().any(|m| m.matches_(d, state, active)),
605            InputMatch::All {
606                tag,
607                name,
608                syspath,
609                devnode,
610                is_keyboard,
611                is_pointer,
612                is_touch,
613                is_tablet_tool,
614                is_tablet_pad,
615                is_gesture,
616                is_switch,
617            } => {
618                if let Some(name) = name
619                    && d.name() != *name
620                {
621                    return false;
622                }
623                if let Some(tag) = tag {
624                    let matches = apply_recursive_match(
625                        "input device",
626                        &state.input_devices,
627                        active,
628                        tag,
629                        |m, active| m.matches_(d, state, active),
630                    );
631                    if !matches {
632                        return false;
633                    }
634                }
635                if let Some(syspath) = syspath
636                    && d.syspath() != *syspath
637                {
638                    return false;
639                }
640                if let Some(devnode) = devnode
641                    && d.devnode() != *devnode
642                {
643                    return false;
644                }
645                macro_rules! check_cap {
646                    ($is:expr, $cap:ident) => {
647                        if let Some(is) = *$is
648                            && d.has_capability(jay_config::input::capability::$cap) != is
649                        {
650                            return false;
651                        }
652                    };
653                }
654                check_cap!(is_keyboard, CAP_KEYBOARD);
655                check_cap!(is_pointer, CAP_POINTER);
656                check_cap!(is_touch, CAP_TOUCH);
657                check_cap!(is_tablet_tool, CAP_TABLET_TOOL);
658                check_cap!(is_tablet_pad, CAP_TABLET_PAD);
659                check_cap!(is_gesture, CAP_GESTURE);
660                check_cap!(is_switch, CAP_SWITCH);
661                true
662            }
663        }
664    }
665}
666
667impl Input {
668    fn apply(&self, c: InputDevice, state: &State) {
669        if let Some(v) = self.accel_profile {
670            c.set_accel_profile(v);
671        }
672        if let Some(v) = self.accel_speed {
673            c.set_accel_speed(v);
674        }
675        if let Some(v) = self.tap_enabled {
676            c.set_tap_enabled(v);
677        }
678        if let Some(v) = self.tap_drag_enabled {
679            c.set_drag_enabled(v);
680        }
681        if let Some(v) = self.tap_drag_lock_enabled {
682            c.set_drag_lock_enabled(v);
683        }
684        if let Some(v) = self.left_handed {
685            c.set_left_handed(v);
686        }
687        if let Some(v) = self.natural_scrolling {
688            c.set_natural_scrolling_enabled(v);
689        }
690        if let Some(v) = self.px_per_wheel_scroll {
691            c.set_px_per_wheel_scroll(v);
692        }
693        if let Some(v) = self.transform_matrix {
694            c.set_transform_matrix(v);
695        }
696        if let Some(v) = &self.keymap
697            && let Some(km) = state.get_keymap(v)
698        {
699            c.set_keymap(km);
700        }
701        if let Some(output) = &self.output {
702            if let Some(output) = output {
703                for connector in connectors() {
704                    if output.matches(connector, state) {
705                        c.set_connector(connector);
706                    }
707                }
708            } else {
709                c.remove_mapping();
710            }
711        }
712        if let Some(v) = self.calibration_matrix {
713            c.set_calibration_matrix(v);
714        }
715        if let Some(v) = self.click_method {
716            c.set_click_method(v);
717        }
718        if let Some(v) = self.middle_button_emulation {
719            c.set_middle_button_emulation_enabled(v);
720        }
721    }
722}
723
724impl OutputMatch {
725    fn matches(&self, c: Connector, state: &State) -> bool {
726        if !c.connected() {
727            return false;
728        }
729        self.matches_(c, state, &mut AHashSet::new())
730    }
731
732    fn matches_<'a>(
733        &'a self,
734        c: Connector,
735        state: &'a State,
736        active: &mut AHashSet<&'a str>,
737    ) -> bool {
738        match self {
739            OutputMatch::Any(m) => m.iter().any(|m| m.matches_(c, state, active)),
740            OutputMatch::All {
741                name,
742                connector,
743                serial_number,
744                manufacturer,
745                model,
746            } => {
747                if let Some(name) = name {
748                    let matches = apply_recursive_match(
749                        "output",
750                        &state.outputs,
751                        active,
752                        name,
753                        |m, active| m.matches_(c, state, active),
754                    );
755                    if !matches {
756                        return false;
757                    }
758                }
759                if let Some(connector) = &connector
760                    && c.name() != *connector
761                {
762                    return false;
763                }
764                if let Some(serial_number) = &serial_number
765                    && c.serial_number() != *serial_number
766                {
767                    return false;
768                }
769                if let Some(manufacturer) = &manufacturer
770                    && c.manufacturer() != *manufacturer
771                {
772                    return false;
773                }
774                if let Some(model) = &model
775                    && c.model() != *model
776                {
777                    return false;
778                }
779                true
780            }
781        }
782    }
783}
784
785impl ConnectorMatch {
786    fn matches(&self, c: Connector) -> bool {
787        if !c.exists() {
788            return false;
789        }
790        match self {
791            ConnectorMatch::Any(m) => m.iter().any(|m| m.matches(c)),
792            ConnectorMatch::All { connector } => {
793                if let Some(connector) = &connector
794                    && c.name() != *connector
795                {
796                    return false;
797                }
798                true
799            }
800        }
801    }
802}
803
804impl ConfigConnector {
805    fn apply(&self, c: Connector) {
806        c.set_enabled(self.enabled);
807    }
808}
809
810impl Output {
811    fn apply(&self, c: Connector) {
812        if self.x.is_some() || self.y.is_some() {
813            let (old_x, old_y) = c.position();
814            c.set_position(self.x.unwrap_or(old_x), self.y.unwrap_or(old_y));
815        }
816        if let Some(scale) = self.scale {
817            c.set_scale(scale);
818        }
819        if let Some(transform) = self.transform {
820            c.set_transform(transform);
821        }
822        if let Some(mode) = &self.mode {
823            let modes = c.modes();
824            let m = modes.iter().find(|m| {
825                if m.width() != mode.width || m.height() != mode.height {
826                    return false;
827                }
828                match mode.refresh_rate {
829                    None => true,
830                    Some(rr) => m.refresh_rate() as f64 / 1000.0 == rr,
831                }
832            });
833            match m {
834                None => {
835                    log::warn!("Output {} does not support mode {mode}", c.name());
836                }
837                Some(m) => c.set_mode(m.width(), m.height(), Some(m.refresh_rate())),
838            }
839        }
840        if let Some(vrr) = &self.vrr {
841            if let Some(mode) = vrr.mode {
842                c.set_vrr_mode(mode);
843            }
844            if let Some(hz) = vrr.cursor_hz {
845                c.set_vrr_cursor_hz(hz);
846            }
847        }
848        if let Some(tearing) = &self.tearing
849            && let Some(mode) = tearing.mode
850        {
851            c.set_tearing_mode(mode);
852        }
853        if let Some(format) = self.format {
854            c.set_format(format);
855        }
856        if self.color_space.is_some() || self.eotf.is_some() {
857            let cs = self.color_space.unwrap_or(ColorSpace::DEFAULT);
858            let tf = self.eotf.unwrap_or(Eotf::DEFAULT);
859            c.set_colors(cs, tf);
860        }
861        if let Some(brightness) = self.brightness {
862            c.set_brightness(brightness);
863        }
864        if let Some(bs) = self.blend_space {
865            c.set_blend_space(bs);
866        }
867        if let Some(use_native_gamut) = self.use_native_gamut {
868            c.set_use_native_gamut(use_native_gamut);
869        }
870    }
871}
872
873struct State {
874    outputs: AHashMap<String, OutputMatch>,
875    drm_devices: AHashMap<String, DrmDeviceMatch>,
876    input_devices: AHashMap<String, InputMatch>,
877    persistent: Rc<PersistentState>,
878    keymaps: AHashMap<String, Keymap>,
879
880    io_maps: Vec<(InputMatch, OutputMatch)>,
881    io_inputs: RefCell<AHashMap<InputDevice, Vec<bool>>>,
882    io_outputs: RefCell<AHashMap<Connector, Vec<bool>>>,
883
884    action_depth_max: u64,
885    action_depth: Cell<u64>,
886
887    client: Cell<Option<Client>>,
888
889    window: Cell<Option<Option<Window>>>,
890}
891
892impl Drop for State {
893    fn drop(&mut self) {
894        for keymap in self.keymaps.values() {
895            keymap.destroy();
896        }
897    }
898}
899
900type SwitchActions = Vec<(InputMatch, AHashMap<SwitchEvent, Box<dyn Fn()>>)>;
901
902impl State {
903    fn get_keymap(&self, map: &ConfigKeymap) -> Option<Keymap> {
904        let map = match map {
905            ConfigKeymap::Named(n) => match self.keymaps.get(n) {
906                None => {
907                    log::warn!("Unknown keymap {n}");
908                    return None;
909                }
910                Some(m) => *m,
911            },
912            ConfigKeymap::Defined { map, .. } => *map,
913            ConfigKeymap::Literal(map) => *map,
914        };
915        Some(map)
916    }
917
918    fn set_keymap(&self, map: &ConfigKeymap) {
919        if let Some(map) = self.get_keymap(map) {
920            self.persistent.seat.set_keymap(map);
921        }
922    }
923
924    fn set_status(&self, status: &Option<Status>) {
925        set_status("");
926        match status {
927            None => unset_status_command(),
928            Some(s) => {
929                set_i3bar_separator(s.separator.as_deref().unwrap_or(" | "));
930                set_status_command(s.format, create_command(&s.exec))
931            }
932        }
933    }
934
935    fn apply_theme(&self, theme: &Theme) {
936        use jay_config::theme::{colors::*, sized::*};
937        macro_rules! color {
938            ($colorable:ident, $field:ident) => {
939                if let Some(color) = theme.$field {
940                    $colorable.set_color(color)
941                }
942            };
943        }
944        color!(
945            ATTENTION_REQUESTED_BACKGROUND_COLOR,
946            attention_requested_bg_color
947        );
948        color!(BACKGROUND_COLOR, bg_color);
949        color!(BAR_BACKGROUND_COLOR, bar_bg_color);
950        color!(BAR_STATUS_TEXT_COLOR, bar_status_text_color);
951        color!(BORDER_COLOR, border_color);
952        color!(
953            CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR,
954            captured_focused_title_bg_color
955        );
956        color!(
957            CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR,
958            captured_unfocused_title_bg_color
959        );
960        color!(
961            FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR,
962            focused_inactive_title_bg_color
963        );
964        color!(
965            FOCUSED_INACTIVE_TITLE_TEXT_COLOR,
966            focused_inactive_title_text_color
967        );
968        color!(FOCUSED_TITLE_BACKGROUND_COLOR, focused_title_bg_color);
969        color!(FOCUSED_TITLE_TEXT_COLOR, focused_title_text_color);
970        color!(SEPARATOR_COLOR, separator_color);
971        color!(UNFOCUSED_TITLE_BACKGROUND_COLOR, unfocused_title_bg_color);
972        color!(UNFOCUSED_TITLE_TEXT_COLOR, unfocused_title_text_color);
973        color!(HIGHLIGHT_COLOR, highlight_color);
974        macro_rules! size {
975            ($sized:ident, $field:ident) => {
976                if let Some(size) = theme.$field {
977                    $sized.set(size);
978                }
979            };
980        }
981        size!(BORDER_WIDTH, border_width);
982        size!(TITLE_HEIGHT, title_height);
983        size!(BAR_HEIGHT, bar_height);
984        size!(BAR_SEPARATOR_WIDTH, bar_separator_width);
985        macro_rules! font {
986            ($fun:ident, $field:ident) => {
987                if let Some(font) = &theme.$field {
988                    $fun(font);
989                }
990            };
991        }
992        font!(set_font, font);
993        font!(set_title_font, title_font);
994        font!(set_bar_font, bar_font);
995    }
996
997    fn handle_switch_device(self: &Rc<Self>, dev: InputDevice, actions: &Rc<SwitchActions>) {
998        if !dev.has_capability(CAP_SWITCH) {
999            return;
1000        }
1001        let state = self.clone();
1002        let actions = actions.clone();
1003        dev.on_switch_event(move |ev| {
1004            for (match_, actions) in &*actions {
1005                if match_.matches(dev, &state)
1006                    && let Some(action) = actions.get(&ev)
1007                {
1008                    action();
1009                }
1010            }
1011        });
1012    }
1013
1014    fn add_io_output(&self, c: Connector) {
1015        let mappings: Vec<_> = self
1016            .io_maps
1017            .iter()
1018            .map(|(_, output)| output.matches(c, self))
1019            .collect();
1020        if mappings.len() > 0 {
1021            self.io_outputs.borrow_mut().insert(c, mappings);
1022        }
1023    }
1024
1025    fn add_io_input(&self, d: InputDevice) {
1026        let mappings: Vec<_> = self
1027            .io_maps
1028            .iter()
1029            .map(|(input, _)| input.matches(d, self))
1030            .collect();
1031        if mappings.len() > 0 {
1032            self.io_inputs.borrow_mut().insert(d, mappings);
1033        }
1034    }
1035
1036    fn map_input_to_output(&self, d: InputDevice) {
1037        let input_mappings = &*self.io_inputs.borrow();
1038        let Some(input_matches) = input_mappings.get(&d) else {
1039            return;
1040        };
1041        for (idx, &input_is_match) in input_matches.iter().enumerate() {
1042            if input_is_match {
1043                for (&c, output_maps) in &*self.io_outputs.borrow() {
1044                    if output_maps.get(idx) == Some(&true) {
1045                        d.set_connector(c);
1046                    }
1047                }
1048            }
1049        }
1050    }
1051
1052    fn map_output_to_input(&self, c: Connector) {
1053        let output_mappings = &*self.io_outputs.borrow();
1054        let Some(output_matches) = output_mappings.get(&c) else {
1055            return;
1056        };
1057        for (idx, &output_is_match) in output_matches.iter().enumerate() {
1058            if output_is_match {
1059                for (&d, input_matches) in &*self.io_inputs.borrow() {
1060                    if input_matches.get(idx) == Some(&true) {
1061                        d.set_connector(c);
1062                    }
1063                }
1064            }
1065        }
1066    }
1067
1068    fn with_client(&self, client: Client, check: bool, f: impl FnOnce()) {
1069        let mut opt = Some(client);
1070        if client.0 == 0 || (check && client.does_not_exist()) {
1071            opt = None;
1072        }
1073        self.client.set(opt);
1074        f();
1075        self.client.set(None);
1076    }
1077
1078    fn with_window(&self, window: Window, check: bool, f: impl FnOnce()) {
1079        let mut w = Some(window);
1080        if check && !window.exists() {
1081            w = None;
1082        }
1083        self.window.set(Some(w));
1084        f();
1085        self.window.set(None);
1086    }
1087}
1088
1089#[derive(Eq, PartialEq, Hash)]
1090struct OutputId {
1091    manufacturer: String,
1092    model: String,
1093    serial_number: String,
1094}
1095
1096struct PersistentState {
1097    seen_outputs: RefCell<AHashSet<OutputId>>,
1098    default: Config,
1099    seat: Seat,
1100    #[expect(clippy::type_complexity)]
1101    actions: RefCell<AHashMap<Rc<String>, Rc<dyn Fn()>>>,
1102    client_rules: Cell<Vec<MatcherTemp<ClientRule>>>,
1103    client_rule_mapper: RefCell<Option<RuleMapper<ClientRule>>>,
1104    window_rules: Cell<Vec<MatcherTemp<WindowRule>>>,
1105    mark_names: RefCell<AHashMap<String, u32>>,
1106    mode_state: ModeState,
1107    watcher_handle: RefCell<Option<JoinHandle<()>>>,
1108    last_config: RefCell<Option<Vec<u8>>>,
1109}
1110
1111async fn watch_config(persistent: Rc<PersistentState>) {
1112    let inotify = match uapi::inotify_init1(IN_NONBLOCK | IN_CLOEXEC) {
1113        Ok(i) => i,
1114        Err(e) => {
1115            log::error!("Could not create inotify fd: {}", Report::new(e));
1116            return;
1117        }
1118    };
1119    let inotify_async = match Async::new(&inotify) {
1120        Ok(i) => i,
1121        Err(e) => {
1122            log::error!(
1123                "Could not create Async object for inotify fd: {}",
1124                Report::new(e)
1125            );
1126            return;
1127        }
1128    };
1129
1130    let timer = match uapi::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC) {
1131        Ok(t) => Rc::new(t),
1132        Err(e) => {
1133            log::error!("Could not create timer fd: {}", Report::new(e));
1134            return;
1135        }
1136    };
1137    let timer_async = match Async::new(timer.clone()) {
1138        Ok(i) => i,
1139        Err(e) => {
1140            log::error!(
1141                "Could not create Async object for timer fd: {}",
1142                Report::new(e)
1143            );
1144            return;
1145        }
1146    };
1147
1148    let timer_task = tasks::spawn(async move {
1149        loop {
1150            if let Err(e) = timer_async.readable().await {
1151                log::error!(
1152                    "Could not wait for timer to become readable: {}",
1153                    Report::new(e),
1154                );
1155                return;
1156            }
1157            let mut buf = 0u64;
1158            if let Err(e) = uapi::read(timer_async.as_ref().raw(), &mut buf) {
1159                log::error!("Could not read from timer fd: {}", Report::new(e));
1160                return;
1161            }
1162            load_config(false, true, &persistent);
1163        }
1164    });
1165    let _cancel_task = on_drop(|| timer_task.abort());
1166
1167    let program_timer = || {
1168        let new_value = c::itimerspec {
1169            it_interval: timespec {
1170                tv_nsec: 0,
1171                tv_sec: 0,
1172            },
1173            it_value: timespec {
1174                tv_nsec: 400_000_000,
1175                tv_sec: 0,
1176            },
1177        };
1178        if let Err(e) = uapi::timerfd_settime(timer.raw(), 0, &new_value) {
1179            log::error!("Could not set timer: {}", Report::new(e));
1180        }
1181    };
1182
1183    let config_dir = config_dir();
1184    let config_dir = Path::new(&config_dir);
1185    let mut dirs = vec![];
1186    for component in config_dir.components() {
1187        dirs.push(component.as_os_str());
1188    }
1189
1190    let mut dir_watches = vec![];
1191    let mut file_watch = None;
1192
1193    let mut path_buf = PathBuf::new();
1194    let mut create_watches = |dir_watches: &mut Vec<c::c_int>,
1195                              file_watch: &mut Option<c::c_int>| {
1196        path_buf.clear();
1197        for (i, dir) in dirs.iter().enumerate() {
1198            path_buf.push(dir);
1199            if dir_watches.len() > i {
1200                continue;
1201            }
1202            let res = uapi::inotify_add_watch(
1203                inotify.raw(),
1204                &*path_buf,
1205                IN_ONLYDIR
1206                    | IN_CREATE
1207                    | IN_DELETE
1208                    | IN_MOVED_FROM
1209                    | IN_MOVED_TO
1210                    | IN_ATTRIB
1211                    | IN_EXCL_UNLINK,
1212            );
1213            let Ok(n) = res else {
1214                return;
1215            };
1216            dir_watches.push(n);
1217        }
1218        if file_watch.is_none() {
1219            path_buf.push(CONFIG_TOML);
1220            let res =
1221                uapi::inotify_add_watch(inotify.raw(), &*path_buf, IN_CLOSE_WRITE | IN_EXCL_UNLINK);
1222            *file_watch = res.ok();
1223        }
1224    };
1225    macro_rules! create_watches {
1226        () => {
1227            create_watches(&mut dir_watches, &mut file_watch);
1228            program_timer();
1229        };
1230    }
1231    create_watches!();
1232
1233    let mut buffer = vec![0; 1024];
1234    loop {
1235        let res = uapi::inotify_read(inotify_async.as_ref().as_raw_fd(), &mut *buffer);
1236        let events = match res {
1237            Ok(e) => e,
1238            Err(Errno(c::EAGAIN)) => {
1239                inotify_async.readable().await.unwrap();
1240                continue;
1241            }
1242            Err(e) => {
1243                log::error!("Could not read from inotify fd: {}", e);
1244                return;
1245            }
1246        };
1247        for event in events {
1248            if Some(event.wd) == file_watch {
1249                program_timer();
1250            } else {
1251                for i in 0..dir_watches.len() {
1252                    if event.wd != dir_watches[i] {
1253                        continue;
1254                    }
1255                    let next = if i + 1 == dirs.len() {
1256                        OsStr::new(CONFIG_TOML)
1257                    } else {
1258                        dirs[i + 1]
1259                    };
1260                    if event.name().to_bytes() != next.as_bytes() {
1261                        break;
1262                    }
1263                    if event.mask & (IN_DELETE | IN_MOVED_FROM) != 0 {
1264                        for wd in dir_watches.drain(i + 1..) {
1265                            let _ = uapi::inotify_rm_watch(inotify.raw(), wd);
1266                        }
1267                        if let Some(wd) = file_watch.take() {
1268                            let _ = uapi::inotify_rm_watch(inotify.raw(), wd);
1269                        }
1270                        program_timer();
1271                    }
1272                    if (event.mask & IN_ATTRIB != 0
1273                        && i + 1 == dir_watches.len()
1274                        && file_watch.is_none())
1275                        || event.mask & (IN_CREATE | IN_MOVED_TO) != 0
1276                    {
1277                        create_watches!();
1278                    }
1279                    break;
1280                }
1281            }
1282        }
1283    }
1284}
1285
1286const CONFIG_TOML: &str = "config.toml";
1287
1288fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<PersistentState>) {
1289    let mut path = PathBuf::from(config_dir());
1290    path.push(CONFIG_TOML);
1291    let mut last_config = persistent.last_config.borrow_mut();
1292    let mut config = match std::fs::read(&path) {
1293        Ok(input) => {
1294            if auto_reload {
1295                if Some(&input) == last_config.as_ref() {
1296                    return;
1297                }
1298                log::info!("Auto reloading config")
1299            }
1300            let parsed = parse_config(&input, &persistent.mark_names, |e| {
1301                log::warn!("Error while parsing {}: {}", path.display(), Report::new(e))
1302            });
1303            *last_config = Some(input);
1304            match parsed {
1305                None if initial_load => {
1306                    log::warn!("Using default config instead");
1307                    persistent.default.clone()
1308                }
1309                None => {
1310                    log::warn!("Ignoring config reload");
1311                    return;
1312                }
1313                Some(c) => c,
1314            }
1315        }
1316        Err(e) if e.kind() == ErrorKind::NotFound => {
1317            if auto_reload {
1318                if last_config.take().is_none() {
1319                    return;
1320                }
1321                log::info!("Auto reloading config")
1322            }
1323            log::info!("{} does not exist. Using default config.", path.display());
1324            persistent.default.clone()
1325        }
1326        Err(e) => {
1327            log::warn!("Could not load {}: {}", path.display(), Report::new(e));
1328            log::warn!("Ignoring config reload");
1329            return;
1330        }
1331    };
1332    drop(last_config);
1333    if let Some(auto_reload) = config.auto_reload {
1334        if auto_reload {
1335            let handle = &mut *persistent.watcher_handle.borrow_mut();
1336            if handle.is_none() {
1337                *handle = Some(tasks::spawn(watch_config(persistent.clone())));
1338            }
1339        } else {
1340            if let Some(handle) = persistent.watcher_handle.take() {
1341                handle.abort();
1342            }
1343        }
1344    }
1345    let mut outputs = AHashMap::new();
1346    for output in &config.outputs {
1347        if let Some(name) = &output.name {
1348            let prev = outputs.insert(name.clone(), output.match_.clone());
1349            if prev.is_some() {
1350                log::warn!("Duplicate output name {name}");
1351            }
1352        }
1353    }
1354    let mut keymaps = AHashMap::new();
1355    for keymap in config.keymaps {
1356        match keymap {
1357            ConfigKeymap::Defined { name, map } => {
1358                keymaps.insert(name, map);
1359            }
1360            _ => log::warn!("Keymap is not in defined form in top-level context"),
1361        }
1362    }
1363    let mut input_devices = AHashMap::new();
1364    let mut io_maps = vec![];
1365    for input in &config.inputs {
1366        if let Some(tag) = &input.tag {
1367            let prev = input_devices.insert(tag.clone(), input.match_.clone());
1368            if prev.is_some() {
1369                log::warn!("Duplicate input tag {tag}");
1370            }
1371        }
1372        if let Some(Some(output)) = &input.output {
1373            io_maps.push((input.match_.clone(), output.clone()));
1374        }
1375    }
1376    let mut named_drm_device = AHashMap::new();
1377    for drm_device in &config.drm_devices {
1378        if let Some(name) = &drm_device.name {
1379            let prev = named_drm_device.insert(name.clone(), drm_device.match_.clone());
1380            if prev.is_some() {
1381                log::warn!("Duplicate drm device name {name}");
1382            }
1383        }
1384    }
1385    let state = Rc::new(State {
1386        outputs,
1387        drm_devices: named_drm_device,
1388        input_devices,
1389        persistent: persistent.clone(),
1390        keymaps,
1391        io_maps,
1392        io_inputs: Default::default(),
1393        io_outputs: Default::default(),
1394        action_depth_max: config.max_action_depth,
1395        action_depth: Cell::new(0),
1396        client: Default::default(),
1397        window: Default::default(),
1398    });
1399    state.clear_modes_after_reload();
1400    let (client_rules, client_rule_mapper) = state.create_rules(&config.client_rules);
1401    persistent.client_rules.set(client_rules);
1402    *state.persistent.client_rule_mapper.borrow_mut() = Some(client_rule_mapper);
1403    let (window_rules, _) = state.create_rules(&config.window_rules);
1404    persistent.window_rules.set(window_rules);
1405    state.set_status(&config.status);
1406    persistent.actions.borrow_mut().clear();
1407    for a in config.named_actions {
1408        let action = a.action.into_rc_fn(&state);
1409        persistent.actions.borrow_mut().insert(a.name, action);
1410    }
1411    let mut switch_actions = vec![];
1412    for input in &mut config.inputs {
1413        let mut actions = AHashMap::new();
1414        for (event, action) in input.switch_actions.drain() {
1415            actions.insert(event, action.into_fn(&state));
1416        }
1417        if actions.len() > 0 {
1418            switch_actions.push((input.match_.clone(), actions));
1419        }
1420    }
1421    let switch_actions = Rc::new(switch_actions);
1422    match config.on_graphics_initialized {
1423        None => on_graphics_initialized(|| ()),
1424        Some(a) => on_graphics_initialized(a.into_fn(&state)),
1425    }
1426    match config.on_idle {
1427        None => on_idle(|| ()),
1428        Some(a) => on_idle(a.into_fn(&state)),
1429    }
1430    state.init_modes(&config.shortcuts, &config.input_modes);
1431    if let Some(keymap) = config.keymap {
1432        state.set_keymap(&keymap);
1433    }
1434    if let Some(repeat_rate) = config.repeat_rate {
1435        persistent
1436            .seat
1437            .set_repeat_rate(repeat_rate.rate, repeat_rate.delay);
1438    }
1439    on_new_connector(move |c| {
1440        for connector in &config.connectors {
1441            if connector.match_.matches(c) {
1442                connector.apply(c);
1443            }
1444        }
1445    });
1446    on_connector_connected({
1447        let state = state.clone();
1448        move |c| {
1449            state.add_io_output(c);
1450            state.map_output_to_input(c);
1451            let id = OutputId {
1452                manufacturer: c.manufacturer(),
1453                model: c.model(),
1454                serial_number: c.serial_number(),
1455            };
1456            if state.persistent.seen_outputs.borrow_mut().insert(id) {
1457                for output in &config.outputs {
1458                    if output.match_.matches(c, &state) {
1459                        output.apply(c);
1460                    }
1461                }
1462            }
1463        }
1464    });
1465    on_connector_disconnected({
1466        let state = state.clone();
1467        move |c| {
1468            state.io_outputs.borrow_mut().remove(&c);
1469        }
1470    });
1471    set_default_workspace_capture(config.workspace_capture);
1472    for (k, v) in config.env {
1473        set_env(&k, &v);
1474    }
1475    if initial_load && !is_reload() {
1476        if let Some(on_startup) = config.on_startup {
1477            on_startup.into_fn(&state)();
1478        }
1479        if let Some(level) = config.log_level {
1480            set_log_level(level);
1481        }
1482        if let Some(idle) = config.idle {
1483            set_idle(Some(idle));
1484        }
1485        if let Some(period) = config.grace_period {
1486            set_idle_grace_period(period);
1487        }
1488    }
1489    on_devices_enumerated({
1490        let state = state.clone();
1491        move || {
1492            if let Some(dev) = config.render_device {
1493                for d in drm_devices() {
1494                    if dev.matches(d, &state) {
1495                        d.make_render_device();
1496                        return;
1497                    }
1498                }
1499            }
1500        }
1501    });
1502    reset_colors();
1503    reset_font();
1504    reset_sizes();
1505    state.apply_theme(&config.theme);
1506    if let Some(api) = config.gfx_api {
1507        set_gfx_api(api);
1508    }
1509    if let Some(dse) = config.direct_scanout_enabled {
1510        set_direct_scanout_enabled(dse);
1511    }
1512    if let Some(ese) = config.explicit_sync_enabled {
1513        set_explicit_sync_enabled(ese);
1514    }
1515    on_new_drm_device({
1516        let state = state.clone();
1517        move |d| {
1518            for dev in &config.drm_devices {
1519                if dev.match_.matches(d, &state) {
1520                    dev.apply(d);
1521                }
1522            }
1523        }
1524    });
1525    on_new_input_device({
1526        let state = state.clone();
1527        let switch_actions = switch_actions.clone();
1528        move |c| {
1529            state.add_io_input(c);
1530            for input in &config.inputs {
1531                if input.match_.matches(c, &state) {
1532                    input.apply(c, &state);
1533                }
1534            }
1535            state.handle_switch_device(c, &switch_actions);
1536        }
1537    });
1538    on_input_device_removed({
1539        let state = state.clone();
1540        move |c| {
1541            state.io_inputs.borrow_mut().remove(&c);
1542        }
1543    });
1544    for c in connectors() {
1545        state.add_io_output(c);
1546    }
1547    for c in jay_config::input::input_devices() {
1548        state.add_io_input(c);
1549        state.map_input_to_output(c);
1550        state.handle_switch_device(c, &switch_actions);
1551    }
1552    persistent
1553        .seat
1554        .set_focus_follows_mouse_mode(match config.focus_follows_mouse {
1555            true => FocusFollowsMouseMode::True,
1556            false => FocusFollowsMouseMode::False,
1557        });
1558    if let Some(window_management_key) = config.window_management_key {
1559        persistent
1560            .seat
1561            .set_window_management_key(window_management_key);
1562    }
1563    if let Some(vrr) = config.vrr {
1564        if let Some(mode) = vrr.mode {
1565            set_vrr_mode(mode);
1566        }
1567        if let Some(hz) = vrr.cursor_hz {
1568            set_vrr_cursor_hz(hz);
1569        }
1570    }
1571    if let Some(tearing) = config.tearing
1572        && let Some(mode) = tearing.mode
1573    {
1574        set_tearing_mode(mode);
1575    }
1576    set_libei_socket_enabled(config.libei.enable_socket.unwrap_or(false));
1577    if let Some(enabled) = config.ui_drag.enabled {
1578        set_ui_drag_enabled(enabled);
1579    }
1580    if let Some(threshold) = config.ui_drag.threshold {
1581        set_ui_drag_threshold(threshold);
1582    }
1583    if let Some(xwayland) = config.xwayland {
1584        if let Some(enabled) = xwayland.enabled {
1585            set_x_wayland_enabled(enabled);
1586        }
1587        if let Some(mode) = xwayland.scaling_mode {
1588            set_x_scaling_mode(mode);
1589        }
1590    }
1591    if let Some(cm) = config.color_management
1592        && let Some(enabled) = cm.enabled
1593    {
1594        set_color_management_enabled(enabled);
1595    }
1596    if let Some(float) = config.float
1597        && let Some(show) = float.show_pin_icon
1598    {
1599        set_show_float_pin_icon(show);
1600    }
1601    if let Some(key) = config.pointer_revert_key {
1602        persistent.seat.set_pointer_revert_key(key);
1603    }
1604    if let Some(v) = config.use_hardware_cursor {
1605        persistent.seat.use_hardware_cursor(v);
1606    }
1607    if let Some(v) = config.show_bar {
1608        set_show_bar(v);
1609    }
1610    if let Some(v) = config.show_titles {
1611        set_show_titles(v);
1612    }
1613    if let Some(v) = config.theme.bar_position {
1614        set_bar_position(v);
1615    }
1616    if let Some(v) = config.focus_history {
1617        if let Some(v) = v.only_visible {
1618            persistent.seat.focus_history_set_only_visible(v);
1619        }
1620        if let Some(v) = v.same_workspace {
1621            persistent.seat.focus_history_set_same_workspace(v);
1622        }
1623    }
1624    if let Some(v) = config.middle_click_paste {
1625        set_middle_click_paste_enabled(v);
1626    }
1627    if let Some(v) = config.workspace_display_order {
1628        set_workspace_display_order(v);
1629    }
1630    if let Some(simple_im) = config.simple_im {
1631        if let Some(enabled) = simple_im.enabled {
1632            persistent.seat.set_simple_im_enabled(enabled);
1633        }
1634    }
1635    if let Some(v) = config.fallback_output_mode {
1636        persistent.seat.set_fallback_output_mode(v);
1637    }
1638    if let Some(f) = &config.egui.proportional_fonts {
1639        set_egui_proportional_fonts(f.iter().map(|s| &**s));
1640    }
1641    if let Some(f) = &config.egui.monospace_fonts {
1642        set_egui_monospace_fonts(f.iter().map(|s| &**s));
1643    }
1644}
1645
1646fn create_command(exec: &Exec) -> Command {
1647    let mut command = Command::new(&exec.prog);
1648    for arg in &exec.args {
1649        command.arg(arg);
1650    }
1651    for (k, v) in &exec.envs {
1652        command.env(k, v);
1653    }
1654    if exec.privileged {
1655        command.privileged();
1656    }
1657    if let Some(tag) = &exec.tag {
1658        command.tag(tag);
1659    }
1660    command
1661}
1662
1663const DEFAULT: &[u8] = include_bytes!("default-config.toml");
1664
1665pub fn configure() {
1666    let mark_names = Default::default();
1667    let default = parse_config(DEFAULT, &mark_names, |e| {
1668        panic!("Could not parse the default config: {}", Report::new(e))
1669    });
1670    let persistent = Rc::new(PersistentState {
1671        seen_outputs: Default::default(),
1672        default: default.unwrap(),
1673        seat: default_seat(),
1674        actions: Default::default(),
1675        client_rules: Default::default(),
1676        client_rule_mapper: Default::default(),
1677        window_rules: Default::default(),
1678        mark_names,
1679        mode_state: Default::default(),
1680        watcher_handle: Default::default(),
1681        last_config: Default::default(),
1682    });
1683    {
1684        let p = persistent.clone();
1685        on_unload(move || {
1686            p.actions.borrow_mut().clear();
1687            p.client_rule_mapper.borrow_mut().take();
1688            p.mode_state.clear();
1689        });
1690    }
1691    load_config(true, false, &persistent);
1692}
1693
1694config!(configure);