aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorvanderlokken <vanderlokken@gmail.com>2025-08-09 16:20:08 +0400
committerGitHub <noreply@github.com>2025-08-09 12:20:08 +0000
commit67361f88fd01974ebee4cf80f0e29c87d805cc39 (patch)
tree593a2d419a6a830186b7027166ca637ee5232611 /src
parentf74d83dccaa6e8fffb38c304dd5d1eae07b87d24 (diff)
downloadniri-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.rs5
-rw-r--r--src/main.rs25
-rw-r--r--src/niri.rs4
-rw-r--r--src/utils/watcher.rs156
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;