diff --git a/apps/desktop/electron/main.cjs b/apps/desktop/electron/main.cjs index db573a1e0d2..0a4f8eec8ad 100644 --- a/apps/desktop/electron/main.cjs +++ b/apps/desktop/electron/main.cjs @@ -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) diff --git a/apps/desktop/electron/preload.cjs b/apps/desktop/electron/preload.cjs index 413abd77b32..f033475c544 100644 --- a/apps/desktop/electron/preload.cjs +++ b/apps/desktop/electron/preload.cjs @@ -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 }) diff --git a/apps/desktop/src/app/desktop-controller.tsx b/apps/desktop/src/app/desktop-controller.tsx index 5ca73061135..c8cb9facc13 100644 --- a/apps/desktop/src/app/desktop-controller.tsx +++ b/apps/desktop/src/app/desktop-controller.tsx @@ -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 = ( <> + {!isSecondaryWindow() && } {!isSecondaryWindow() && ( (null) + const [dismissed, setDismissed] = useState(false) + + useEffect(() => { + void window.hermesDesktop?.getRemoteDisplayReason?.().then(result => setReason(result)) + }, []) + + if (!reason || dismissed) { + return null + } + + return ( + + + + + {t.remoteDisplayBanner.message(reason)} + + setDismissed(true)} + size="icon-xs" + type="button" + variant="ghost" + > + + + + + ) +} diff --git a/apps/desktop/src/global.d.ts b/apps/desktop/src/global.d.ts index c615ad2d61a..26ab49fea51 100644 --- a/apps/desktop/src/global.d.ts +++ b/apps/desktop/src/global.d.ts @@ -102,6 +102,7 @@ declare global { cancelBootstrap: () => Promise<{ ok: boolean; cancelled: boolean }> onBootstrapEvent: (callback: (payload: DesktopBootstrapEvent) => void) => () => void getVersion: () => Promise + getRemoteDisplayReason?: () => Promise updates: { check: () => Promise apply: (opts?: DesktopUpdateApplyOptions) => Promise diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts index afe1e0117a2..704ed5f8e56 100644 --- a/apps/desktop/src/i18n/en.ts +++ b/apps/desktop/src/i18n/en.ts @@ -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', diff --git a/apps/desktop/src/i18n/ja.ts b/apps/desktop/src/i18n/ja.ts index 03fd9b4354b..a3109b94ffa 100644 --- a/apps/desktop/src/i18n/ja.ts +++ b/apps/desktop/src/i18n/ja.ts @@ -147,6 +147,12 @@ export const ja = defineLocale({ } }, + remoteDisplayBanner: { + message: reason => + `ソフトウェアレンダリングが有効です — リモートディスプレイを検出しました(${reason})。ちらつきを防ぐため GPU アクセラレーションは無効化されています。`, + dismiss: '閉じる' + }, + titlebar: { hideSidebar: 'サイドバーを非表示', showSidebar: 'サイドバーを表示', diff --git a/apps/desktop/src/i18n/types.ts b/apps/desktop/src/i18n/types.ts index da025767fff..7cb915b6ac3 100644 --- a/apps/desktop/src/i18n/types.ts +++ b/apps/desktop/src/i18n/types.ts @@ -159,6 +159,11 @@ export interface Translations { } } + remoteDisplayBanner: { + message: (reason: string) => string + dismiss: string + } + titlebar: { hideSidebar: string showSidebar: string diff --git a/apps/desktop/src/i18n/zh-hant.ts b/apps/desktop/src/i18n/zh-hant.ts index b60fe5d423d..23fc6027b42 100644 --- a/apps/desktop/src/i18n/zh-hant.ts +++ b/apps/desktop/src/i18n/zh-hant.ts @@ -142,6 +142,11 @@ export const zhHant = defineLocale({ } }, + remoteDisplayBanner: { + message: reason => `軟體繪圖已啟用 — 偵測到遠端顯示(${reason})。為防止畫面閃爍,已停用 GPU 加速。`, + dismiss: '關閉' + }, + titlebar: { hideSidebar: '隱藏側邊欄', showSidebar: '顯示側邊欄', diff --git a/apps/desktop/src/i18n/zh.ts b/apps/desktop/src/i18n/zh.ts index bc0b828b955..271ca9e4899 100644 --- a/apps/desktop/src/i18n/zh.ts +++ b/apps/desktop/src/i18n/zh.ts @@ -142,6 +142,11 @@ export const zh: Translations = { } }, + remoteDisplayBanner: { + message: reason => `软件渲染已启用 — 检测到远程显示(${reason})。为防止画面闪烁,已禁用 GPU 加速。`, + dismiss: '关闭' + }, + titlebar: { hideSidebar: '隐藏侧边栏', showSidebar: '显示侧边栏',
{t.remoteDisplayBanner.message(reason)}