mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Add the LoadConfigFile action (#2163)
* Add the `LoadConfigFile` action * fixes --------- Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
+86
-66
@@ -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,79 +31,104 @@ 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 || {
|
||||
// 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
|
||||
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_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();
|
||||
|
||||
let mut last_props = see(&config_path).ok();
|
||||
if let Some(started) = started {
|
||||
let _ = started.send(());
|
||||
}
|
||||
|
||||
if let Some(started) = started {
|
||||
let _ = started.send(());
|
||||
}
|
||||
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,
|
||||
};
|
||||
|
||||
loop {
|
||||
thread::sleep(polling_interval);
|
||||
|
||||
if should_stop.load(Ordering::SeqCst) {
|
||||
break;
|
||||
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");
|
||||
if should_load {
|
||||
let rv = process(&config_path);
|
||||
|
||||
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 { should_stop }
|
||||
Self { load_config }
|
||||
}
|
||||
|
||||
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)]
|
||||
|
||||
Reference in New Issue
Block a user