Compare commits

...

2 commits
v0.2.7 ... main

Author SHA1 Message Date
Breadway
10f62fb1a6 feat: add breadpaper to ecosystem registry
Some checks failed
Mirror to GitHub / mirror (push) Successful in 2s
Build and publish package / package (push) Failing after 2m11s
2026-06-17 22:55:12 +08:00
Breadway
5e58558dd3 bread-theme 0.2.8: fix live reload — watch the dir, not the file
Some checks failed
Mirror to GitHub / mirror (push) Successful in 2s
Build and publish package / package (push) Failing after 1m54s
The stylesheet is written with write-tmp-then-rename (atomic), which replaces the
inode. A monitor on the file itself caught the first replace then went deaf
(inotify reports DELETE_SELF and never re-arms), so `bread-theme reload` updated
the file but no running GUI ever recoloured. Monitor the parent directory and
filter for the stylesheet filename instead — that fires on every reload. Verified
against a real atomic-rename write (event arrives as Renamed with the new name in
other_file, so match both file and other_file).
2026-06-17 12:53:35 +08:00
2 changed files with 34 additions and 10 deletions

View file

@ -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<gio::FileMonitor> {
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<F: Fn() -> 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);
});
}

View file

@ -36,3 +36,8 @@ description = "Profile-aware Wi-Fi state machine with Tailscale integration"
name = "breadpad"
repo = "Breadway/breadpad"
description = "Quick-capture scratchpad and note viewer with AI classification"
[[products]]
name = "breadpaper"
repo = "Breadway/breadpaper"
description = "Wallpaper manager for the bread desktop"