toml: automatically reload configuration
This commit is contained in:
parent
636b242347
commit
f943036522
6 changed files with 256 additions and 26 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -672,6 +672,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"uapi",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ struct Interest {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Tasks {
|
struct Tasks {
|
||||||
last_id: Cell<u64>,
|
last_id: Cell<u64>,
|
||||||
ready_front: RefCell<VecDeque<u64>>,
|
ready_front: Cell<VecDeque<u64>>,
|
||||||
ready_back: Arc<TasksBackBuffer>,
|
ready_back: Arc<TasksBackBuffer>,
|
||||||
tasks: RefCell<HashMap<u64, Rc<RefCell<Task>>>>,
|
tasks: RefCell<HashMap<u64, Rc<RefCell<Task>>>>,
|
||||||
}
|
}
|
||||||
|
|
@ -1848,9 +1848,9 @@ impl ConfigClient {
|
||||||
if !futures.ready_back.any.load(Relaxed) {
|
if !futures.ready_back.any.load(Relaxed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut ready = futures.ready_front.borrow_mut();
|
let mut ready = futures.ready_front.take();
|
||||||
loop {
|
loop {
|
||||||
mem::swap(&mut *ready, &mut *futures.ready_back.tasks.lock().unwrap());
|
mem::swap(&mut ready, &mut *futures.ready_back.tasks.lock().unwrap());
|
||||||
futures.ready_back.any.store(false, Relaxed);
|
futures.ready_back.any.store(false, Relaxed);
|
||||||
while let Some(id) = ready.pop_front() {
|
while let Some(id) = ready.pop_front() {
|
||||||
let fut = futures.tasks.borrow_mut().get(&id).cloned();
|
let fut = futures.tasks.borrow_mut().get(&id).cloned();
|
||||||
|
|
@ -1863,19 +1863,20 @@ impl ConfigClient {
|
||||||
match res {
|
match res {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::error!("A task panicked");
|
log::error!("A task panicked");
|
||||||
futures.tasks.borrow_mut().remove(&id);
|
let _tmp = futures.tasks.borrow_mut().remove(&id);
|
||||||
}
|
}
|
||||||
Ok(Poll::Ready(())) => {
|
Ok(Poll::Ready(())) => {
|
||||||
futures.tasks.borrow_mut().remove(&id);
|
let _tmp = futures.tasks.borrow_mut().remove(&id);
|
||||||
}
|
}
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !futures.ready_back.any.load(Relaxed) {
|
if !futures.ready_back.any.load(Relaxed) {
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
futures.ready_front.set(ready);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn_task<T: 'static>(&self, f: impl Future<Output = T> + 'static) -> Rc<JoinSlot<T>> {
|
pub fn spawn_task<T: 'static>(&self, f: impl Future<Output = T> + 'static) -> Rc<JoinSlot<T>> {
|
||||||
|
|
@ -1910,7 +1911,7 @@ impl ConfigClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn abort_task(&self, id: u64) {
|
pub fn abort_task(&self, id: u64) {
|
||||||
self.tasks.tasks.borrow_mut().remove(&id);
|
let _tmp = self.tasks.tasks.borrow_mut().remove(&id);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_invoke_shortcut(
|
fn handle_invoke_shortcut(
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ indexmap = "2.2.5"
|
||||||
bstr = { version = "1.9.1", default-features = false }
|
bstr = { version = "1.9.1", default-features = false }
|
||||||
ahash = "0.8.11"
|
ahash = "0.8.11"
|
||||||
run-on-drop = "1.0.0"
|
run-on-drop = "1.0.0"
|
||||||
|
uapi = "0.2.13"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
simplelog = { version = "0.12.2", features = ["test"] }
|
simplelog = { version = "0.12.2", features = ["test"] }
|
||||||
|
|
|
||||||
|
|
@ -488,6 +488,7 @@ pub struct Config {
|
||||||
pub env: Vec<(String, String)>,
|
pub env: Vec<(String, String)>,
|
||||||
pub on_startup: Option<Action>,
|
pub on_startup: Option<Action>,
|
||||||
pub keymaps: Vec<ConfigKeymap>,
|
pub keymaps: Vec<ConfigKeymap>,
|
||||||
|
pub auto_reload: Option<bool>,
|
||||||
pub log_level: Option<LogLevel>,
|
pub log_level: Option<LogLevel>,
|
||||||
pub theme: Theme,
|
pub theme: Theme,
|
||||||
pub gfx_api: Option<GfxApi>,
|
pub gfx_api: Option<GfxApi>,
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
show_bar,
|
show_bar,
|
||||||
focus_history_val,
|
focus_history_val,
|
||||||
),
|
),
|
||||||
(middle_click_paste, input_modes_val, workspace_display_order_val),
|
(middle_click_paste, input_modes_val, workspace_display_order_val, auto_reload),
|
||||||
) = ext.extract((
|
) = ext.extract((
|
||||||
(
|
(
|
||||||
opt(val("keymap")),
|
opt(val("keymap")),
|
||||||
|
|
@ -193,6 +193,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
recover(opt(bol("middle-click-paste"))),
|
recover(opt(bol("middle-click-paste"))),
|
||||||
opt(val("modes")),
|
opt(val("modes")),
|
||||||
opt(val("workspace-display-order")),
|
opt(val("workspace-display-order")),
|
||||||
|
recover(opt(bol("auto-reload"))),
|
||||||
),
|
),
|
||||||
))?;
|
))?;
|
||||||
let mut keymap = None;
|
let mut keymap = None;
|
||||||
|
|
@ -279,6 +280,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut log_level = None;
|
let mut log_level = None;
|
||||||
if let Some(value) = log_level_val {
|
if let Some(value) = log_level_val {
|
||||||
match value.parse(&mut LogLevelParser) {
|
match value.parse(&mut LogLevelParser) {
|
||||||
|
|
@ -516,6 +518,7 @@ impl Parser for ConfigParser<'_> {
|
||||||
env,
|
env,
|
||||||
on_startup,
|
on_startup,
|
||||||
keymaps,
|
keymaps,
|
||||||
|
auto_reload: auto_reload.despan(),
|
||||||
log_level,
|
log_level,
|
||||||
theme,
|
theme,
|
||||||
gfx_api,
|
gfx_api,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ use {
|
||||||
get_seat, input_devices, on_input_device_removed, on_new_input_device,
|
get_seat, input_devices, on_input_device_removed, on_new_input_device,
|
||||||
set_libei_socket_enabled,
|
set_libei_socket_enabled,
|
||||||
},
|
},
|
||||||
|
io::Async,
|
||||||
is_reload,
|
is_reload,
|
||||||
keyboard::Keymap,
|
keyboard::Keymap,
|
||||||
logging::set_log_level,
|
logging::set_log_level,
|
||||||
|
|
@ -41,6 +42,7 @@ use {
|
||||||
set_show_float_pin_icon, set_ui_drag_enabled, set_ui_drag_threshold,
|
set_show_float_pin_icon, set_ui_drag_enabled, set_ui_drag_threshold,
|
||||||
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
|
||||||
switch_to_vt,
|
switch_to_vt,
|
||||||
|
tasks::{self, JoinHandle},
|
||||||
theme::{reset_colors, reset_font, reset_sizes, set_bar_font, set_font, set_title_font},
|
theme::{reset_colors, reset_font, reset_sizes, set_bar_font, set_font, set_title_font},
|
||||||
toggle_float_above_fullscreen, toggle_show_bar,
|
toggle_float_above_fullscreen, toggle_show_bar,
|
||||||
video::{
|
video::{
|
||||||
|
|
@ -56,11 +58,21 @@ use {
|
||||||
run_on_drop::on_drop,
|
run_on_drop::on_drop,
|
||||||
std::{
|
std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
ffi::OsStr,
|
||||||
io::ErrorKind,
|
io::ErrorKind,
|
||||||
path::PathBuf,
|
os::{fd::AsRawFd, unix::ffi::OsStrExt},
|
||||||
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
},
|
},
|
||||||
|
uapi::{
|
||||||
|
Errno,
|
||||||
|
c::{
|
||||||
|
self, CLOCK_MONOTONIC, IN_ATTRIB, IN_CLOEXEC, IN_CLOSE_WRITE, IN_CREATE, IN_DELETE,
|
||||||
|
IN_EXCL_UNLINK, IN_MOVED_FROM, IN_MOVED_TO, IN_NONBLOCK, IN_ONLYDIR, TFD_CLOEXEC,
|
||||||
|
TFD_NONBLOCK, timespec,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn default_seat() -> Seat {
|
fn default_seat() -> Seat {
|
||||||
|
|
@ -169,7 +181,7 @@ impl Action {
|
||||||
SimpleCommand::Quit => b.new(quit),
|
SimpleCommand::Quit => b.new(quit),
|
||||||
SimpleCommand::ReloadConfigToml => {
|
SimpleCommand::ReloadConfigToml => {
|
||||||
let persistent = state.persistent.clone();
|
let persistent = state.persistent.clone();
|
||||||
b.new(move || load_config(false, &persistent))
|
b.new(move || load_config(false, false, &persistent))
|
||||||
}
|
}
|
||||||
SimpleCommand::ReloadConfigSo => b.new(reload),
|
SimpleCommand::ReloadConfigSo => b.new(reload),
|
||||||
SimpleCommand::None => b.new(|| ()),
|
SimpleCommand::None => b.new(|| ()),
|
||||||
|
|
@ -1016,26 +1028,222 @@ struct PersistentState {
|
||||||
window_rules: Cell<Vec<MatcherTemp<WindowRule>>>,
|
window_rules: Cell<Vec<MatcherTemp<WindowRule>>>,
|
||||||
mark_names: RefCell<AHashMap<String, u32>>,
|
mark_names: RefCell<AHashMap<String, u32>>,
|
||||||
mode_state: ModeState,
|
mode_state: ModeState,
|
||||||
|
watcher_handle: RefCell<Option<JoinHandle<()>>>,
|
||||||
|
last_config: RefCell<Option<Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
async fn watch_config(persistent: Rc<PersistentState>) {
|
||||||
let mut path = PathBuf::from(config_dir());
|
let inotify = match uapi::inotify_init1(IN_NONBLOCK | IN_CLOEXEC) {
|
||||||
path.push("config.toml");
|
Ok(i) => i,
|
||||||
let mut config = match std::fs::read(&path) {
|
Err(e) => {
|
||||||
Ok(input) => match parse_config(&input, &persistent.mark_names, |e| {
|
log::error!("Could not create inotify fd: {}", Report::new(e));
|
||||||
log::warn!("Error while parsing {}: {}", path.display(), Report::new(e))
|
return;
|
||||||
}) {
|
}
|
||||||
None if initial_load => {
|
};
|
||||||
log::warn!("Using default config instead");
|
let inotify_async = match Async::new(&inotify) {
|
||||||
persistent.default.clone()
|
Ok(i) => i,
|
||||||
}
|
Err(e) => {
|
||||||
None => {
|
log::error!(
|
||||||
log::warn!("Ignoring config reload");
|
"Could not create Async object for inotify fd: {}",
|
||||||
|
Report::new(e)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let timer = match uapi::timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC) {
|
||||||
|
Ok(t) => Rc::new(t),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not create timer fd: {}", Report::new(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let timer_async = match Async::new(timer.clone()) {
|
||||||
|
Ok(i) => i,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Could not create Async object for timer fd: {}",
|
||||||
|
Report::new(e)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let timer_task = tasks::spawn(async move {
|
||||||
|
loop {
|
||||||
|
if let Err(e) = timer_async.readable().await {
|
||||||
|
log::error!(
|
||||||
|
"Could not wait for timer to become readable: {}",
|
||||||
|
Report::new(e),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Some(c) => c,
|
let mut buf = 0u64;
|
||||||
},
|
if let Err(e) = uapi::read(timer_async.as_ref().raw(), &mut buf) {
|
||||||
|
log::error!("Could not read from timer fd: {}", Report::new(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
load_config(false, true, &persistent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let _cancel_task = on_drop(|| timer_task.abort());
|
||||||
|
|
||||||
|
let program_timer = || {
|
||||||
|
let new_value = c::itimerspec {
|
||||||
|
it_interval: timespec {
|
||||||
|
tv_nsec: 0,
|
||||||
|
tv_sec: 0,
|
||||||
|
},
|
||||||
|
it_value: timespec {
|
||||||
|
tv_nsec: 400_000_000,
|
||||||
|
tv_sec: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if let Err(e) = uapi::timerfd_settime(timer.raw(), 0, &new_value) {
|
||||||
|
log::error!("Could not set timer: {}", Report::new(e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let config_dir = config_dir();
|
||||||
|
let config_dir = Path::new(&config_dir);
|
||||||
|
let mut dirs = vec![];
|
||||||
|
for component in config_dir.components() {
|
||||||
|
dirs.push(component.as_os_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dir_watches = vec![];
|
||||||
|
let mut file_watch = None;
|
||||||
|
|
||||||
|
let mut path_buf = PathBuf::new();
|
||||||
|
let mut create_watches = |dir_watches: &mut Vec<c::c_int>,
|
||||||
|
file_watch: &mut Option<c::c_int>| {
|
||||||
|
path_buf.clear();
|
||||||
|
for (i, dir) in dirs.iter().enumerate() {
|
||||||
|
path_buf.push(dir);
|
||||||
|
if dir_watches.len() > i {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let res = uapi::inotify_add_watch(
|
||||||
|
inotify.raw(),
|
||||||
|
&*path_buf,
|
||||||
|
IN_ONLYDIR
|
||||||
|
| IN_CREATE
|
||||||
|
| IN_DELETE
|
||||||
|
| IN_MOVED_FROM
|
||||||
|
| IN_MOVED_TO
|
||||||
|
| IN_ATTRIB
|
||||||
|
| IN_EXCL_UNLINK,
|
||||||
|
);
|
||||||
|
let Ok(n) = res else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
dir_watches.push(n);
|
||||||
|
}
|
||||||
|
if file_watch.is_none() {
|
||||||
|
path_buf.push(CONFIG_TOML);
|
||||||
|
let res =
|
||||||
|
uapi::inotify_add_watch(inotify.raw(), &*path_buf, IN_CLOSE_WRITE | IN_EXCL_UNLINK);
|
||||||
|
*file_watch = res.ok();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
macro_rules! create_watches {
|
||||||
|
() => {
|
||||||
|
create_watches(&mut dir_watches, &mut file_watch);
|
||||||
|
program_timer();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
create_watches!();
|
||||||
|
|
||||||
|
let mut buffer = vec![0; 1024];
|
||||||
|
loop {
|
||||||
|
let res = uapi::inotify_read(inotify_async.as_ref().as_raw_fd(), &mut *buffer);
|
||||||
|
let events = match res {
|
||||||
|
Ok(e) => e,
|
||||||
|
Err(Errno(c::EAGAIN)) => {
|
||||||
|
inotify_async.readable().await.unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Could not read from inotify fd: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for event in events {
|
||||||
|
if Some(event.wd) == file_watch {
|
||||||
|
program_timer();
|
||||||
|
} else {
|
||||||
|
for i in 0..dir_watches.len() {
|
||||||
|
if event.wd != dir_watches[i] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let next = if i + 1 == dirs.len() {
|
||||||
|
OsStr::new(CONFIG_TOML)
|
||||||
|
} else {
|
||||||
|
dirs[i + 1]
|
||||||
|
};
|
||||||
|
if event.name().to_bytes() != next.as_bytes() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if event.mask & (IN_DELETE | IN_MOVED_FROM) != 0 {
|
||||||
|
for wd in dir_watches.drain(i + 1..) {
|
||||||
|
let _ = uapi::inotify_rm_watch(inotify.raw(), wd);
|
||||||
|
}
|
||||||
|
if let Some(wd) = file_watch.take() {
|
||||||
|
let _ = uapi::inotify_rm_watch(inotify.raw(), wd);
|
||||||
|
}
|
||||||
|
program_timer();
|
||||||
|
}
|
||||||
|
if (event.mask & IN_ATTRIB != 0
|
||||||
|
&& i + 1 == dir_watches.len()
|
||||||
|
&& file_watch.is_none())
|
||||||
|
|| event.mask & (IN_CREATE | IN_MOVED_TO) != 0
|
||||||
|
{
|
||||||
|
create_watches!();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONFIG_TOML: &str = "config.toml";
|
||||||
|
|
||||||
|
fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<PersistentState>) {
|
||||||
|
let mut path = PathBuf::from(config_dir());
|
||||||
|
path.push(CONFIG_TOML);
|
||||||
|
let mut last_config = persistent.last_config.borrow_mut();
|
||||||
|
let mut config = match std::fs::read(&path) {
|
||||||
|
Ok(input) => {
|
||||||
|
if auto_reload {
|
||||||
|
if Some(&input) == last_config.as_ref() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log::info!("Auto reloading config")
|
||||||
|
}
|
||||||
|
let parsed = parse_config(&input, &persistent.mark_names, |e| {
|
||||||
|
log::warn!("Error while parsing {}: {}", path.display(), Report::new(e))
|
||||||
|
});
|
||||||
|
*last_config = Some(input);
|
||||||
|
match parsed {
|
||||||
|
None if initial_load => {
|
||||||
|
log::warn!("Using default config instead");
|
||||||
|
persistent.default.clone()
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
log::warn!("Ignoring config reload");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Some(c) => c,
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||||
|
if auto_reload {
|
||||||
|
if last_config.take().is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log::info!("Auto reloading config")
|
||||||
|
}
|
||||||
log::info!("{} does not exist. Using default config.", path.display());
|
log::info!("{} does not exist. Using default config.", path.display());
|
||||||
persistent.default.clone()
|
persistent.default.clone()
|
||||||
}
|
}
|
||||||
|
|
@ -1045,6 +1253,19 @@ fn load_config(initial_load: bool, persistent: &Rc<PersistentState>) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
drop(last_config);
|
||||||
|
if let Some(auto_reload) = config.auto_reload {
|
||||||
|
if auto_reload {
|
||||||
|
let handle = &mut *persistent.watcher_handle.borrow_mut();
|
||||||
|
if handle.is_none() {
|
||||||
|
*handle = Some(tasks::spawn(watch_config(persistent.clone())));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(handle) = persistent.watcher_handle.take() {
|
||||||
|
handle.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut outputs = AHashMap::new();
|
let mut outputs = AHashMap::new();
|
||||||
for output in &config.outputs {
|
for output in &config.outputs {
|
||||||
if let Some(name) = &output.name {
|
if let Some(name) = &output.name {
|
||||||
|
|
@ -1354,6 +1575,8 @@ pub fn configure() {
|
||||||
window_rules: Default::default(),
|
window_rules: Default::default(),
|
||||||
mark_names,
|
mark_names,
|
||||||
mode_state: Default::default(),
|
mode_state: Default::default(),
|
||||||
|
watcher_handle: Default::default(),
|
||||||
|
last_config: Default::default(),
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
let p = persistent.clone();
|
let p = persistent.clone();
|
||||||
|
|
@ -1363,7 +1586,7 @@ pub fn configure() {
|
||||||
p.mode_state.clear();
|
p.mode_state.clear();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
load_config(true, &persistent);
|
load_config(true, false, &persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
config!(configure);
|
config!(configure);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue