fix(desktop): pass wayland ozone on native Wayland sessions
Native Linux Wayland stayed on XWayland because the relaunch only ran for WSLg. Append --ozone-platform=wayland when the user did not already choose a platform. An explicit x11 hint and desktop.electron_flags still win.
This commit is contained in:
@@ -1,9 +1,31 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
import { app } from 'electron'
|
||||
|
||||
import { readDesktopLaunchConfig } from './renderer-heap-flags'
|
||||
import { wslgLaunchArgs } from './wslg-launch'
|
||||
import { spawnWslgLaunch } from './wslg-launch-process'
|
||||
|
||||
const args = wslgLaunchArgs(process.argv.slice(1), process.env, process.platform)
|
||||
function configuredElectronFlags(env: NodeJS.ProcessEnv): string[] {
|
||||
const raw = env.HERMES_HOME
|
||||
|
||||
const home = raw
|
||||
? path.resolve(raw === '~' || raw.startsWith('~/') ? path.join(os.homedir(), raw.slice(1)) : raw)
|
||||
: env.HERMES_DESKTOP_USER_DATA_DIR
|
||||
? path.join(path.resolve(env.HERMES_DESKTOP_USER_DATA_DIR), 'hermes-home')
|
||||
: path.join(os.homedir(), '.hermes')
|
||||
|
||||
try {
|
||||
return readDesktopLaunchConfig(readFileSync(path.join(home, 'config.yaml'), 'utf8')).electronFlags
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const electronFlags = process.platform === 'linux' ? configuredElectronFlags(process.env) : []
|
||||
const args = wslgLaunchArgs(process.argv.slice(1), process.env, process.platform, undefined, electronFlags)
|
||||
|
||||
if (args) {
|
||||
// Keep the launcher alive until the child exits: npm's concurrently must not
|
||||
@@ -13,7 +35,7 @@ if (args) {
|
||||
const child = spawnWslgLaunch(args)
|
||||
|
||||
child.once('error', error => {
|
||||
console.error('[hermes] WSLg launch failed:', error)
|
||||
console.error('[hermes] Wayland ozone launch failed:', error)
|
||||
app.exit(1)
|
||||
})
|
||||
child.once('exit', code => app.exit(code ?? 1))
|
||||
|
||||
@@ -40,3 +40,45 @@ describe('WSLg launch arguments', () => {
|
||||
expect(wslgLaunchArgs([], { ...env, DISPLAY: 'localhost:10.0' }, 'linux')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
const nativeWayland = { XDG_SESSION_TYPE: 'wayland', WAYLAND_DISPLAY: 'wayland-0', DISPLAY: ':0' }
|
||||
|
||||
describe('native Wayland launch arguments', () => {
|
||||
it('appends wayland on a native Linux Wayland session when the user did not choose a platform', () => {
|
||||
expect(wslgLaunchArgs(['.'], nativeWayland, 'linux', false)).toEqual(['.', '--ozone-platform=wayland'])
|
||||
expect(wslgLaunchArgs([], { WAYLAND_DISPLAY: 'wayland-0', DISPLAY: ':0' }, 'linux', false)).toEqual([
|
||||
'--ozone-platform=wayland'
|
||||
])
|
||||
expect(wslgLaunchArgs([], { XDG_SESSION_TYPE: 'wayland', DISPLAY: ':0' }, 'linux', false)).toEqual([
|
||||
'--ozone-platform=wayland'
|
||||
])
|
||||
expect(wslgLaunchArgs(['--ozone-platform=wayland'], nativeWayland, 'linux', false)).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps an explicit x11 platform already on the command line', () => {
|
||||
expect(wslgLaunchArgs(['--ozone-platform=x11'], nativeWayland, 'linux', false)).toBeNull()
|
||||
expect(wslgLaunchArgs(['--ozone-platform', 'x11'], nativeWayland, 'linux', false)).toBeNull()
|
||||
})
|
||||
|
||||
it('lets an x11 hint win over the automatic wayland platform', () => {
|
||||
expect(wslgLaunchArgs([], { ...nativeWayland, ELECTRON_OZONE_PLATFORM_HINT: 'x11' }, 'linux', false)).toEqual([
|
||||
'--ozone-platform=x11'
|
||||
])
|
||||
expect(wslgLaunchArgs(['--ozone-platform-hint=x11'], nativeWayland, 'linux', false)).toEqual([
|
||||
'--ozone-platform-hint=x11',
|
||||
'--ozone-platform=x11'
|
||||
])
|
||||
})
|
||||
|
||||
it('lets desktop.electron_flags choose the ozone platform instead of forcing wayland', () => {
|
||||
expect(wslgLaunchArgs([], nativeWayland, 'linux', false, ['--ozone-platform=x11'])).toEqual([
|
||||
'--ozone-platform=x11'
|
||||
])
|
||||
expect(wslgLaunchArgs(['.'], nativeWayland, 'linux', false, ['--disable-gpu'])).toEqual([
|
||||
'.',
|
||||
'--ozone-platform=wayland'
|
||||
])
|
||||
expect(wslgLaunchArgs([], { XDG_SESSION_TYPE: 'x11', DISPLAY: ':0' }, 'linux', false)).toBeNull()
|
||||
expect(wslgLaunchArgs([], { ...nativeWayland, SSH_CONNECTION: 'remote' }, 'linux', false)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,21 +6,69 @@ export function wslgLaunchArgs(
|
||||
argv: readonly string[],
|
||||
env: NodeJS.ProcessEnv,
|
||||
platform: NodeJS.Platform,
|
||||
isWsl = isWslEnvironment(env, platform)
|
||||
isWsl = isWslEnvironment(env, platform),
|
||||
electronFlags: readonly string[] = []
|
||||
): string[] | null {
|
||||
const displayEnv = { ...env, HERMES_DESKTOP_DISABLE_GPU: undefined }
|
||||
|
||||
if (platform !== 'linux' || !isWsl || !env.WAYLAND_DISPLAY || detectRemoteDisplay({ env: displayEnv, platform })) {
|
||||
// Remote/forwarded displays stay on the software-rendering path. A local
|
||||
// Wayland session — native Linux or WSLg — otherwise defaults to XWayland
|
||||
// unless the platform is on argv before application JavaScript.
|
||||
if (platform !== 'linux' || detectRemoteDisplay({ env: displayEnv, platform })) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (argv.some(arg => arg === '--ozone-platform' || arg.startsWith('--ozone-platform='))) {
|
||||
const nativeWayland = env.XDG_SESSION_TYPE === 'wayland' || Boolean(env.WAYLAND_DISPLAY)
|
||||
const wslgWayland = Boolean(isWsl && env.WAYLAND_DISPLAY)
|
||||
|
||||
if (!nativeWayland && !wslgWayland) {
|
||||
return null
|
||||
}
|
||||
|
||||
const hintArg = argv.findLast(arg => arg.startsWith('--ozone-platform-hint='))
|
||||
const hint = hintArg?.split('=')[1] ?? env.ELECTRON_OZONE_PLATFORM_HINT
|
||||
if (hasOzonePlatform(argv)) {
|
||||
return null
|
||||
}
|
||||
|
||||
// desktop.electron_flags are not always on argv yet (.desktop entry, source
|
||||
// launches). An explicit platform there wins over the automatic default, and
|
||||
// must be on the relaunch command line or appendSwitch applies it too late.
|
||||
const flagged = explicitOzonePlatform(electronFlags)
|
||||
|
||||
if (flagged !== null) {
|
||||
return flagged ? [...argv, `--ozone-platform=${flagged}`] : null
|
||||
}
|
||||
|
||||
const hintArg = findOzoneHint(argv) ?? findOzoneHint(electronFlags)
|
||||
const hint = hintArg ?? env.ELECTRON_OZONE_PLATFORM_HINT
|
||||
const backend = hint === 'x11' ? 'x11' : 'wayland'
|
||||
|
||||
return [...argv, `--ozone-platform=${backend}`]
|
||||
}
|
||||
|
||||
function hasOzonePlatform(args: readonly string[]): boolean {
|
||||
return args.some(arg => arg === '--ozone-platform' || arg.startsWith('--ozone-platform='))
|
||||
}
|
||||
|
||||
function explicitOzonePlatform(args: readonly string[]): string | null {
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
const arg = args[i] ?? ''
|
||||
|
||||
if (arg === '--ozone-platform') {
|
||||
const next = args[i + 1]
|
||||
|
||||
return next && !next.startsWith('-') ? next : ''
|
||||
}
|
||||
|
||||
if (arg.startsWith('--ozone-platform=')) {
|
||||
return arg.slice('--ozone-platform='.length)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function findOzoneHint(args: readonly string[]): string | undefined {
|
||||
const hintArg = args.findLast(arg => arg.startsWith('--ozone-platform-hint='))
|
||||
|
||||
return hintArg?.slice('--ozone-platform-hint='.length)
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ Talk to Hermes and hear it back, the same [voice mode](./features/voice-mode.md)
|
||||
|
||||
#### Linux / Wayland
|
||||
|
||||
Electron 20+ already runs as a native Wayland client on a Wayland session. Drag, click-through, and resize work on that path.
|
||||
On a local Wayland session (`XDG_SESSION_TYPE=wayland`, or `WAYLAND_DISPLAY` set) Hermes launches with `--ozone-platform=wayland` so Electron does not fall back to XWayland. The platform has to be on the process command line before application JavaScript loads. An explicit `--ozone-platform`, `desktop.ozone_platform_hint: x11`, or an ozone platform in `desktop.electron_flags` still wins. Drag, click-through, and resize work on the native Wayland path.
|
||||
|
||||
On **Hyprland** (including Omarchy) the HUD is floated and pinned through the compositor's IPC after it maps — otherwise Hyprland tiles it like any other window, `always-on-top` is ignored, and compositor drag does nothing. No extra window rule is required.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user