From 5f5aba8dff0b60279145b33d05e705dcb20bb929 Mon Sep 17 00:00:00 2001 From: funman300 Date: Thu, 30 Apr 2026 20:04:43 +0000 Subject: [PATCH] =?UTF-8?q?feat(app):=20window=20polish=20=E2=80=94=20WM?= =?UTF-8?q?=5FCLASS,=20centered=20window,=20crash=20log=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sets Window::name so X11/Wayland taskbars group the game correctly, centers the window on the primary monitor on startup, and installs a panic hook that appends a timestamped crash record to crash.log under the platform data dir (gracefully no-ops when the directory is unavailable). Co-Authored-By: Claude Opus 4.7 (1M context) --- solitaire_app/src/main.rs | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/solitaire_app/src/main.rs b/solitaire_app/src/main.rs index d5c23b9..5b44ec3 100644 --- a/solitaire_app/src/main.rs +++ b/solitaire_app/src/main.rs @@ -1,4 +1,9 @@ +use std::fs::OpenOptions; +use std::io::Write; +use std::time::{SystemTime, UNIX_EPOCH}; + use bevy::prelude::*; +use bevy::window::{MonitorSelection, WindowPosition}; use solitaire_data::{load_settings_from, provider_for_backend, settings_file_path, Settings}; use solitaire_engine::{ AchievementPlugin, AnimationPlugin, AudioPlugin, AutoCompletePlugin, CardAnimationPlugin, @@ -10,6 +15,11 @@ use solitaire_engine::{ }; fn main() { + // Install a panic hook that writes a crash log next to the save files + // before re-running the default hook (so stderr still gets the message + // and any debugger attached still sees the panic). + install_crash_log_hook(); + // Initialise the platform keyring store before any token operations. // On Linux this uses the Secret Service (GNOME Keyring / KWallet); on // macOS it uses the Keychain; on Windows it uses the Credential store. @@ -35,7 +45,11 @@ fn main() { .set(WindowPlugin { primary_window: Some(Window { title: "Solitaire Quest".into(), + // X11/Wayland WM_CLASS so taskbar managers group + // multiple windows of this app correctly. + name: Some("solitaire-quest".into()), resolution: (1280u32, 800u32).into(), + position: WindowPosition::Centered(MonitorSelection::Primary), resize_constraints: bevy::window::WindowResizeConstraints { min_width: 800.0, min_height: 600.0, @@ -87,3 +101,33 @@ fn main() { .add_plugins(UiModalPlugin) .run(); } + +/// Wraps the default panic hook with one that also appends a crash log +/// to `/crash.log` (next to `settings.json`). The default hook +/// still runs afterwards, so stderr output and debugger integration are +/// unchanged. If the data directory is unavailable, the wrapper silently +/// falls through — the default hook handles output either way. +fn install_crash_log_hook() { + let crash_log_path = settings_file_path().and_then(|p| { + p.parent() + .map(|parent| parent.join("crash.log")) + }); + let default_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + if let Some(path) = crash_log_path.as_ref() + && let Ok(mut file) = OpenOptions::new() + .create(true) + .append(true) + .open(path) + { + // Plain unix-seconds timestamp keeps the format trivially + // parseable and avoids pulling in chrono just for this. + let secs = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + let _ = writeln!(file, "----- t={secs} -----\n{info}\n"); + } + default_hook(info); + })); +}