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),
#[knuffel(skip)]
UnsetWindowUrgent(u64),
#[knuffel(skip)]
LoadConfigFile,
}
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::SetWindowUrgent { id } => Self::SetWindowUrgent(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))]
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.
+5
View File
@@ -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();
}
}
}
}
+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,
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);
+4
View File
@@ -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,
+40 -20
View File
@@ -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,10 +31,8 @@ impl Watcher {
started: Option<mpsc::SyncSender<()>>,
polling_interval: Duration,
) -> 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()
.name(format!("Filesystem Watcher for {config_path:?}"))
.spawn(move || {
@@ -80,24 +73,26 @@ impl Watcher {
}
loop {
thread::sleep(polling_interval);
if should_stop.load(Ordering::SeqCst) {
break;
}
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 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);
}
}
}
@@ -105,12 +100,37 @@ impl Watcher {
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;