diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index ce41cca7b7..3a05cbfd37 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1579,6 +1579,8 @@ "enable-shadows": "Shadows", "enable-backdrop-effects": "Background effects for menus, popups and panels", "enable-smooth-scroll": "Smooth scrolling", + "enable-hardware-acceleration": "Hardware acceleration (GPU)", + "enable-hardware-acceleration-description": "Disable this if the app shows a blank window or has display glitches caused by your GPU drivers. Requires an app restart.", "app-restart-required": "Requires app restart" }, "zoom_factor": { diff --git a/apps/client/src/widgets/type_widgets/options/appearance.tsx b/apps/client/src/widgets/type_widgets/options/appearance.tsx index 972f2da637..b93026dd99 100644 --- a/apps/client/src/widgets/type_widgets/options/appearance.tsx +++ b/apps/client/src/widgets/type_widgets/options/appearance.tsx @@ -658,6 +658,8 @@ function Performance() { {isElectron() && } + {isElectron() && } + ; } @@ -673,6 +675,18 @@ function SmoothScrollEnabledOption() { />; } +function HardwareAccelerationOption() { + const [ hardwareAccelerationEnabled, setHardwareAccelerationEnabled ] = useTriliumOptionBool("hardwareAccelerationEnabled"); + + return ; +} + function MaxContentWidth() { const [maxContentWidth, setMaxContentWidth] = useTriliumOption("maxContentWidth"); const [centerContent, setCenterContent] = useTriliumOptionBool("centerContent"); diff --git a/apps/desktop/src/main.spec.ts b/apps/desktop/src/main.spec.ts index 3be7ce5b30..5cab3134e0 100644 --- a/apps/desktop/src/main.spec.ts +++ b/apps/desktop/src/main.spec.ts @@ -53,6 +53,8 @@ const h = vi.hoisted(() => ({ isPrimaryInstance: true as boolean, allWindows: [] as unknown[], smoothScroll: "true" as string | null, + hardwareAcceleration: "true" as string | null, + disableHardwareAcceleration: vi.fn(), // When true, reading an option throws (simulates first run before the schema exists). dbUninitialized: false as boolean, isDbInitialized: true as boolean, @@ -91,6 +93,7 @@ vi.mock("electron", () => { setName: (...a: unknown[]) => h.setName(...a), setUserTasks: (...a: unknown[]) => h.setUserTasks(...a), commandLine: { appendSwitch: (...a: unknown[]) => h.appendSwitch(...a) }, + disableHardwareAcceleration: (...a: unknown[]) => h.disableHardwareAcceleration(...a), on: (event: string, cb: Handler) => h.appOn.set(event, cb), quit: (...a: unknown[]) => h.quit(...a), exit: (...a: unknown[]) => h.exit(...a), @@ -183,6 +186,7 @@ vi.mock("@triliumnext/server/src/sql_provider.js", () => ({ if (h.dbUninitialized) throw new Error("no such table: options"); const values: Record = { smoothScrollEnabled: h.smoothScroll, + hardwareAccelerationEnabled: h.hardwareAcceleration, locale: h.locale, formattingLocale: h.formattingLocale }; @@ -264,6 +268,8 @@ function resetState() { h.isPrimaryInstance = true; h.allWindows = []; h.smoothScroll = "true"; + h.hardwareAcceleration = "true"; + h.disableHardwareAcceleration.mockClear(); h.dbUninitialized = false; h.isDbInitialized = true; h.securitySettings = {}; @@ -406,6 +412,28 @@ describe("main() bootstrap", () => { expect(switches).not.toContain("disable-smooth-scrolling"); }); + // #10572: hardware acceleration is disabled before `ready` from the shared provider + // (same reason as smooth-scroll — core options aren't wired up yet). + it("disables hardware acceleration when the option is off", async () => { + setPlatform("darwin"); + h.hardwareAcceleration = "false"; + const { main } = await importMain(); + await main(); + expect(h.disableHardwareAcceleration).toHaveBeenCalled(); + }); + + it("keeps hardware acceleration when the option is on, absent, or the DB has no schema yet", async () => { + for (const scenario of [{ value: "true" as string | null }, { value: null }, { value: "false" as string | null, uninitialized: true }]) { + resetState(); + setPlatform("darwin"); + h.hardwareAcceleration = scenario.value; + if (scenario.uninitialized) h.dbUninitialized = true; + const { main } = await importMain(); + await main(); + expect(h.disableHardwareAcceleration).not.toHaveBeenCalled(); + } + }); + // Regression for #10559 (sibling of the smooth-scroll bug): the --lang switch is set // before core init from the same provider, so it must reflect the configured locale // instead of always falling back to "en". diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index e42ded163e..d39d83bc47 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -191,6 +191,12 @@ export async function main() { if (readDbOption(dbProvider, "smoothScrollEnabled") === "false") { app.commandLine.appendSwitch("disable-smooth-scrolling"); } + // Lets users work around GPU driver incompatibilities that render a blank window + // (see #10572) without having to pass --disable-gpu on the command line. Like the + // switch above, this must run before `ready`. + if (readDbOption(dbProvider, "hardwareAccelerationEnabled") === "false") { + app.disableHardwareAcceleration(); + } // The IPC provider just registers an `ipcMain.on` listener; no TCP socket // or session parser needed, so we can init it here (before startTriliumServer). diff --git a/packages/commons/src/lib/options_interface.ts b/packages/commons/src/lib/options_interface.ts index a77893b40c..1829c9ee00 100644 --- a/packages/commons/src/lib/options_interface.ts +++ b/packages/commons/src/lib/options_interface.ts @@ -138,6 +138,8 @@ export interface OptionDefinitions extends KeyboardShortcutsOptions([ "motionEnabled", "shadowsEnabled", "smoothScrollEnabled", + "hardwareAccelerationEnabled", "backdropEffectsEnabled", "maxContentWidth", "centerContent", diff --git a/packages/trilium-core/src/services/options_init.ts b/packages/trilium-core/src/services/options_init.ts index 3bf9bbfafe..f446cd0ba1 100644 --- a/packages/trilium-core/src/services/options_init.ts +++ b/packages/trilium-core/src/services/options_init.ts @@ -244,6 +244,7 @@ const defaultOptions: DefaultOption[] = [ { name: "shadowsEnabled", value: "true", isSynced: false }, { name: "backdropEffectsEnabled", value: "true", isSynced: false }, { name: "smoothScrollEnabled", value: "true", isSynced: false }, + { name: "hardwareAccelerationEnabled", value: "true", isSynced: false }, { name: "newLayout", value: "true", isSynced: true }, // PDF