From 150c45c31a730f501e4a8afb0aa0785a2e40b824 Mon Sep 17 00:00:00 2001 From: Myx Date: Fri, 13 Mar 2026 20:13:05 +0100 Subject: [PATCH] Use wayland if possible on linux --- dev.sh | 2 +- electron/app/flags.ts | 6 +- electron/ipc/system.ts | 55 +++++++++++++++ electron/preload.ts | 18 +++++ package.json | 6 +- .../debug-console-environment.service.ts | 34 +++++++-- tools/launch-electron.js | 70 +++++++++++++++++++ 7 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 tools/launch-electron.js diff --git a/dev.sh b/dev.sh index c600e9e..7eef7fa 100755 --- a/dev.sh +++ b/dev.sh @@ -33,4 +33,4 @@ fi exec npx concurrently --kill-others \ "cd server && npm run dev" \ "$NG_SERVE" \ - "wait-on $WAIT_URL $HEALTH_URL && cross-env NODE_ENV=development SSL=$SSL electron . --no-sandbox --disable-dev-shm-usage" + "wait-on $WAIT_URL $HEALTH_URL && cross-env NODE_ENV=development SSL=$SSL node tools/launch-electron.js . --no-sandbox --disable-dev-shm-usage" diff --git a/electron/app/flags.ts b/electron/app/flags.ts index 604d54f..86fe704 100644 --- a/electron/app/flags.ts +++ b/electron/app/flags.ts @@ -45,9 +45,9 @@ function linuxSpecificFlags(): void { app.commandLine.appendSwitch('no-sandbox'); app.commandLine.appendSwitch('disable-dev-shm-usage'); - // Auto-detect Wayland vs X11 so the xdg-desktop-portal system picker - // works for screen capture on Wayland compositors - app.commandLine.appendSwitch('ozone-platform-hint', 'auto'); + // Chromium chooses the Linux Ozone platform before Electron runs this file. + // The launch scripts pass `--ozone-platform=wayland` up front for Wayland + // sessions so the browser process selects the correct backend early enough. } function networkFlags(): void { diff --git a/electron/ipc/system.ts b/electron/ipc/system.ts index 84a7333..2171c4e 100644 --- a/electron/ipc/system.ts +++ b/electron/ipc/system.ts @@ -83,6 +83,57 @@ interface ClipboardFilePayload { path?: string; } +function resolveLinuxDisplayServer(): string { + if (process.platform !== 'linux') { + return 'N/A'; + } + + const ozonePlatform = app.commandLine.getSwitchValue('ozone-platform') + .trim() + .toLowerCase(); + + if (ozonePlatform === 'wayland') { + return 'Wayland'; + } + + if (ozonePlatform === 'x11') { + return 'X11'; + } + + const ozonePlatformHint = app.commandLine.getSwitchValue('ozone-platform-hint') + .trim() + .toLowerCase(); + + if (ozonePlatformHint === 'wayland') { + return 'Wayland'; + } + + if (ozonePlatformHint === 'x11') { + return 'X11'; + } + + const sessionType = String(process.env['XDG_SESSION_TYPE'] || '').trim() + .toLowerCase(); + + if (sessionType === 'wayland') { + return 'Wayland'; + } + + if (sessionType === 'x11') { + return 'X11'; + } + + if (String(process.env['WAYLAND_DISPLAY'] || '').trim().length > 0) { + return 'Wayland'; + } + + if (String(process.env['DISPLAY'] || '').trim().length > 0) { + return 'X11'; + } + + return 'Unknown (Linux)'; +} + function isSupportedClipboardFileFormat(format: string): boolean { return FILE_CLIPBOARD_FORMATS.some( (supportedFormat) => supportedFormat.toLowerCase() === format.toLowerCase() @@ -194,6 +245,10 @@ async function readClipboardFiles(): Promise { } export function setupSystemHandlers(): void { + ipcMain.on('get-linux-display-server', (event) => { + event.returnValue = resolveLinuxDisplayServer(); + }); + ipcMain.handle('open-external', async (_event, url: string) => { if (typeof url === 'string' && (url.startsWith('http://') || url.startsWith('https://'))) { await shell.openExternal(url); diff --git a/electron/preload.ts b/electron/preload.ts index 07bcef9..4c2d26f 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -83,7 +83,24 @@ export interface DesktopUpdateState { targetVersion: string | null; } +function readLinuxDisplayServer(): string { + if (process.platform !== 'linux') { + return 'N/A'; + } + + try { + const displayServer = ipcRenderer.sendSync('get-linux-display-server'); + + return typeof displayServer === 'string' && displayServer.trim().length > 0 + ? displayServer + : 'Unknown (Linux)'; + } catch { + return 'Unknown (Linux)'; + } +} + export interface ElectronAPI { + linuxDisplayServer: string; minimizeWindow: () => void; maximizeWindow: () => void; closeWindow: () => void; @@ -139,6 +156,7 @@ export interface ElectronAPI { } const electronAPI: ElectronAPI = { + linuxDisplayServer: readLinuxDisplayServer(), minimizeWindow: () => ipcRenderer.send('window-minimize'), maximizeWindow: () => ipcRenderer.send('window-maximize'), closeWindow: () => ipcRenderer.send('window-close'), diff --git a/package.json b/package.json index 8d4e2dc..b5a7481 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,10 @@ "server:build": "cd server && npm run build", "server:start": "cd server && npm start", "server:dev": "cd server && npm run dev", - "electron": "ng build && npm run build:electron && electron . --no-sandbox --disable-dev-shm-usage", - "electron:dev": "concurrently \"ng serve\" \"wait-on http://localhost:4200 && npm run build:electron && cross-env NODE_ENV=development electron . --no-sandbox --disable-dev-shm-usage\"", + "electron": "ng build && npm run build:electron && node tools/launch-electron.js . --no-sandbox --disable-dev-shm-usage", + "electron:dev": "concurrently \"ng serve\" \"wait-on http://localhost:4200 && npm run build:electron && cross-env NODE_ENV=development node tools/launch-electron.js . --no-sandbox --disable-dev-shm-usage\"", "electron:full": "./dev.sh", - "electron:full:build": "npm run build:all && concurrently --kill-others \"cd server && npm start\" \"cross-env NODE_ENV=production electron . --no-sandbox --disable-dev-shm-usage\"", + "electron:full:build": "npm run build:all && concurrently --kill-others \"cd server && npm start\" \"cross-env NODE_ENV=production node tools/launch-electron.js . --no-sandbox --disable-dev-shm-usage\"", "migration:generate": "typeorm migration:generate electron/migrations/Auto -d dist/electron/data-source.js", "migration:create": "typeorm migration:create electron/migrations/New", "migration:run": "typeorm migration:run -d dist/electron/data-source.js", diff --git a/src/app/shared/components/debug-console/services/debug-console-environment.service.ts b/src/app/shared/components/debug-console/services/debug-console-environment.service.ts index 6997ea8..f5240db 100644 --- a/src/app/shared/components/debug-console/services/debug-console-environment.service.ts +++ b/src/app/shared/components/debug-console/services/debug-console-environment.service.ts @@ -15,6 +15,10 @@ export interface DebugExportEnvironment { userId: string; } +interface DebugConsoleElectronApi { + linuxDisplayServer?: string; +} + @Injectable({ providedIn: 'root' }) export class DebugConsoleEnvironmentService { private readonly store = inject(Store); @@ -113,19 +117,24 @@ export class DebugConsoleEnvironmentService { if (!navigator.userAgent.includes('Linux')) return 'N/A'; + const electronDisplayServer = this.readElectronDisplayServer(); + + if (electronDisplayServer) + return electronDisplayServer; + try { const ua = navigator.userAgent.toLowerCase(); if (ua.includes('wayland')) return 'Wayland'; - if (ua.includes('x11')) - return 'X11'; - const isOzone = ua.includes('ozone'); if (isOzone) return 'Ozone (Wayland likely)'; + + if (ua.includes('x11')) + return 'X11'; } catch { // Ignore } @@ -133,11 +142,22 @@ export class DebugConsoleEnvironmentService { return this.detectDisplayServerFromEnv(); } + private readElectronDisplayServer(): string | null { + try { + const displayServer = this.getElectronApi()?.linuxDisplayServer; + + return typeof displayServer === 'string' && displayServer.trim().length > 0 + ? displayServer + : null; + } catch { + return null; + } + } + private detectDisplayServerFromEnv(): string { try { // Electron may expose env vars - const api = this.getElectronApi() as - Record | null; + const api = this.getElectronApi(); if (!api) return 'Unknown (Linux)'; @@ -201,10 +221,10 @@ export class DebugConsoleEnvironmentService { } } - private getElectronApi(): Record | null { + private getElectronApi(): DebugConsoleElectronApi | null { try { const win = window as Window & - { electronAPI?: Record }; + { electronAPI?: DebugConsoleElectronApi }; return win.electronAPI ?? null; } catch { diff --git a/tools/launch-electron.js b/tools/launch-electron.js new file mode 100644 index 0000000..869e2e1 --- /dev/null +++ b/tools/launch-electron.js @@ -0,0 +1,70 @@ +const { spawn } = require('child_process'); + +function isWaylandSession(env) { + const sessionType = String(env.XDG_SESSION_TYPE || '').trim().toLowerCase(); + + if (sessionType === 'wayland') { + return true; + } + + return String(env.WAYLAND_DISPLAY || '').trim().length > 0; +} + +function hasSwitch(args, switchName) { + const normalizedSwitch = `--${switchName}`; + + return args.some((arg) => arg === normalizedSwitch || arg.startsWith(`${normalizedSwitch}=`)); +} + +function resolveElectronBinary() { + const electronModule = require('electron'); + + if (typeof electronModule === 'string') { + return electronModule; + } + + if (electronModule && typeof electronModule.default === 'string') { + return electronModule.default; + } + + throw new Error('Could not resolve the Electron executable.'); +} + +function buildElectronArgs(argv) { + const args = [...argv]; + + if ( + process.platform === 'linux' + && isWaylandSession(process.env) + && !hasSwitch(args, 'ozone-platform') + ) { + args.push('--ozone-platform=wayland'); + } + + return args; +} + +function main() { + const electronBinary = resolveElectronBinary(); + const args = buildElectronArgs(process.argv.slice(2)); + const child = spawn(electronBinary, args, { + env: process.env, + stdio: 'inherit' + }); + + child.on('error', (error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + }); + + child.on('exit', (code, signal) => { + if (signal) { + process.kill(process.pid, signal); + return; + } + + process.exit(code ?? 0); + }); +} + +main();