Files
hermes-agent/apps/desktop/src/lib/renderer-loop-pause.ts
T
hermes-seaeye[bot] 610576454a fmt(js): npm run fix on merge (#106231)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-09 03:02:27 +00:00

76 lines
2.1 KiB
TypeScript

interface WindowStatePayload {
isMinimized?: boolean
isVisible?: boolean
}
export const RENDERER_ANIMATIONS_PAUSED_ATTRIBUTE = 'data-renderer-animations-paused'
export function createRendererLoopPauseController(onChange: () => void, { pauseWhenUnfocused = false } = {}) {
let windowPaused = false
let windowFocused = document.hasFocus()
const onVisibilityChange = () => onChange()
const onBlur = () => {
if (windowFocused) {
windowFocused = false
onChange()
}
}
const onFocus = () => {
if (!windowFocused) {
windowFocused = true
onChange()
}
}
const offWindowState = window.hermesDesktop?.onWindowStateChanged?.((payload: WindowStatePayload) => {
const next = payload?.isMinimized === true || payload?.isVisible === false
if (windowPaused === next) {
return
}
windowPaused = next
onChange()
})
document.addEventListener('visibilitychange', onVisibilityChange)
if (pauseWhenUnfocused) {
window.addEventListener('blur', onBlur)
window.addEventListener('focus', onFocus)
}
return {
dispose: () => {
document.removeEventListener('visibilitychange', onVisibilityChange)
window.removeEventListener('blur', onBlur)
window.removeEventListener('focus', onFocus)
offWindowState?.()
},
isPaused: () => document.visibilityState === 'hidden' || (pauseWhenUnfocused && !windowFocused) || windowPaused
}
}
/**
* Mirrors the main window's observability onto :root so continuous decorative
* CSS animations can sleep with the JS renderer loops. The caller owns the
* returned cleanup; overlay windows intentionally do not install this state.
*/
export function installRendererAnimationPauseState(): () => void {
const root = document.documentElement
let controller: ReturnType<typeof createRendererLoopPauseController>
const sync = () => root.toggleAttribute(RENDERER_ANIMATIONS_PAUSED_ATTRIBUTE, controller.isPaused())
controller = createRendererLoopPauseController(sync)
sync()
return () => {
controller.dispose()
root.removeAttribute(RENDERER_ANIMATIONS_PAUSED_ATTRIBUTE)
}
}