config: live reload on SIGUSR1

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I2549feccf6eb3a48ea39dbf0e370e0c46a6a6964
This commit is contained in:
raf 2026-06-25 11:09:40 +03:00
commit 0ecda1b5ce
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
2 changed files with 49 additions and 2 deletions

View file

@ -62,5 +62,5 @@ fn run(cli: Cli) -> anyhow::Result<ExitCode> {
let config = Config::load(cli.config.as_deref()); let config = Config::load(cli.config.as_deref());
tracing::info!("starting beer"); tracing::info!("starting beer");
wayland::run(config) wayland::run(config, cli.config)
} }

View file

@ -170,7 +170,7 @@ const DEFAULT_W: u32 = 800;
const DEFAULT_H: u32 = 600; const DEFAULT_H: u32 = 600;
/// Run a single window until it is closed, returning the shell's exit code. /// Run a single window until it is closed, returning the shell's exit code.
pub fn run(config: Config) -> anyhow::Result<ExitCode> { pub fn run(config: Config, config_path: Option<std::path::PathBuf>) -> anyhow::Result<ExitCode> {
let conn = Connection::connect_to_env().context("connect to Wayland compositor")?; let conn = Connection::connect_to_env().context("connect to Wayland compositor")?;
let (globals, event_queue) = let (globals, event_queue) =
registry_queue_init(&conn).context("initialize Wayland registry")?; registry_queue_init(&conn).context("initialize Wayland registry")?;
@ -254,6 +254,7 @@ pub fn run(config: Config) -> anyhow::Result<ExitCode> {
session: None, session: None,
title: None, title: None,
config, config,
config_path,
bindings, bindings,
font_size, font_size,
fullscreen: false, fullscreen: false,
@ -286,6 +287,19 @@ pub fn run(config: Config) -> anyhow::Result<ExitCode> {
tracing::warn!("register blink timer: {err}"); tracing::warn!("register blink timer: {err}");
} }
// SIGUSR1 reloads the config in place.
match calloop::signals::Signals::new(&[calloop::signals::Signal::SIGUSR1]) {
Ok(signals) => {
let registered = event_loop
.handle()
.insert_source(signals, |_, _, app: &mut App| app.reload_config());
if let Err(err) = registered {
tracing::warn!("register signal source: {err}");
}
}
Err(err) => tracing::warn!("install SIGUSR1 handler: {err}"),
}
// Each iteration blocks until an event (PTY output, input, configure, frame // Each iteration blocks until an event (PTY output, input, configure, frame
// callback, blink) arrives, then presents at most one frame; bursts of PTY // callback, blink) arrives, then presents at most one frame; bursts of PTY
// output between frame callbacks coalesce into a single repaint. // output between frame callbacks coalesce into a single repaint.
@ -380,6 +394,8 @@ struct App {
title: Option<String>, title: Option<String>,
/// The active user configuration. /// The active user configuration.
config: Config, config: Config,
/// Path the config was loaded from, for SIGUSR1 live reload.
config_path: Option<std::path::PathBuf>,
/// Resolved key/text bindings. /// Resolved key/text bindings.
bindings: crate::bindings::Bindings, bindings: crate::bindings::Bindings,
/// Current font size in pixels (changed by font-resize bindings). /// Current font size in pixels (changed by font-resize bindings).
@ -575,6 +591,37 @@ impl App {
} }
} }
/// Re-read the config file and apply it in place (SIGUSR1).
fn reload_config(&mut self) {
let new = Config::load(self.config_path.as_deref());
self.bindings =
crate::bindings::Bindings::from_config(&new.key_bindings, &new.text_bindings);
self.renderer.set_padding(new.main.pad_x, new.main.pad_y);
if new.main.font != self.config.main.font || new.main.font_size != self.font_size {
match self.renderer.set_font(&new.main.font, new.main.font_size) {
Ok(()) => self.font_size = new.main.font_size,
Err(err) => tracing::warn!("reload font: {err:#}"),
}
}
if let Some(session) = self.session.as_mut() {
session
.term
.set_theme(crate::theme::Theme::from_config(&new.colors));
let grid = session.term.grid_mut();
grid.set_word_delimiters(new.main.word_delimiters.clone());
grid.set_scrollback_cap(new.scrollback.lines);
if let Some(shape) = cursor_shape_from(new.cursor.style.as_deref()) {
grid.set_cursor_shape(shape);
}
grid.set_cursor_blink(new.cursor.blink);
}
self.config = new;
self.frames.clear();
self.resize_grid();
self.needs_draw = true;
tracing::info!("config reloaded");
}
/// Re-rasterize the font at `new_size`, then re-derive the grid geometry. /// Re-rasterize the font at `new_size`, then re-derive the grid geometry.
fn change_font_size(&mut self, new_size: u32) { fn change_font_size(&mut self, new_size: u32) {
let new_size = new_size.clamp(6, 200); let new_size = new_size.clamp(6, 200);