diff --git a/bread-theme/src/gtk.rs b/bread-theme/src/gtk.rs index bc68dc2..aab7d01 100644 --- a/bread-theme/src/gtk.rs +++ b/bread-theme/src/gtk.rs @@ -26,6 +26,33 @@ fn reload_app() { } } +/// Watch the shared stylesheet for changes and run `reload` when it's rewritten. +/// +/// `bread-theme` writes the file with write-tmp-then-rename (atomic), which +/// *replaces the inode*. A monitor on the file itself dies after the first +/// replace (inotify reports DELETE_SELF and never re-arms), so we monitor the +/// parent *directory* and filter for the stylesheet's filename — that fires +/// reliably on every reload. Returns the monitor (keep it alive to stay armed). +fn watch_theme_file(reload: fn()) -> Option { + let target = crate::shared_css_path(); + let dir = target.parent()?; + // The dir must exist to be monitored; `bread-theme generate` makes it at + // login, but create it here too so a GUI started first still arms the watch. + let _ = std::fs::create_dir_all(dir); + let monitor = gio::File::for_path(dir) + .monitor_directory(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE) + .ok()?; + monitor.connect_changed(move |_, file, other, _event| { + // The rename lands as an event whose file (or move destination) is the + // stylesheet. Match either to catch both CREATED/CHANGED and MOVED_IN. + let is_target = |f: &gio::File| f.path().as_deref() == Some(target.as_path()); + if is_target(file) || other.is_some_and(is_target) { + reload(); + } + }); + Some(monitor) +} + /// Apply an app's *own* stylesheet and keep it live across palette changes. /// /// `build` is called now to produce the app-specific CSS, and again every time @@ -47,11 +74,7 @@ pub fn apply_app_css String + 'static>(build: F) { if cell.borrow().is_some() { return; } - let file = gio::File::for_path(crate::shared_css_path()); - if let Ok(monitor) = file.monitor_file(gio::FileMonitorFlags::NONE, gio::Cancellable::NONE) { - monitor.connect_changed(|_, _, _, _| reload_app()); - *cell.borrow_mut() = Some(monitor); - } + *cell.borrow_mut() = watch_theme_file(reload_app); }); } @@ -68,11 +91,7 @@ pub fn apply_shared() { if cell.borrow().is_some() { return; } - let file = gio::File::for_path(crate::shared_css_path()); - if let Ok(monitor) = file.monitor_file(gio::FileMonitorFlags::NONE, gio::Cancellable::NONE) { - monitor.connect_changed(|_, _, _, _| reload_shared()); - *cell.borrow_mut() = Some(monitor); - } + *cell.borrow_mut() = watch_theme_file(reload_shared); }); }