summaryrefslogtreecommitdiff
path: root/src/config.rs
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-08-16 08:21:37 +0200
committercashmere <cashmere@autistici.org>2026-08-16 08:21:37 +0200
commit4901b5d77d73db59f0c1545719ec6d1404aad979 (patch)
tree0b3233e82387ffe7a9cd5c0934971a665e0575a9 /src/config.rs
parent91b84d1f1bfbc5de2eb140a48185a5252b1b592e (diff)
downloadi3bar-mango-main.tar.gz
Run detached and reload config liveHEADmain
Diffstat (limited to 'src/config.rs')
-rw-r--r--src/config.rs98
1 files changed, 84 insertions, 14 deletions
diff --git a/src/config.rs b/src/config.rs
index 4e41069..d354839 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,11 +1,15 @@
use crate::color::Color;
use crate::protocol::{zwlr_layer_shell_v1, zwlr_layer_surface_v1};
use anyhow::{Context, Result};
+use inotify::{Inotify, WatchMask};
use pangocairo::pango::FontDescription;
use serde::{Deserialize, de};
use std::collections::HashMap;
+use std::ffi::OsString;
use std::fs::read_to_string;
+use std::io;
use std::ops::Deref;
+use std::os::fd::{AsRawFd, RawFd};
use std::path::{Path, PathBuf};
use std::{env, fmt};
@@ -114,27 +118,21 @@ impl Default for Config {
impl Config {
pub fn new(path: Option<&Path>) -> Result<Self> {
- let buf;
-
- let path = match path {
- Some(path) => Some(path),
- None => {
- buf = config_path();
- buf.as_deref()
- }
- };
-
- Ok(match path {
+ match Self::path(path) {
Some(config_path) => {
let config = read_to_string(config_path).context("Failed to read configuration")?;
- toml::from_str(&config).context("Failed to deserialize configuration")?
+ toml::from_str(&config).context("Failed to deserialize configuration")
}
None => {
eprintln!("Could not find the configuration path");
eprintln!("Using default configuration");
- Self::default()
+ Ok(Self::default())
}
- })
+ }
+ }
+
+ pub fn path(path: Option<&Path>) -> Option<PathBuf> {
+ path.map(Path::to_path_buf).or_else(config_path)
}
pub fn output_enabled(&self, output: &str) -> bool {
@@ -145,6 +143,47 @@ impl Config {
}
}
+pub struct ConfigWatcher {
+ inotify: Inotify,
+ buffer: Vec<u8>,
+ filename: OsString,
+}
+
+impl ConfigWatcher {
+ pub fn new(path: &Path) -> io::Result<Self> {
+ let directory = path
+ .parent()
+ .filter(|parent| !parent.as_os_str().is_empty())
+ .unwrap_or_else(|| Path::new("."));
+ let filename = path
+ .file_name()
+ .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid config path"))?
+ .to_os_string();
+ let inotify = Inotify::init()?;
+ inotify.watches().add(
+ directory,
+ WatchMask::CLOSE_WRITE | WatchMask::CREATE | WatchMask::MOVED_TO,
+ )?;
+ Ok(Self {
+ inotify,
+ buffer: vec![0; 4096],
+ filename,
+ })
+ }
+
+ pub fn fd(&self) -> RawFd {
+ self.inotify.as_raw_fd()
+ }
+
+ pub fn changed(&mut self) -> io::Result<bool> {
+ let mut changed = false;
+ for event in self.inotify.read_events(&mut self.buffer)? {
+ changed |= event.name.is_some_and(|name| name == self.filename);
+ }
+ Ok(changed)
+ }
+}
+
fn config_dir() -> Option<PathBuf> {
env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
@@ -296,3 +335,34 @@ impl<'de> de::Deserialize<'de> for Font {
deserializer.deserialize_str(FontVisitor)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::ConfigWatcher;
+ use std::fs;
+ use std::time::{SystemTime, UNIX_EPOCH};
+
+ #[test]
+ fn watcher_detects_overwrite_and_atomic_replacement() {
+ let unique = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap()
+ .as_nanos();
+ let directory =
+ std::env::temp_dir().join(format!("i3bar-mango-{unique}-{}", std::process::id()));
+ let config_path = directory.join("config.toml");
+ let replacement_path = directory.join("config.toml.new");
+ fs::create_dir(&directory).unwrap();
+ fs::write(&config_path, "height = 24").unwrap();
+
+ let mut watcher = ConfigWatcher::new(&config_path).unwrap();
+ fs::write(&config_path, "height = 25").unwrap();
+ assert!(watcher.changed().unwrap());
+
+ fs::write(&replacement_path, "height = 26").unwrap();
+ fs::rename(&replacement_path, &config_path).unwrap();
+ assert!(watcher.changed().unwrap());
+
+ fs::remove_dir_all(directory).unwrap();
+ }
+}