diff options
| author | vanderlokken <vanderlokken@gmail.com> | 2025-08-09 16:20:08 +0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-09 12:20:08 +0000 |
| commit | 67361f88fd01974ebee4cf80f0e29c87d805cc39 (patch) | |
| tree | 593a2d419a6a830186b7027166ca637ee5232611 /src | |
| parent | f74d83dccaa6e8fffb38c304dd5d1eae07b87d24 (diff) | |
| download | niri-67361f88fd01974ebee4cf80f0e29c87d805cc39.tar.gz niri-67361f88fd01974ebee4cf80f0e29c87d805cc39.tar.bz2 niri-67361f88fd01974ebee4cf80f0e29c87d805cc39.zip | |
Add the `LoadConfigFile` action (#2163)
* Add the `LoadConfigFile` action
* fixes
---------
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/input/mod.rs | 5 | ||||
| -rw-r--r-- | src/main.rs | 25 | ||||
| -rw-r--r-- | src/niri.rs | 4 | ||||
| -rw-r--r-- | src/utils/watcher.rs | 156 |
4 files changed, 99 insertions, 91 deletions
diff --git a/src/input/mod.rs b/src/input/mod.rs index 2aba1f97..c2667dfc 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -2113,6 +2113,11 @@ impl State { } self.niri.queue_redraw_all(); } + Action::LoadConfigFile => { + if let Some(watcher) = &self.niri.config_file_watcher { + watcher.load_config(); + } + } } } diff --git a/src/main.rs b/src/main.rs index 007dd8b8..2b3e1c5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,8 +23,7 @@ use niri::utils::spawning::{ spawn, store_and_increase_nofile_rlimit, CHILD_DISPLAY, CHILD_ENV, REMOVE_ENV_RUST_BACKTRACE, REMOVE_ENV_RUST_LIB_BACKTRACE, }; -use niri::utils::watcher::Watcher; -use niri::utils::{cause_panic, version, xwayland, IS_SYSTEMD_SERVICE}; +use niri::utils::{cause_panic, version, watcher, xwayland, IS_SYSTEMD_SERVICE}; use niri_config::ConfigPath; use niri_ipc::socket::SOCKET_PATH_ENV; use portable_atomic::Ordering; @@ -230,27 +229,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { } } - // Set up config file watcher. - let _watcher = { - // Parsing the config actually takes > 20 ms on my beefy machine, so let's do it on the - // watcher thread. - let process = |path: &ConfigPath| { - path.load().map_err(|err| { - warn!("{err:?}"); - }) - }; - - let (tx, rx) = calloop::channel::sync_channel(1); - let watcher = Watcher::new(config_path.clone(), process, tx); - event_loop - .handle() - .insert_source(rx, |event, _, state| match event { - calloop::channel::Event::Msg(config) => state.reload_config(config), - calloop::channel::Event::Closed => (), - }) - .unwrap(); - watcher - }; + watcher::setup(&mut state, &config_path); // Spawn commands from cli and auto-start. spawn(cli.command, None); diff --git a/src/niri.rs b/src/niri.rs index ce8bd46f..a990023e 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -164,6 +164,7 @@ use crate::ui::screen_transition::{self, ScreenTransition}; use crate::ui::screenshot_ui::{OutputScreenshot, ScreenshotUi, ScreenshotUiRenderElement}; use crate::utils::scale::{closest_representable_scale, guess_monitor_scale}; use crate::utils::spawning::{CHILD_DISPLAY, CHILD_ENV}; +use crate::utils::watcher::Watcher; use crate::utils::xwayland::satellite::Satellite; use crate::utils::{ center, center_f64, expand_home, get_monotonic_time, ipc_transform_to_smithay, is_mapped, @@ -190,6 +191,8 @@ pub struct Niri { /// (and transient changes dropped). pub config_file_output_config: niri_config::Outputs, + pub config_file_watcher: Option<Watcher>, + pub event_loop: LoopHandle<'static, State>, pub scheduler: Scheduler<()>, pub stop_signal: LoopSignal, @@ -2528,6 +2531,7 @@ impl Niri { let mut niri = Self { config, config_file_output_config, + config_file_watcher: None, event_loop, scheduler, diff --git a/src/utils/watcher.rs b/src/utils/watcher.rs index 635e95e7..8a6509b8 100644 --- a/src/utils/watcher.rs +++ b/src/utils/watcher.rs @@ -1,22 +1,17 @@ //! File modification watcher. use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{mpsc, Arc}; +use std::sync::mpsc; use std::time::{Duration, SystemTime}; use std::{io, thread}; use niri_config::ConfigPath; use smithay::reexports::calloop::channel::SyncSender; -pub struct Watcher { - should_stop: Arc<AtomicBool>, -} +use crate::niri::State; -impl Drop for Watcher { - fn drop(&mut self) { - self.should_stop.store(true, Ordering::SeqCst); - } +pub struct Watcher { + load_config: mpsc::Sender<()>, } impl Watcher { @@ -36,81 +31,106 @@ impl Watcher { started: Option<mpsc::SyncSender<()>>, polling_interval: Duration, ) -> Self { - let should_stop = Arc::new(AtomicBool::new(false)); - - { - let should_stop = should_stop.clone(); - thread::Builder::new() - .name(format!("Filesystem Watcher for {config_path:?}")) - .spawn(move || { - // this "should" be as simple as storing the last seen mtime, - // and if the contents change without updating mtime, we ignore it. - // - // but that breaks if the config is a symlink, and its target - // changes but the new target and old target have identical mtimes. - // in which case we should *not* ignore it; this is an entirely different file. - // - // in practice, this edge case does not occur on systems other than nix. - // because, on nix, everything is a symlink to /nix/store - // and /nix/store keeps no mtime (= 1970-01-01) - // so, symlink targets change frequently when mtime doesn't. - // - // therefore, we must also store the canonical path, along with its mtime - - fn see_path(path: &Path) -> io::Result<(SystemTime, PathBuf)> { - let canon = path.canonicalize()?; - let mtime = canon.metadata()?.modified()?; - Ok((mtime, canon)) - } + let (load_config, load_config_rx) = mpsc::channel(); + + thread::Builder::new() + .name(format!("Filesystem Watcher for {config_path:?}")) + .spawn(move || { + // this "should" be as simple as storing the last seen mtime, + // and if the contents change without updating mtime, we ignore it. + // + // but that breaks if the config is a symlink, and its target + // changes but the new target and old target have identical mtimes. + // in which case we should *not* ignore it; this is an entirely different file. + // + // in practice, this edge case does not occur on systems other than nix. + // because, on nix, everything is a symlink to /nix/store + // and /nix/store keeps no mtime (= 1970-01-01) + // so, symlink targets change frequently when mtime doesn't. + // + // therefore, we must also store the canonical path, along with its mtime + + fn see_path(path: &Path) -> io::Result<(SystemTime, PathBuf)> { + let canon = path.canonicalize()?; + let mtime = canon.metadata()?.modified()?; + Ok((mtime, canon)) + } - fn see(config_path: &ConfigPath) -> io::Result<(SystemTime, PathBuf)> { - match config_path { - ConfigPath::Explicit(path) => see_path(path), - ConfigPath::Regular { - user_path, - system_path, - } => see_path(user_path).or_else(|_| see_path(system_path)), - } + fn see(config_path: &ConfigPath) -> io::Result<(SystemTime, PathBuf)> { + match config_path { + ConfigPath::Explicit(path) => see_path(path), + ConfigPath::Regular { + user_path, + system_path, + } => see_path(user_path).or_else(|_| see_path(system_path)), } + } - let mut last_props = see(&config_path).ok(); - - if let Some(started) = started { - let _ = started.send(()); - } + let mut last_props = see(&config_path).ok(); - loop { - thread::sleep(polling_interval); + if let Some(started) = started { + let _ = started.send(()); + } - if should_stop.load(Ordering::SeqCst) { - break; + loop { + let mut should_load = match load_config_rx.recv_timeout(polling_interval) { + Ok(()) => true, + Err(mpsc::RecvTimeoutError::Disconnected) => break, + Err(mpsc::RecvTimeoutError::Timeout) => false, + }; + + if let Ok(new_props) = see(&config_path) { + if last_props.as_ref() != Some(&new_props) { + last_props = Some(new_props); + trace!("config file changed"); + should_load = true; } - if let Ok(new_props) = see(&config_path) { - if last_props.as_ref() != Some(&new_props) { - trace!("config file changed"); - - let rv = process(&config_path); + if should_load { + let rv = process(&config_path); - if let Err(err) = changed.send(rv) { - warn!("error sending change notification: {err:?}"); - break; - } - - last_props = Some(new_props); + if let Err(err) = changed.send(rv) { + warn!("error sending change notification: {err:?}"); + break; } } } + } - debug!("exiting watcher thread for {config_path:?}"); - }) - .unwrap(); - } + debug!("exiting watcher thread for {config_path:?}"); + }) + .unwrap(); + + Self { load_config } + } - Self { should_stop } + pub fn load_config(&self) { + let _ = self.load_config.send(()); } } +pub fn setup(state: &mut State, config_path: &ConfigPath) { + // Parsing the config actually takes > 20 ms on my beefy machine, so let's do it on the + // watcher thread. + let process = |path: &ConfigPath| { + path.load().map_err(|err| { + warn!("{err:?}"); + }) + }; + + let (tx, rx) = calloop::channel::sync_channel(1); + state + .niri + .event_loop + .insert_source(rx, |event, _, state| match event { + calloop::channel::Event::Msg(config) => state.reload_config(config), + calloop::channel::Event::Closed => (), + }) + .unwrap(); + + state.niri.config_file_watcher = Some(Watcher::new(config_path.clone(), process, tx)); +} + #[cfg(test)] mod tests { use std::error::Error; |
