fix(tray): blank Mainstage after cold-start minimized to tray (#1303)

This commit is contained in:
cucadmuh
2026-07-15 11:05:43 +03:00
committed by GitHub
parent 0a52e875a2
commit 6d9018e6b6
6 changed files with 43 additions and 66 deletions
+7 -1
View File
@@ -163,6 +163,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Fixed
### Tray — blank Mainstage after cold start minimized to tray
**By [@cucadmuh](https://github.com/cucadmuh), PR [#1303](https://github.com/Psychotoxical/psysonic/pull/1303)**
* With **Start minimized to tray** enabled, the main content area could stay blank after opening from the tray (sidebar and queue rendered, Mainstage did not) until a manual tray hide/show cycle — `PAUSE_RENDERING_JS` on cold start froze Mainstage's `animate-fade-in` at `opacity: 0`. Cold-start tray now hides without pausing animations, `startup-splash-reveal.js` proactively hides when the tray flag is set, Mainstage drops the entrance fade, and showing the window resumes rendering.
### Tray — release build compile after #1296
**By [@cucadmuh](https://github.com/cucadmuh), PR [#1298](https://github.com/Psychotoxical/psysonic/pull/1298)**
@@ -173,7 +179,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
**By [@cucadmuh](https://github.com/cucadmuh), PR [#1296](https://github.com/Psychotoxical/psysonic/pull/1296)**
* With **Start minimized to tray** enabled, opening the main window from the tray could leave the left sidebar menu invisible until a full app restart (seen on Linux tiling WMs such as Hyprland). The sidebar no longer uses a slide-in entrance animation that starts at `opacity: 0`; cold-start tray hide restores the original pause-and-hide path from PR #1271, and tray show again resumes rendering before `show()` without an extra `unminimize()` that could pop the window on tiling WMs.
* With **Start minimized to tray** enabled, opening the main window from the tray could leave the left sidebar menu invisible until a full app restart (seen on Linux tiling WMs such as Hyprland). The sidebar no longer uses a slide-in entrance animation that starts at `opacity: 0`; tray show resumes rendering before `show()` without an extra `unminimize()` that could pop the window on tiling WMs.
### Playlist and radio custom covers blank
+29 -1
View File
@@ -1,5 +1,7 @@
/**
* Show the native window after the inline startup splash has painted.
* When starting minimized to tray, hide the main window as early as possible
* (visible:false may still map briefly on some Linux WMs before this script).
* __TAURI_INTERNALS__ may not exist yet when this script first runs.
*/
(function startupSplashReveal() {
@@ -12,8 +14,22 @@
return true;
}
function tryHideMainWindow() {
var internals = window.__TAURI_INTERNALS__;
if (!internals || typeof internals.invoke !== 'function') return false;
internals.invoke('plugin:window|hide', { label: 'main' }).catch(function () {});
return true;
}
function reveal(attempt) {
if (window.__psyStartMinimizedToTray) return;
if (window.__psyStartMinimizedToTray) {
if (tryHideMainWindow()) return;
if (attempt >= MAX_ATTEMPTS) return;
window.setTimeout(function () {
reveal(attempt + 1);
}, 50);
return;
}
if (tryShowMainWindow()) return;
if (attempt >= MAX_ATTEMPTS) return;
window.setTimeout(function () {
@@ -21,6 +37,18 @@
}, 50);
}
if (window.__psyStartMinimizedToTray) {
// Mark this synchronously, before React mounts. This deliberately does
// not set the CSS animation-pause attribute: entrance animations may
// still mount while the native window is hidden.
window.__psyHidden = true;
try {
sessionStorage.setItem('psy-startup-tray-handled', '1');
} catch (_err) {}
reveal(0);
return;
}
requestAnimationFrame(function () {
requestAnimationFrame(function () {
reveal(0);
-22
View File
@@ -906,28 +906,6 @@ pub fn run() {
}
}
})
.on_page_load(|webview, payload| {
if webview.label() != "main" {
return;
}
match payload.event() {
tauri::webview::PageLoadEvent::Started => {
let app = webview.app_handle().clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(48));
if let Some(window) = app.get_webview_window("main") {
crate::lib_commands::ui::mini::eval_startup_main_window_visibility(&window);
}
});
}
tauri::webview::PageLoadEvent::Finished => {
if let Some(window) = webview.app_handle().get_webview_window("main") {
crate::lib_commands::ui::mini::eval_startup_main_window_visibility(&window);
}
}
}
})
.invoke_handler(tauri::generate_handler![
greet,
theme_import::import_theme_zip,
-40
View File
@@ -169,46 +169,6 @@ document.documentElement.style.removeProperty('--psy-anim-speed');
})();
"#;
/// Show the main window after startup splash paint, or pause rendering and hide
/// when the user chose "start minimized to tray" (flag set in
/// `startup-splash-preflight.js`).
///
/// The shell no longer uses entrance animations that start at `opacity: 0`, so
/// pausing CSS here is safe on WebKitGTK tiling WMs (sidebar fix in #1296).
pub(crate) fn eval_startup_main_window_visibility(window: &tauri::WebviewWindow) {
let js = format!(
"(function () {{
try {{
if (sessionStorage.getItem('psy-startup-tray-handled') === '1') return;
}} catch (e) {{}}
var deferToTray = !!window.__psyStartMinimizedToTray;
if (!deferToTray) {{
try {{
var raw = localStorage.getItem('psysonic-auth');
if (raw) {{
var state = JSON.parse(raw).state;
deferToTray = !!(state && state.startMinimizedToTray && state.showTrayIcon !== false);
}}
}} catch (e) {{}}
}}
var internals = window.__TAURI_INTERNALS__;
if (deferToTray) {{
{pause}
try {{ sessionStorage.setItem('psy-startup-tray-handled', '1'); }} catch (e) {{}}
if (internals && typeof internals.invoke === 'function') {{
internals.invoke('plugin:window|hide', {{ label: 'main' }}).catch(function () {{}});
}}
return;
}}
if (internals && typeof internals.invoke === 'function') {{
internals.invoke('plugin:window|show', {{ label: 'main' }}).catch(function () {{}});
}}
}})();",
pause = PAUSE_RENDERING_JS.trim(),
);
let _ = window.eval(&js);
}
/// Resume rendering and bring the main window to the foreground.
pub(crate) fn restore_main_window(main: &tauri::WebviewWindow) -> Result<(), String> {
main.eval(RESUME_RENDERING_JS).map_err(|e| e.to_string())?;
+6 -1
View File
@@ -345,7 +345,12 @@ export default function Home() {
// sidebar) instead of leaving the user on nothing.
const allSectionsHidden = homeSections.every(s => !s.visible);
return (
<div className={`animate-fade-in${homeLiteArtworkFx ? ' home-lite-artwork' : ''}${homeFlatArtworkClip ? ' home-flat-artwork-clip' : ''}`}>
<div
className={[
homeLiteArtworkFx ? 'home-lite-artwork' : '',
homeFlatArtworkClip ? 'home-flat-artwork-clip' : '',
].filter(Boolean).join(' ') || undefined}
>
{!loading && !perfFlags.disableMainstageHero && isVisible('hero') && <Hero albums={heroAlbums} />}
<div className="content-body" style={{ display: 'flex', flexDirection: 'column', gap: '3rem' }}>
+1 -1
View File
@@ -1,6 +1,6 @@
const AUTH_STORAGE_KEY = 'psysonic-auth';
/** Keep in sync with `public/startup-splash-preflight.js` and Rust `eval_startup_main_window_visibility`. */
/** Keep in sync with `public/startup-splash-preflight.js` and `public/startup-splash-reveal.js`. */
export const STARTUP_TRAY_HANDLED_KEY = 'psy-startup-tray-handled';
/** Read persisted "start minimized to tray" before Zustand rehydrates. */