feat(desktop): notify renderer when GPU acceleration is disabled due to remote display

Remote displays (RDP/SSH/X11) silently disable GPU hardware acceleration with
only a console.log, leaving the user unaware that software rendering is
active. Expose the detected reason over IPC and surface a dismissible banner
in the renderer.
This commit is contained in:
sprmn24 2026-06-20 00:50:51 +03:00 committed by Teknium
parent 64b21e50fb
commit 8ebe37f6ad
10 changed files with 75 additions and 0 deletions

View file

@ -150,6 +150,8 @@ if (REMOTE_DISPLAY_REASON) {
)
}
ipcMain.handle('hermes:get-remote-display-reason', () => REMOTE_DISPLAY_REASON)
// Keep the renderer running at full speed while the window is in the background
// or occluded. The chat transcript streams to screen through a
// requestAnimationFrame-gated flush; Chromium pauses rAF (and clamps timers)

View file

@ -140,6 +140,7 @@ contextBridge.exposeInMainWorld('hermesDesktop', {
return () => ipcRenderer.removeListener('hermes:bootstrap:event', listener)
},
getVersion: () => ipcRenderer.invoke('hermes:version'),
getRemoteDisplayReason: () => ipcRenderer.invoke('hermes:get-remote-display-reason'),
uninstall: {
summary: () => ipcRenderer.invoke('hermes:uninstall:summary'),
run: mode => ipcRenderer.invoke('hermes:uninstall:run', { mode })

View file

@ -8,6 +8,7 @@ import { DesktopInstallOverlay } from '@/components/desktop-install-overlay'
import { DesktopOnboardingOverlay } from '@/components/desktop-onboarding-overlay'
import { GatewayConnectingOverlay } from '@/components/gateway-connecting-overlay'
import { Pane, PaneMain } from '@/components/pane-shell'
import { RemoteDisplayBanner } from '@/components/remote-display-banner'
import { useMediaQuery } from '@/hooks/use-media-query'
import { useSkinCommand } from '@/themes/use-skin-command'
@ -956,6 +957,7 @@ export function DesktopController() {
const overlays = (
<>
<RemoteDisplayBanner />
{!isSecondaryWindow() && <DesktopInstallOverlay />}
{!isSecondaryWindow() && (
<DesktopOnboardingOverlay

View file

@ -0,0 +1,42 @@
import { useEffect, useState } from 'react'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Codicon } from '@/components/ui/codicon'
import { useI18n } from '@/i18n'
import { Info } from '@/lib/icons'
export function RemoteDisplayBanner() {
const { t } = useI18n()
const [reason, setReason] = useState<string | null>(null)
const [dismissed, setDismissed] = useState(false)
useEffect(() => {
void window.hermesDesktop?.getRemoteDisplayReason?.().then(result => setReason(result))
}, [])
if (!reason || dismissed) {
return null
}
return (
<div className="pointer-events-none fixed left-1/2 top-[calc(var(--titlebar-height,34px)+0.75rem)] z-[200] w-[min(32rem,calc(100%-2rem))] -translate-x-1/2">
<Alert className="pointer-events-auto grid-cols-[auto_minmax(0,1fr)_auto] border-(--stroke-nous) bg-popover/95 pr-2.5 shadow-nous backdrop-blur-md">
<Info className="text-muted-foreground" />
<AlertDescription className="col-start-2">
<p className="m-0">{t.remoteDisplayBanner.message(reason)}</p>
</AlertDescription>
<Button
aria-label={t.remoteDisplayBanner.dismiss}
className="col-start-3 -mr-1 text-muted-foreground"
onClick={() => setDismissed(true)}
size="icon-xs"
type="button"
variant="ghost"
>
<Codicon name="close" size="0.875rem" />
</Button>
</Alert>
</div>
)
}

View file

@ -102,6 +102,7 @@ declare global {
cancelBootstrap: () => Promise<{ ok: boolean; cancelled: boolean }>
onBootstrapEvent: (callback: (payload: DesktopBootstrapEvent) => void) => () => void
getVersion: () => Promise<DesktopVersionInfo>
getRemoteDisplayReason?: () => Promise<string | null>
updates: {
check: () => Promise<DesktopUpdateStatus>
apply: (opts?: DesktopUpdateApplyOptions) => Promise<DesktopUpdateApplyResult>

View file

@ -146,6 +146,12 @@ export const en: Translations = {
}
},
remoteDisplayBanner: {
message: reason =>
`Software rendering active — remote display detected (${reason}). GPU acceleration is disabled to prevent flickering.`,
dismiss: 'Dismiss'
},
titlebar: {
hideSidebar: 'Hide sidebar',
showSidebar: 'Show sidebar',

View file

@ -147,6 +147,12 @@ export const ja = defineLocale({
}
},
remoteDisplayBanner: {
message: reason =>
`ソフトウェアレンダリングが有効です — リモートディスプレイを検出しました(${reason})。ちらつきを防ぐため GPU アクセラレーションは無効化されています。`,
dismiss: '閉じる'
},
titlebar: {
hideSidebar: 'サイドバーを非表示',
showSidebar: 'サイドバーを表示',

View file

@ -159,6 +159,11 @@ export interface Translations {
}
}
remoteDisplayBanner: {
message: (reason: string) => string
dismiss: string
}
titlebar: {
hideSidebar: string
showSidebar: string

View file

@ -142,6 +142,11 @@ export const zhHant = defineLocale({
}
},
remoteDisplayBanner: {
message: reason => `軟體繪圖已啟用 — 偵測到遠端顯示(${reason})。為防止畫面閃爍,已停用 GPU 加速。`,
dismiss: '關閉'
},
titlebar: {
hideSidebar: '隱藏側邊欄',
showSidebar: '顯示側邊欄',

View file

@ -142,6 +142,11 @@ export const zh: Translations = {
}
},
remoteDisplayBanner: {
message: reason => `软件渲染已启用 — 检测到远程显示(${reason})。为防止画面闪烁,已禁用 GPU 加速。`,
dismiss: '关闭'
},
titlebar: {
hideSidebar: '隐藏侧边栏',
showSidebar: '显示侧边栏',