Add the LoadConfigFile action (#2163)

* Add the `LoadConfigFile` action

* fixes

---------

Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
vanderlokken
2025-08-09 16:20:08 +04:00
committed by GitHub
parent f74d83dcca
commit 67361f88fd
6 changed files with 105 additions and 89 deletions
+3
View File
@@ -1924,6 +1924,8 @@ pub enum Action {
SetWindowUrgent(u64), SetWindowUrgent(u64),
#[knuffel(skip)] #[knuffel(skip)]
UnsetWindowUrgent(u64), UnsetWindowUrgent(u64),
#[knuffel(skip)]
LoadConfigFile,
} }
impl From<niri_ipc::Action> for Action { impl From<niri_ipc::Action> for Action {
@@ -2199,6 +2201,7 @@ impl From<niri_ipc::Action> for Action {
niri_ipc::Action::ToggleWindowUrgent { id } => Self::ToggleWindowUrgent(id), niri_ipc::Action::ToggleWindowUrgent { id } => Self::ToggleWindowUrgent(id),
niri_ipc::Action::SetWindowUrgent { id } => Self::SetWindowUrgent(id), niri_ipc::Action::SetWindowUrgent { id } => Self::SetWindowUrgent(id),
niri_ipc::Action::UnsetWindowUrgent { id } => Self::UnsetWindowUrgent(id), niri_ipc::Action::UnsetWindowUrgent { id } => Self::UnsetWindowUrgent(id),
niri_ipc::Action::LoadConfigFile {} => Self::LoadConfigFile,
} }
} }
} }
+5
View File
@@ -840,6 +840,11 @@ pub enum Action {
#[cfg_attr(feature = "clap", arg(long))] #[cfg_attr(feature = "clap", arg(long))]
id: u64, id: u64,
}, },
/// Reload the config file.
///
/// Can be useful for scripts changing the config file, to avoid waiting the small duration for
/// niri's config file watcher to notice the changes.
LoadConfigFile {},
} }
/// Change in window or column size. /// Change in window or column size.
+5
View File
@@ -2113,6 +2113,11 @@ impl State {
} }
self.niri.queue_redraw_all(); self.niri.queue_redraw_all();
} }
Action::LoadConfigFile => {
if let Some(watcher) = &self.niri.config_file_watcher {
watcher.load_config();
}
}
} }
} }
+2 -23
View File
@@ -23,8 +23,7 @@ use niri::utils::spawning::{
spawn, store_and_increase_nofile_rlimit, CHILD_DISPLAY, CHILD_ENV, REMOVE_ENV_RUST_BACKTRACE, spawn, store_and_increase_nofile_rlimit, CHILD_DISPLAY, CHILD_ENV, REMOVE_ENV_RUST_BACKTRACE,
REMOVE_ENV_RUST_LIB_BACKTRACE, REMOVE_ENV_RUST_LIB_BACKTRACE,
}; };
use niri::utils::watcher::Watcher; use niri::utils::{cause_panic, version, watcher, xwayland, IS_SYSTEMD_SERVICE};
use niri::utils::{cause_panic, version, xwayland, IS_SYSTEMD_SERVICE};
use niri_config::ConfigPath; use niri_config::ConfigPath;
use niri_ipc::socket::SOCKET_PATH_ENV; use niri_ipc::socket::SOCKET_PATH_ENV;
use portable_atomic::Ordering; use portable_atomic::Ordering;
@@ -230,27 +229,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
// Set up config file watcher. watcher::setup(&mut state, &config_path);
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
};
// Spawn commands from cli and auto-start. // Spawn commands from cli and auto-start.
spawn(cli.command, None); spawn(cli.command, None);
+4
View File
@@ -164,6 +164,7 @@ use crate::ui::screen_transition::{self, ScreenTransition};
use crate::ui::screenshot_ui::{OutputScreenshot, ScreenshotUi, ScreenshotUiRenderElement}; use crate::ui::screenshot_ui::{OutputScreenshot, ScreenshotUi, ScreenshotUiRenderElement};
use crate::utils::scale::{closest_representable_scale, guess_monitor_scale}; use crate::utils::scale::{closest_representable_scale, guess_monitor_scale};
use crate::utils::spawning::{CHILD_DISPLAY, CHILD_ENV}; use crate::utils::spawning::{CHILD_DISPLAY, CHILD_ENV};
use crate::utils::watcher::Watcher;
use crate::utils::xwayland::satellite::Satellite; use crate::utils::xwayland::satellite::Satellite;
use crate::utils::{ use crate::utils::{
center, center_f64, expand_home, get_monotonic_time, ipc_transform_to_smithay, is_mapped, 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). /// (and transient changes dropped).
pub config_file_output_config: niri_config::Outputs, pub config_file_output_config: niri_config::Outputs,
pub config_file_watcher: Option<Watcher>,
pub event_loop: LoopHandle<'static, State>, pub event_loop: LoopHandle<'static, State>,
pub scheduler: Scheduler<()>, pub scheduler: Scheduler<()>,
pub stop_signal: LoopSignal, pub stop_signal: LoopSignal,
@@ -2528,6 +2531,7 @@ impl Niri {
let mut niri = Self { let mut niri = Self {
config, config,
config_file_output_config, config_file_output_config,
config_file_watcher: None,
event_loop, event_loop,
scheduler, scheduler,
+40 -20
View File
@@ -1,22 +1,17 @@
//! File modification watcher. //! File modification watcher.
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc;
use std::sync::{mpsc, Arc};
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use std::{io, thread}; use std::{io, thread};
use niri_config::ConfigPath; use niri_config::ConfigPath;
use smithay::reexports::calloop::channel::SyncSender; use smithay::reexports::calloop::channel::SyncSender;
pub struct Watcher { use crate::niri::State;
should_stop: Arc<AtomicBool>,
}
impl Drop for Watcher { pub struct Watcher {
fn drop(&mut self) { load_config: mpsc::Sender<()>,
self.should_stop.store(true, Ordering::SeqCst);
}
} }
impl Watcher { impl Watcher {
@@ -36,10 +31,8 @@ impl Watcher {
started: Option<mpsc::SyncSender<()>>, started: Option<mpsc::SyncSender<()>>,
polling_interval: Duration, polling_interval: Duration,
) -> Self { ) -> Self {
let should_stop = Arc::new(AtomicBool::new(false)); let (load_config, load_config_rx) = mpsc::channel();
{
let should_stop = should_stop.clone();
thread::Builder::new() thread::Builder::new()
.name(format!("Filesystem Watcher for {config_path:?}")) .name(format!("Filesystem Watcher for {config_path:?}"))
.spawn(move || { .spawn(move || {
@@ -80,24 +73,26 @@ impl Watcher {
} }
loop { loop {
thread::sleep(polling_interval); let mut should_load = match load_config_rx.recv_timeout(polling_interval) {
Ok(()) => true,
if should_stop.load(Ordering::SeqCst) { Err(mpsc::RecvTimeoutError::Disconnected) => break,
break; Err(mpsc::RecvTimeoutError::Timeout) => false,
} };
if let Ok(new_props) = see(&config_path) { if let Ok(new_props) = see(&config_path) {
if last_props.as_ref() != Some(&new_props) { if last_props.as_ref() != Some(&new_props) {
last_props = Some(new_props);
trace!("config file changed"); trace!("config file changed");
should_load = true;
}
if should_load {
let rv = process(&config_path); let rv = process(&config_path);
if let Err(err) = changed.send(rv) { if let Err(err) = changed.send(rv) {
warn!("error sending change notification: {err:?}"); warn!("error sending change notification: {err:?}");
break; break;
} }
last_props = Some(new_props);
} }
} }
} }
@@ -105,12 +100,37 @@ impl Watcher {
debug!("exiting watcher thread for {config_path:?}"); debug!("exiting watcher thread for {config_path:?}");
}) })
.unwrap(); .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)] #[cfg(test)]
mod tests { mod tests {
use std::error::Error; use std::error::Error;