diff --git a/src/App.tsx b/src/App.tsx index 006609ce..8a74a925 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -931,7 +931,7 @@ function TauriEventBridge() { unlisten.push(u); } - // window:close-requested is emitted by Rust (prevent_close + emit). + // window:close-requested is emitted by Rust (prevent_close + emit). // JS decides: minimize to tray or exit, based on user setting. const u = await listen('window:close-requested', async () => { if (useAuthStore.getState().minimizeToTray) { diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index 05277c79..3603021b 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -95,7 +95,7 @@ export default function Hero({ albums: albumsProp }: HeroProps = {}) { timerRef.current = null; if (len <= 1 || windowHidden) return; timerRef.current = setInterval(() => { - if (document.hidden || (window as any).__psyHidden) return; + if (document.hidden || window.__psyHidden) return; setActiveIdx(prev => (prev + 1) % len); }, INTERVAL_MS); }, [windowHidden]); diff --git a/src/components/MiniPlayer.tsx b/src/components/MiniPlayer.tsx index 613bac4a..df90ea3c 100644 --- a/src/components/MiniPlayer.tsx +++ b/src/components/MiniPlayer.tsx @@ -239,7 +239,7 @@ export default function MiniPlayer() { if (typeof e.payload.volume === 'number') setVolumeState(e.payload.volume); }); const unProgress = listen('audio:progress', (e) => { - if (hiddenRef.current || (window as any).__psyHidden) return; + if (hiddenRef.current || window.__psyHidden) return; setCurrentTime(e.payload.current_time); if (e.payload.duration > 0) setDuration(e.payload.duration); }); diff --git a/src/components/PlaybackScheduleBadge.tsx b/src/components/PlaybackScheduleBadge.tsx index 40a577de..7c9de1ee 100644 --- a/src/components/PlaybackScheduleBadge.tsx +++ b/src/components/PlaybackScheduleBadge.tsx @@ -52,7 +52,7 @@ export default function PlaybackScheduleBadge({ layoutAnchorRef, className }: Pl useEffect(() => { if (deadlineMs == null || windowHidden) return; const id = window.setInterval(() => { - if (document.hidden || (window as any).__psyHidden) return; + if (document.hidden || window.__psyHidden) return; setNowMs(Date.now()); }, 500); return () => window.clearInterval(id); diff --git a/src/components/WaveformSeek.tsx b/src/components/WaveformSeek.tsx index 110b28e9..3f710723 100644 --- a/src/components/WaveformSeek.tsx +++ b/src/components/WaveformSeek.tsx @@ -750,7 +750,7 @@ export function SeekbarPreview({ } }; const tick = () => { - if (document.hidden || (window as any).__psyHidden) { + if (document.hidden || window.__psyHidden) { pollId = window.setTimeout(() => { pollId = null; tick(); @@ -900,7 +900,7 @@ export default function WaveformSeek({ trackId }: Props) { } }; const tick = () => { - if (document.hidden || (window as any).__psyHidden) { + if (document.hidden || window.__psyHidden) { pollId = window.setTimeout(() => { pollId = null; tick(); diff --git a/src/hooks/useWindowVisibility.tsx b/src/hooks/useWindowVisibility.tsx index a2c90a70..2ef84a18 100644 --- a/src/hooks/useWindowVisibility.tsx +++ b/src/hooks/useWindowVisibility.tsx @@ -14,25 +14,31 @@ const WindowVisibilityContext = createContext(false); * * On Windows WebView2, `visibilitychange` and `blur`/`focus` events do not * fire when `win.hide()` is called. We fall back to polling `document.hidden` - * with an adaptive interval: fast checks while visible (to catch show quickly), - * slow checks while hidden (to minimize CPU wakeups). + * OR-ed with `window.__psyHidden` (set from Rust before/after `win.hide()` / + * `show()`) — the latter is the reliable signal on WebView2 where + * `document.hidden` may stay false. Adaptive interval: slow while hidden + * (minimize wakeups), 500 ms while visible (catch show without burning CPU). */ +function isWindowHidden() { + return document.hidden || !!window.__psyHidden; +} + export function WindowVisibilityProvider({ children }: { children: ReactNode }) { - const [hidden, setHidden] = useState(document.hidden); + const [hidden, setHidden] = useState(isWindowHidden); const hiddenRef = useRef(hidden); useEffect(() => { - hiddenRef.current = document.hidden; + hiddenRef.current = isWindowHidden(); let cancelled = false; let timeoutId: ReturnType | null = null; const schedule = () => { if (cancelled) return; - const interval = hiddenRef.current ? 1000 : 200; + const interval = hiddenRef.current ? 1000 : 500; timeoutId = setTimeout(() => { timeoutId = null; if (cancelled) return; - const current = document.hidden; + const current = isWindowHidden(); if (current !== hiddenRef.current) { hiddenRef.current = current; setHidden(current); diff --git a/src/utils/playbackScheduleFormat.ts b/src/utils/playbackScheduleFormat.ts index f58f9ff9..1335d114 100644 --- a/src/utils/playbackScheduleFormat.ts +++ b/src/utils/playbackScheduleFormat.ts @@ -48,7 +48,7 @@ export function usePlaybackScheduleRemaining(): PlaybackScheduleInfo | null { useEffect(() => { if (deadlineMs == null || windowHidden) return; const id = window.setInterval(() => { - if (document.hidden || (window as any).__psyHidden) return; + if (document.hidden || window.__psyHidden) return; setNowMs(Date.now()); }, 500); return () => window.clearInterval(id); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe2..0c02eab1 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,9 @@ /// + +declare global { + interface Window { + __psyHidden?: boolean; + } +} + +export {};