From 0b0ffe06d4faf11abe89d3becf84a9a9c60237df Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Sat, 15 Nov 2025 01:21:18 -0800 Subject: [PATCH] Desktop: Fixes #13561: Upgrade to Electron 39 (#13567) --- Joplin_install_and_update.sh | 52 ++++++++++++++++++- packages/app-desktop/ElectronAppWrapper.ts | 30 +++++++++++ packages/app-desktop/InteropServiceHelper.ts | 11 ++-- packages/app-desktop/package.json | 5 +- packages/app-desktop/tools/electronRebuild.js | 2 +- packages/lib/utils/processStartFlags.ts | 7 +++ yarn.lock | 10 ++-- 7 files changed, 105 insertions(+), 12 deletions(-) diff --git a/Joplin_install_and_update.sh b/Joplin_install_and_update.sh index 0cd76d878f..453a5d5112 100755 --- a/Joplin_install_and_update.sh +++ b/Joplin_install_and_update.sh @@ -67,6 +67,45 @@ showHelp() { fi } +# Accepts two versions in symver (a.b.c). +# Echos -1 if the first version is less than the second, +# 0 if they're equal, +# 1 if the first version is greater than second. +compareVersions() { + V_MAJOR1=$(echo "$1"|cut -d. -f1) + V_MAJOR2=$(echo "$2"|cut -d. -f1) + + if [[ $V_MAJOR1 -lt $V_MAJOR2 ]] ; then + echo -1 + return + elif [[ $V_MAJOR1 -gt $V_MAJOR2 ]] ; then + echo 1 + return + fi + + V_MINOR1=$(echo "$1"|cut -d. -f2) + V_MINOR2=$(echo "$2"|cut -d. -f2) + + if [[ $V_MINOR1 -lt $V_MINOR2 ]] ; then + echo -1 + return + elif [[ $V_MINOR1 -gt $V_MINOR2 ]] ; then + echo 1 + return + fi + + V_PATCH1=$(echo "$1"|cut -d. -f3) + V_PATCH2=$(echo "$2"|cut -d. -f3) + + if [[ $V_PATCH1 -lt $V_PATCH2 ]] ; then + echo -1 + elif [[ $V_PATCH1 -gt $V_PATCH2 ]] ; then + echo 1 + else + echo 0 + fi +} + #----------------------------------------------------- # Setup Download Helper: DL #----------------------------------------------------- @@ -258,6 +297,15 @@ fi if [[ $DESKTOP =~ .*gnome.*|.*kde.*|.*xfce.*|.*mate.*|.*lxqt.*|.*unity.*|.*x-cinnamon.*|.*deepin.*|.*pantheon.*|.*lxde.*|.*i3.*|.*sway.* ]] || [[ `command -v update-desktop-database` ]]; then DATA_HOME=${XDG_DATA_HOME:-~/.local/share} DESKTOP_FILE_LOCATION="$DATA_HOME/applications" + + # Only later versions of Joplin default to Wayland + IS_WAYLAND_BY_DEFAULT=$(compareVersions "$RELEASE_VERSION" "3.5.6") + # Joplin has a different startup WM class on Wayland and X11: + STARTUP_WM_CLASS=Joplin + if [[ $XDG_SESSION_TYPE != "x11" && $IS_WAYLAND_BY_DEFAULT == "1" ]]; then + STARTUP_WM_CLASS=@joplin/app-desktop + fi + # Only delete the desktop file if it will be replaced rm -f "$DESKTOP_FILE_LOCATION/appimagekit-joplin.desktop" @@ -272,7 +320,9 @@ Name=Joplin Comment=Joplin for Desktop Exec=env APPIMAGELAUNCHER_DISABLE=TRUE "${INSTALL_DIR}/Joplin.AppImage" ${SANDBOXPARAM} %u Icon=joplin -StartupWMClass=Joplin +# This will be different between Wayland and X11. On Wayland, the startup +# WM class is "@joplin/app-desktop". On X11, it's "Joplin". +StartupWMClass=${STARTUP_WM_CLASS} Type=Application Categories=Office; MimeType=x-scheme-handler/joplin; diff --git a/packages/app-desktop/ElectronAppWrapper.ts b/packages/app-desktop/ElectronAppWrapper.ts index 540ab3c4f1..477f4328f7 100644 --- a/packages/app-desktop/ElectronAppWrapper.ts +++ b/packages/app-desktop/ElectronAppWrapper.ts @@ -23,6 +23,7 @@ import { defaultWindowId } from '@joplin/lib/reducer'; import { msleep, Second } from '@joplin/utils/time'; import determineBaseAppDirs from '@joplin/lib/determineBaseAppDirs'; import getAppName from '@joplin/lib/getAppName'; +import { execCommand } from '@joplin/utils'; interface RendererProcessQuitReply { canClose: boolean; @@ -810,6 +811,33 @@ export default class ElectronAppWrapper { return this.customProtocolHandler_; } + private async fixLinuxAccessibility_() { + if (this.electronApp().accessibilitySupportEnabled) return; + + const isOrcaRunning = async () => { + if (!shim.isLinux()) return false; + try { + const matchingProcesses = await execCommand(['ps', '--no-headers', '-C', 'orca'], { quiet: true }); + return matchingProcesses.trim().length > 0; + } catch (error) { + if (error.stderr || error.exitCode !== 1) { + // eslint-disable-next-line no-console -- The main logger is not available at this point. + console.error('Failed to check for and enable accessibility support:', error.stderr); + } + + return false; + } + }; + + // Work around https://issues.chromium.org/issues/431257156 by force-enabling accessibility + // when Orca (a screen reader) is running: + if (await isOrcaRunning()) { + // eslint-disable-next-line no-console -- The main logger is not available at this point. + console.log('Linux accessibility: Enabling full accessibility support.'); + this.electronApp().setAccessibilitySupportEnabled(true); + } + } + public async start() { // Since we are doing other async things before creating the window, we might miss // the "ready" event. So we use the function below to make sure that the app is ready. @@ -818,6 +846,8 @@ export default class ElectronAppWrapper { const alreadyRunning = await this.ensureSingleInstance(); if (alreadyRunning) return; + await this.fixLinuxAccessibility_(); + this.customProtocolHandler_ = handleCustomProtocols(); this.createWindow(); diff --git a/packages/app-desktop/InteropServiceHelper.ts b/packages/app-desktop/InteropServiceHelper.ts index 2f558d2ef0..b1223d4af4 100644 --- a/packages/app-desktop/InteropServiceHelper.ts +++ b/packages/app-desktop/InteropServiceHelper.ts @@ -11,7 +11,7 @@ import Setting from '@joplin/lib/models/Setting'; import Note from '@joplin/lib/models/Note'; const { friendlySafeFilename } = require('@joplin/lib/path-utils'); import time from '@joplin/lib/time'; -import { BrowserWindow } from 'electron'; +import { BrowserWindow, BrowserWindowConstructorOptions } from 'electron'; const md5 = require('md5'); const url = require('url'); @@ -62,8 +62,10 @@ export default class InteropServiceHelper { htmlFile = await this.exportNoteToHtmlFile(noteId, exportOptions); - const windowOptions = { - show: false, + const windowOptions: BrowserWindowConstructorOptions = { + // Work around a printing issue: As of Electron 39, if the window is initially hidden, printing crashes the app. + // This only seems to be necessary on Linux. + show: shim.isLinux(), }; win = bridge().newBrowserWindow(windowOptions); @@ -120,6 +122,9 @@ export default class InteropServiceHelper { // // 2025-05-03: Windows and MacOS also need the window.print() workaround. // See https://github.com/electron/electron/pull/46937. + // + // 2025-10-30: window.print() now causes a crash on Linux -- switch back to the + // other method. const applyWorkaround = true; if (applyWorkaround) { diff --git a/packages/app-desktop/package.json b/packages/app-desktop/package.json index df0f734e64..8c19a5b093 100644 --- a/packages/app-desktop/package.json +++ b/packages/app-desktop/package.json @@ -119,7 +119,8 @@ "category": "Office", "desktop": { "Icon": "joplin", - "MimeType": "x-scheme-handler/joplin;" + "MimeType": "x-scheme-handler/joplin;", + "StartupWMClass": "@joplin/app-desktop" }, "target": [ "AppImage", @@ -161,7 +162,7 @@ "compare-versions": "6.1.1", "countable": "3.0.1", "debounce": "1.2.1", - "electron": "37.7.0", + "electron": "39.0.0", "electron-builder": "24.13.3", "electron-updater": "6.6.2", "electron-window-state": "5.0.3", diff --git a/packages/app-desktop/tools/electronRebuild.js b/packages/app-desktop/tools/electronRebuild.js index 77ca937eda..c2ee7afb19 100644 --- a/packages/app-desktop/tools/electronRebuild.js +++ b/packages/app-desktop/tools/electronRebuild.js @@ -25,7 +25,7 @@ async function main() { // wrong one. However it means it will have to be manually upgraded for each // new Electron release. Some ABI map there: // https://github.com/electron/node-abi/tree/master/test - const forceAbiArgs = '--force-abi 138'; + const forceAbiArgs = '--force-abi 142'; if (isWindows()) { // Cannot run this in parallel, or the 64-bit version might end up diff --git a/packages/lib/utils/processStartFlags.ts b/packages/lib/utils/processStartFlags.ts index 3b6830c6f2..58244fcdf6 100644 --- a/packages/lib/utils/processStartFlags.ts +++ b/packages/lib/utils/processStartFlags.ts @@ -192,6 +192,13 @@ const processStartFlags = async (argv: string[], setDefaults = true) => { continue; } + if (arg === '--force-renderer-accessibility') { + // Electron-specific flag - ignore it + // Allows users to force-enable accessibility support + argv.splice(0, 1); + continue; + } + if (arg === '--updated') { // Electron-specific flag - ignore it // Allows to restart with the updated application after the update option is selected by the user diff --git a/yarn.lock b/yarn.lock index c575c69a37..de5188d2d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9812,7 +9812,7 @@ __metadata: compare-versions: "npm:6.1.1" countable: "npm:3.0.1" debounce: "npm:1.2.1" - electron: "npm:37.7.0" + electron: "npm:39.0.0" electron-builder: "npm:24.13.3" electron-updater: "npm:6.6.2" electron-window-state: "npm:5.0.3" @@ -25439,16 +25439,16 @@ __metadata: languageName: node linkType: hard -"electron@npm:37.7.0": - version: 37.7.0 - resolution: "electron@npm:37.7.0" +"electron@npm:39.0.0": + version: 39.0.0 + resolution: "electron@npm:39.0.0" dependencies: "@electron/get": "npm:^2.0.0" "@types/node": "npm:^22.7.7" extract-zip: "npm:^2.0.1" bin: electron: cli.js - checksum: 10/90d65ac676770662442f7f473791ed7488c2186f42d516e1cebe1deac64a8259a67cef1126787a729309c25801ee6f53a312127e71696d86a51c056d3b6a6646 + checksum: 10/c2bb9f84bd06d12a90c650b2741ee618426e1ce20be18ff3708bf5f373e942e75e6a8cade94c15d96a06077f7f1fae6709b95f53784456d1db09f493928b47f3 languageName: node linkType: hard