From 9b363a33db8bdca2f40b15498d9f0dd2d0398e2f Mon Sep 17 00:00:00 2001 From: itsflownium <157689911+itsflownium@users.noreply.github.com> Date: Sat, 5 Sep 2026 19:37:13 +1000 Subject: [PATCH] fix(desktop): explain unavailable Windows ARM64 window context --- apps/desktop/electron/window-below.test.ts | 34 +++++++++++++++++++++- apps/desktop/electron/window-below.ts | 19 +++++++++++- website/docs/user-guide/desktop.md | 13 +++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/apps/desktop/electron/window-below.test.ts b/apps/desktop/electron/window-below.test.ts index c676c84ac7..83c5b95780 100644 --- a/apps/desktop/electron/window-below.test.ts +++ b/apps/desktop/electron/window-below.test.ts @@ -1,6 +1,12 @@ import { describe, expect, it } from 'vitest' -import { type EnumeratedWindow, enumerationFailureNote, pickWindowBelow, resolveOutsideAsar } from './window-below' +import { + type EnumeratedWindow, + enumerationFailureNote, + getWindowsFailureReason, + pickWindowBelow, + resolveOutsideAsar +} from './window-below' const win = (pid: number, x = 0, y = 0, width = 800, height = 600, app = `app-${pid}`): EnumeratedWindow => ({ app, @@ -146,6 +152,32 @@ describe('enumerationFailureNote', () => { }) }) +describe('getWindowsFailureReason', () => { + it('explains the native binding gap on Windows ARM64 without discarding the failure', () => { + for (const detail of [ + 'module missing', + 'binding failed to load', + 'the window enumerator returned no window list' + ]) { + const reason = getWindowsFailureReason(detail, 'win32', 'arm64') + + expect(reason).toContain(detail) + expect(reason).toMatch(/win32-arm64/) + expect(reason).toMatch(/native binding/) + expect(reason).toMatch(/x64/) + expect(enumerationFailureNote('win32', {}, reason)).toContain(reason) + + for (const [platform, arch] of [ + ['win32', 'x64'], + ['darwin', 'arm64'], + ['linux', 'arm64'] + ]) { + expect(getWindowsFailureReason(detail, platform, arch)).toBe(detail) + } + } + }) +}) + describe('resolveOutsideAsar', () => { // The helper binary get-windows execs cannot run from inside the archive // (execFile on a path through app.asar fails ENOTDIR), so the import must diff --git a/apps/desktop/electron/window-below.ts b/apps/desktop/electron/window-below.ts index 5f0477d565..93bd59801a 100644 --- a/apps/desktop/electron/window-below.ts +++ b/apps/desktop/electron/window-below.ts @@ -145,6 +145,19 @@ export interface EnumerationFailure { export const enumerationFailed = (result: EnumerationFailure | T): result is EnumerationFailure => typeof result === 'object' && result !== null && 'reason' in result +// Keep the native provider's failure detail shared by the tool and HUD log. +export function getWindowsFailureReason(detail: string, platform: string, arch: string): string { + if (platform === 'win32' && arch === 'arm64') { + return ( + `${detail}. Window context requires a working win32-arm64 native binding; ` + + 'get-windows 9.3.0 does not ship one. Use the x64 desktop build under Windows emulation, ' + + 'or a build with a matching native binding. This affects both read_window_below and HUD window context.' + ) + } + + return detail +} + const describeError = (error: unknown): string => error instanceof Error ? error.message : String(error ?? 'unknown error') @@ -284,7 +297,11 @@ export async function enumerateWindowsFrontToBack( selfPid: number, titlesAvailable: boolean ): Promise { - return (await readHyprlandWindows(selfPid)) ?? (await enumerateViaGetWindows(titlesAvailable)) + const result = (await readHyprlandWindows(selfPid)) ?? (await enumerateViaGetWindows(titlesAvailable)) + + return enumerationFailed(result) + ? { reason: getWindowsFailureReason(result.reason, process.platform, process.arch) } + : result } export async function readWindowBelow( diff --git a/website/docs/user-guide/desktop.md b/website/docs/user-guide/desktop.md index 2ed42b2bf9..678294f052 100644 --- a/website/docs/user-guide/desktop.md +++ b/website/docs/user-guide/desktop.md @@ -539,6 +539,19 @@ The remote gateway host is configured per [profile](./profiles.md), so each prof ### Troubleshooting +### Window context unavailable on Windows ARM64 + +The ARM64 desktop build's `get-windows` 9.3.0 dependency does not ship a +`win32-arm64` native binding. Without a working binding, `read_window_below` +and HUD window context cannot enumerate other apps' windows. The error and +HUD log include this limitation alongside the underlying failure reason. + +Use the x64 desktop build under Windows emulation, or a custom build with a +matching native binding. Changing the agent backend or granting macOS screen +permissions cannot fix a missing Windows binding; enumeration runs on the +computer hosting the desktop app. This diagnostic does not add native ARM64 +window enumeration support. + - **Sign-in fails with 401 / "Invalid credentials"** — the username or password doesn't match the backend's `HERMES_DASHBOARD_BASIC_AUTH_USERNAME` / `HERMES_DASHBOARD_BASIC_AUTH_PASSWORD`. The backend returns the same generic error for an unknown user and a wrong password (no enumeration oracle), so double-check both. Confirm the gate is on with `curl -s http://:9119/api/status | jq '.auth_required, .auth_providers'` — it should report `true` and include `"basic"`. - **No "Sign in" button — it asks for a session token instead** — the backend's username/password provider isn't active. `/api/status` won't list `"basic"` in `auth_providers`. Make sure both the username and a password (or password hash) are set in `~/.hermes/.env` and that the dashboard process actually loaded them. - **Signed out on every restart** — set `HERMES_DASHBOARD_BASIC_AUTH_SECRET` to a stable value. Without it the token-signing key is regenerated per boot, invalidating all sessions.