mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-22 02:01:55 +07:00
fix hot reloading /etc/niri/config.kdl (#1907)
* refactor config load logic, and properly watch the system config path * move config creation to niri-config, and make the errors a bit nicer notably, "error creating config" is now a cause for "error loading config", instead of it being one error and then "error loading config: no such file or directory". also, failure to load a config is now printed as an error level diagnostic (because it is indeed an error, not just a warning you can shrug off) * refactor watcher tests; add some new ones now they check for the file contents too! and i added some tests for ConfigPath::Regular, including a messy one with many symlink swaps * fixes --------- Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
+112
-6
@@ -3,6 +3,8 @@ extern crate tracing;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
use std::ops::{Mul, MulAssign};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
@@ -2368,14 +2370,118 @@ pub enum PreviewRender {
|
||||
ScreenCapture,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(path: &Path) -> miette::Result<Self> {
|
||||
let _span = tracy_client::span!("Config::load");
|
||||
Self::load_internal(path).context("error loading config")
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConfigPath {
|
||||
/// Explicitly set config path.
|
||||
///
|
||||
/// Load the config only from this path, never create it.
|
||||
Explicit(PathBuf),
|
||||
|
||||
/// Default config path.
|
||||
///
|
||||
/// Prioritize the user path, fallback to the system path, fallback to creating the user path
|
||||
/// at compositor startup.
|
||||
Regular {
|
||||
/// User config path, usually `$XDG_CONFIG_HOME/niri/config.kdl`.
|
||||
user_path: PathBuf,
|
||||
/// System config path, usually `/etc/niri/config.kdl`.
|
||||
system_path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
impl ConfigPath {
|
||||
/// Load the config, or return an error if it doesn't exist.
|
||||
pub fn load(&self) -> miette::Result<Config> {
|
||||
let _span = tracy_client::span!("ConfigPath::load");
|
||||
|
||||
self.load_inner(|user_path, system_path| {
|
||||
Err(miette::miette!(
|
||||
"no config file found; create one at {user_path:?} or {system_path:?}",
|
||||
))
|
||||
})
|
||||
.context("error loading config")
|
||||
}
|
||||
|
||||
fn load_internal(path: &Path) -> miette::Result<Self> {
|
||||
let contents = std::fs::read_to_string(path)
|
||||
/// Load the config, or create it if it doesn't exist.
|
||||
///
|
||||
/// Returns a tuple containing the path that was created, if any, and the loaded config.
|
||||
///
|
||||
/// If the config was created, but for some reason could not be read afterwards,
|
||||
/// this may return `(Some(_), Err(_))`.
|
||||
pub fn load_or_create(&self) -> (Option<&Path>, miette::Result<Config>) {
|
||||
let _span = tracy_client::span!("ConfigPath::load_or_create");
|
||||
|
||||
let mut created_at = None;
|
||||
|
||||
let result = self
|
||||
.load_inner(|user_path, _| {
|
||||
Self::create(user_path, &mut created_at)
|
||||
.map(|()| user_path)
|
||||
.with_context(|| format!("error creating config at {user_path:?}"))
|
||||
})
|
||||
.context("error loading config");
|
||||
|
||||
(created_at, result)
|
||||
}
|
||||
|
||||
fn load_inner<'a>(
|
||||
&'a self,
|
||||
maybe_create: impl FnOnce(&'a Path, &'a Path) -> miette::Result<&'a Path>,
|
||||
) -> miette::Result<Config> {
|
||||
let path = match self {
|
||||
ConfigPath::Explicit(path) => path.as_path(),
|
||||
ConfigPath::Regular {
|
||||
user_path,
|
||||
system_path,
|
||||
} => {
|
||||
if user_path.exists() {
|
||||
user_path.as_path()
|
||||
} else if system_path.exists() {
|
||||
system_path.as_path()
|
||||
} else {
|
||||
maybe_create(user_path.as_path(), system_path.as_path())?
|
||||
}
|
||||
}
|
||||
};
|
||||
Config::load(path)
|
||||
}
|
||||
|
||||
fn create<'a>(path: &'a Path, created_at: &mut Option<&'a Path>) -> miette::Result<()> {
|
||||
if let Some(default_parent) = path.parent() {
|
||||
fs::create_dir_all(default_parent)
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("error creating config directory {default_parent:?}"))?;
|
||||
}
|
||||
|
||||
// Create the config and fill it with the default config if it doesn't exist.
|
||||
let mut new_file = match File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(path)
|
||||
{
|
||||
Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => return Ok(()),
|
||||
res => res,
|
||||
}
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("error opening config file at {path:?}"))?;
|
||||
|
||||
*created_at = Some(path);
|
||||
|
||||
let default = include_bytes!("../../resources/default-config.kdl");
|
||||
|
||||
new_file
|
||||
.write_all(default)
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("error writing default config to {path:?}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(path: &Path) -> miette::Result<Self> {
|
||||
let contents = fs::read_to_string(path)
|
||||
.into_diagnostic()
|
||||
.with_context(|| format!("error reading {path:?}"))?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user