feat: switch to systemd-journal-logger, add debug logging (v0.6.0)

Replace env_logger with systemd-journal-logger for consistent logging
across moonset/moonlock/moongreet. Add MOONSET_DEBUG env var and debug
statements across all modules. Also includes shared blur cache for
multi-monitor and detached moonlock spawn for lock action.
This commit is contained in:
2026-03-28 22:58:25 +01:00
parent 14affb1533
commit 71670eb263
11 changed files with 131 additions and 199 deletions
+5 -1
View File
@@ -33,6 +33,7 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
if let Ok(content) = fs::read_to_string(path) {
match toml::from_str::<Config>(&content) {
Ok(parsed) => {
log::debug!("Config loaded: {}", path.display());
if parsed.background_path.is_some() {
merged.background_path = parsed.background_path;
}
@@ -41,7 +42,7 @@ pub fn load_config(config_paths: Option<&[PathBuf]>) -> Config {
}
}
Err(e) => {
eprintln!("Warning: failed to parse {}: {e}", path.display());
log::warn!("Failed to parse {}: {e}", path.display());
}
}
}
@@ -63,17 +64,20 @@ pub fn resolve_background_path_with(config: &Config, moonarch_wallpaper: &Path)
if let Some(ref bg) = config.background_path {
let path = PathBuf::from(bg);
if path.is_file() {
log::debug!("Wallpaper source: config ({})", path.display());
return path;
}
}
// Moonarch ecosystem default
if moonarch_wallpaper.is_file() {
log::debug!("Wallpaper source: moonarch default ({})", moonarch_wallpaper.display());
return moonarch_wallpaper.to_path_buf();
}
// GResource fallback path (loaded from compiled resources at runtime)
let prefix = crate::GRESOURCE_PREFIX;
log::debug!("Wallpaper source: GResource fallback");
PathBuf::from(format!("{prefix}/wallpaper.jpg"))
}
+11 -6
View File
@@ -112,15 +112,20 @@ fn read_lang_from_conf(path: &Path) -> Option<String> {
/// Determine the system language from LANG env var or /etc/locale.conf.
pub fn detect_locale() -> String {
let lang = env::var("LANG")
.ok()
.filter(|s| !s.is_empty())
.or_else(|| read_lang_from_conf(Path::new(DEFAULT_LOCALE_CONF)));
let (raw, source) = if let Some(val) = env::var("LANG").ok().filter(|s| !s.is_empty()) {
(Some(val), "LANG env")
} else if let Some(val) = read_lang_from_conf(Path::new(DEFAULT_LOCALE_CONF)) {
(Some(val), "locale.conf")
} else {
(None, "default")
};
match lang {
let result = match raw {
Some(l) => parse_lang_prefix(&l),
None => "en".to_string(),
}
};
log::debug!("Detected locale: {result} (source: {source})");
result
}
/// Return the string table for the given locale, defaulting to English.
+26 -7
View File
@@ -53,14 +53,16 @@ fn activate(app: &gtk::Application) {
load_css(&display);
// Resolve wallpaper once, decode texture once, share across all windows
// Blur is applied on the GPU via GskBlurNode at widget realization time.
// Resolve wallpaper once, decode texture once, share across all windows.
// Blur is applied on the GPU via GskBlurNode at first widget realization,
// then cached and reused by all subsequent windows.
let config = config::load_config(None);
let bg_path = config::resolve_background_path(&config);
let texture = panel::load_background_texture(&bg_path);
let blur_cache = panel::new_blur_cache();
// Panel on focused output (no set_monitor → compositor picks focused)
let panel = panel::create_panel_window(&texture, config.background_blur, app);
let panel = panel::create_panel_window(&texture, config.background_blur, &blur_cache, app);
setup_layer_shell(&panel, true, gtk4_layer_shell::Layer::Overlay);
panel.present();
@@ -68,7 +70,7 @@ fn activate(app: &gtk::Application) {
let monitors = display.monitors();
for i in 0..monitors.n_items() {
if let Some(monitor) = monitors.item(i).and_then(|obj| obj.downcast::<gdk::Monitor>().ok()) {
let wallpaper = panel::create_wallpaper_window(&texture, config.background_blur, app);
let wallpaper = panel::create_wallpaper_window(&texture, config.background_blur, &blur_cache, app);
setup_layer_shell(&wallpaper, false, gtk4_layer_shell::Layer::Top);
wallpaper.set_monitor(Some(&monitor));
wallpaper.present();
@@ -76,10 +78,27 @@ fn activate(app: &gtk::Application) {
}
}
fn setup_logging() {
match systemd_journal_logger::JournalLog::new() {
Ok(logger) => {
if let Err(e) = logger.install() {
eprintln!("Failed to install journal logger: {e}");
}
}
Err(e) => {
eprintln!("Failed to create journal logger: {e}");
}
}
let level = if std::env::var("MOONSET_DEBUG").is_ok() {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
};
log::set_max_level(level);
}
fn main() {
env_logger::Builder::from_default_env()
.filter_level(log::LevelFilter::Info)
.init();
setup_logging();
log::info!("Moonset starting");
// Register compiled GResources
+34 -6
View File
@@ -91,6 +91,7 @@ pub fn action_definitions() -> Vec<ActionDef> {
pub fn load_background_texture(bg_path: &Path) -> gdk::Texture {
let fallback = format!("{}/wallpaper.jpg", crate::GRESOURCE_PREFIX);
log::debug!("Background: {}", bg_path.display());
if bg_path.starts_with(crate::GRESOURCE_PREFIX) {
let resource_path = bg_path.to_str().unwrap_or(&fallback);
gdk::Texture::from_resource(resource_path)
@@ -134,14 +135,19 @@ fn fade_out_and_quit(app: &gtk::Application) {
});
}
/// Create a new shared blur cache for GPU-blurred wallpaper textures.
pub fn new_blur_cache() -> BlurCache {
Rc::new(RefCell::new(None))
}
/// Create a wallpaper-only window for secondary monitors.
pub fn create_wallpaper_window(texture: &gdk::Texture, blur_radius: Option<f32>, app: &gtk::Application) -> gtk::ApplicationWindow {
pub fn create_wallpaper_window(texture: &gdk::Texture, blur_radius: Option<f32>, blur_cache: &BlurCache, app: &gtk::Application) -> gtk::ApplicationWindow {
let window = gtk::ApplicationWindow::builder()
.application(app)
.build();
window.add_css_class("wallpaper");
let background = create_background_picture(texture, blur_radius);
let background = create_background_picture(texture, blur_radius, blur_cache);
window.set_child(Some(&background));
// Fade-in on map
@@ -159,7 +165,7 @@ pub fn create_wallpaper_window(texture: &gdk::Texture, blur_radius: Option<f32>,
}
/// Create the main panel window with action buttons and confirm flow.
pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option<f32>, app: &gtk::Application) -> gtk::ApplicationWindow {
pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option<f32>, blur_cache: &BlurCache, app: &gtk::Application) -> gtk::ApplicationWindow {
let window = gtk::ApplicationWindow::builder()
.application(app)
.build();
@@ -172,6 +178,7 @@ pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option<f32>, app
home: dirs::home_dir().unwrap_or_default(),
uid: u32::MAX,
});
log::debug!("User: {} ({})", user.display_name, user.username);
// State for confirm box
let confirm_box: Rc<RefCell<Option<gtk::Box>>> = Rc::new(RefCell::new(None));
@@ -181,7 +188,7 @@ pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option<f32>, app
window.set_child(Some(&overlay));
// Background wallpaper
let background = create_background_picture(texture, blur_radius);
let background = create_background_picture(texture, blur_radius, blur_cache);
overlay.set_child(Some(&background));
// Click on background dismisses the menu
@@ -285,8 +292,17 @@ pub fn create_panel_window(texture: &gdk::Texture, blur_radius: Option<f32>, app
window
}
/// Shared cache for the GPU-blurred wallpaper texture.
/// Computed once on first window realize, reused by all subsequent windows.
type BlurCache = Rc<RefCell<Option<gdk::Texture>>>;
/// Create a Picture widget for the wallpaper background, optionally with GPU blur.
fn create_background_picture(texture: &gdk::Texture, blur_radius: Option<f32>) -> gtk::Picture {
/// When a blur_cache is provided, the blurred texture is computed once and shared.
fn create_background_picture(
texture: &gdk::Texture,
blur_radius: Option<f32>,
blur_cache: &BlurCache,
) -> gtk::Picture {
let background = gtk::Picture::for_paintable(texture);
background.set_content_fit(gtk::ContentFit::Cover);
background.set_hexpand(true);
@@ -294,9 +310,18 @@ fn create_background_picture(texture: &gdk::Texture, blur_radius: Option<f32>) -
if let Some(sigma) = blur_radius.filter(|s| *s > 0.0) {
let texture = texture.clone();
let cache = blur_cache.clone();
background.connect_realize(move |picture| {
// Use cached blur if available, otherwise compute and cache
if let Some(ref cached) = *cache.borrow() {
log::debug!("Blur cache hit");
picture.set_paintable(Some(cached));
return;
}
log::debug!("Blur cache miss, rendering GPU blur");
if let Some(blurred) = render_blurred_texture(picture, &texture, sigma) {
picture.set_paintable(Some(&blurred));
*cache.borrow_mut() = Some(blurred);
}
});
}
@@ -470,6 +495,7 @@ fn execute_action(
error_label: &gtk::Label,
) {
dismiss_confirm(confirm_area, confirm_box);
log::debug!("Executing power action: {}", action_def.name);
let action_fn = action_def.action_fn;
let action_name = action_def.name;
@@ -514,6 +540,7 @@ fn load_avatar_async(image: &gtk::Image, window: &gtk::ApplicationWindow, user:
match avatar_path {
Some(path) => {
log::debug!("Avatar source: file {}", path.display());
// File-based avatar: load and scale in background thread
glib::spawn_future_local(clone!(
#[weak]
@@ -521,7 +548,7 @@ fn load_avatar_async(image: &gtk::Image, window: &gtk::ApplicationWindow, user:
async move {
let result = gio::spawn_blocking(move || {
Pixbuf::from_file_at_scale(
path.to_str().unwrap_or(""),
&path,
AVATAR_SIZE,
AVATAR_SIZE,
true,
@@ -539,6 +566,7 @@ fn load_avatar_async(image: &gtk::Image, window: &gtk::ApplicationWindow, user:
));
}
None => {
log::debug!("Avatar source: default SVG");
// Default SVG avatar: needs widget color, keep synchronous
set_default_avatar(image, window);
}
+18 -1
View File
@@ -31,6 +31,7 @@ impl std::error::Error for PowerError {}
/// Run a command with timeout and return a PowerError on failure.
fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(), PowerError> {
log::debug!("Power action: {action} ({program} {args:?})");
let mut child = Command::new(program)
.args(args)
.stdout(Stdio::piped())
@@ -45,6 +46,9 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(),
loop {
match child.try_wait() {
Ok(Some(status)) => {
if status.success() {
log::debug!("Power action {action} completed");
}
if !status.success() {
let mut stderr_buf = String::new();
if let Some(mut stderr) = child.stderr.take() {
@@ -76,8 +80,21 @@ fn run_command(action: &'static str, program: &str, args: &[&str]) -> Result<(),
}
/// Lock the current session by launching moonlock.
/// Spawns moonlock as a detached process and returns immediately —
/// moonlock runs independently until the user unlocks.
pub fn lock() -> Result<(), PowerError> {
run_command("lock", "/usr/bin/moonlock", &[])
log::debug!("Power action: lock (spawning moonlock)");
Command::new("/usr/bin/moonlock")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(|e| PowerError::CommandFailed {
action: "lock",
message: e.to_string(),
})?;
// Child handle is dropped here — moonlock continues running independently.
Ok(())
}
/// Quit the Niri compositor (logout).
+2
View File
@@ -55,6 +55,7 @@ pub fn get_avatar_path_with(
// ~/.face takes priority
let face = home.join(".face");
if face.exists() {
log::debug!("Avatar: using ~/.face");
return Some(face);
}
@@ -63,6 +64,7 @@ pub fn get_avatar_path_with(
if accountsservice_dir.exists() {
let icon = accountsservice_dir.join(name);
if icon.exists() {
log::debug!("Avatar: using AccountsService icon");
return Some(icon);
}
}