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 if let Some(direction) = direction {
378 let current_ws = match workspace {
380 Some(ws) => ws,
381 None => s.get_workspace(),
382 };
383 if !current_ws.exists() {
384 return;
385 }
386 let source_connector = current_ws.connector();
388 if !source_connector.exists() {
389 return;
390 }
391 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 '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);