From 23ce89668156d8361d670eac38a9f479e185a3af Mon Sep 17 00:00:00 2001 From: perf3ct Date: Wed, 11 Jun 2025 14:04:42 +0000 Subject: [PATCH 01/14] feat(client): show warning when running through rosetta 2 --- apps/client/src/components/app_context.ts | 1 + apps/client/src/desktop.ts | 21 ++++- apps/client/src/layouts/layout_commons.ts | 2 + .../src/widgets/dialogs/rosetta_warning.ts | 84 +++++++++++++++++++ apps/server/src/routes/api/system_info.ts | 12 +++ apps/server/src/routes/routes.ts | 2 + apps/server/src/services/utils.ts | 29 +++++++ 7 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/widgets/dialogs/rosetta_warning.ts create mode 100644 apps/server/src/routes/api/system_info.ts diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index f7a5dfb1e..cdaf1b137 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -128,6 +128,7 @@ export type CommandMappings = { openAboutDialog: CommandData; hideFloatingButtons: {}; hideLeftPane: CommandData; + showRosettaWarning: CommandData; showLeftPane: CommandData; hoistNote: CommandData & { noteId: string }; leaveProtectedSession: CommandData; diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index 1a0f7e8a9..e762aa65b 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -8,6 +8,7 @@ import electronContextMenu from "./menus/electron_context_menu.js"; import glob from "./services/glob.js"; import { t } from "./services/i18n.js"; import options from "./services/options.js"; +import server from "./services/server.js"; import type ElectronRemote from "@electron/remote"; import type Electron from "electron"; import "./stylesheets/bootstrap.scss"; @@ -22,7 +23,10 @@ bundleService.getWidgetBundlesByParent().then(async (widgetBundles) => { const DesktopLayout = (await import("./layouts/desktop_layout.js")).default; appContext.setLayout(new DesktopLayout(widgetBundles)); - appContext.start().catch((e) => { + appContext.start().then(() => { + // Check for Rosetta 2 after the app has fully started + checkRosetta2Warning(); + }).catch((e) => { toastService.showPersistent({ title: t("toast.critical-error.title"), icon: "alert", @@ -114,3 +118,18 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) { const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote; nativeTheme.themeSource = themeSource; } + +async function checkRosetta2Warning() { + if (!utils.isElectron()) return; + + try { + // Check if running under Rosetta 2 by calling the server + const response = await server.get("api/system-info/rosetta-check") as { isRunningUnderRosetta2: boolean }; + if (response.isRunningUnderRosetta2) { + // Trigger the Rosetta 2 warning dialog + appContext.triggerCommand("showRosettaWarning", {}); + } + } catch (error) { + console.warn("Could not check Rosetta 2 status:", error); + } +} diff --git a/apps/client/src/layouts/layout_commons.ts b/apps/client/src/layouts/layout_commons.ts index d9559cde2..9c0716c4f 100644 --- a/apps/client/src/layouts/layout_commons.ts +++ b/apps/client/src/layouts/layout_commons.ts @@ -21,6 +21,7 @@ import ConfirmDialog from "../widgets/dialogs/confirm.js"; import RevisionsDialog from "../widgets/dialogs/revisions.js"; import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js"; import InfoDialog from "../widgets/dialogs/info.js"; +import RosettaWarningDialog from "../widgets/dialogs/rosetta_warning.js"; export function applyModals(rootContainer: RootContainer) { rootContainer @@ -45,4 +46,5 @@ export function applyModals(rootContainer: RootContainer) { .child(new InfoDialog()) .child(new ConfirmDialog()) .child(new PromptDialog()) + .child(new RosettaWarningDialog()) } diff --git a/apps/client/src/widgets/dialogs/rosetta_warning.ts b/apps/client/src/widgets/dialogs/rosetta_warning.ts new file mode 100644 index 000000000..1cd067869 --- /dev/null +++ b/apps/client/src/widgets/dialogs/rosetta_warning.ts @@ -0,0 +1,84 @@ +import BasicWidget from "../basic_widget.js"; +import { Modal } from "bootstrap"; +import utils from "../../services/utils.js"; + +const TPL = /*html*/` +`; + +export default class RosettaWarningDialog extends BasicWidget { + private modal!: Modal; + private $downloadButton!: JQuery; + private $continueButton!: JQuery; + + doRender() { + this.$widget = $(TPL); + this.modal = Modal.getOrCreateInstance(this.$widget[0]); + this.$downloadButton = this.$widget.find(".download-correct-version-button"); + this.$continueButton = this.$widget.find(".continue-anyway-button"); + + this.$downloadButton.on("click", () => { + // Open the releases page where users can download the correct version + if (utils.isElectron()) { + const { shell } = utils.dynamicRequire("electron"); + shell.openExternal("https://github.com/TriliumNext/Notes/releases/latest"); + } else { + window.open("https://github.com/TriliumNext/Notes/releases/latest", "_blank"); + } + }); + + // Auto-focus the download button when shown + this.$widget.on("shown.bs.modal", () => { + this.$downloadButton.trigger("focus"); + }); + } + + showRosettaWarningEvent() { + this.modal.show(); + } +} diff --git a/apps/server/src/routes/api/system_info.ts b/apps/server/src/routes/api/system_info.ts new file mode 100644 index 000000000..4d49f4ca2 --- /dev/null +++ b/apps/server/src/routes/api/system_info.ts @@ -0,0 +1,12 @@ +import { isRunningUnderRosetta2 } from "../../services/utils.js"; +import type { Request, Response } from "express"; + +function rosettaCheck(req: Request, res: Response) { + res.json({ + isRunningUnderRosetta2: isRunningUnderRosetta2() + }); +} + +export default { + rosettaCheck +}; diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index b988ecb11..eee979859 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -58,6 +58,7 @@ import ollamaRoute from "./api/ollama.js"; import openaiRoute from "./api/openai.js"; import anthropicRoute from "./api/anthropic.js"; import llmRoute from "./api/llm.js"; +import systemInfoRoute from "./api/system_info.js"; import etapiAuthRoutes from "../etapi/auth.js"; import etapiAppInfoRoutes from "../etapi/app_info.js"; @@ -238,6 +239,7 @@ function register(app: express.Application) { apiRoute(PST, "/api/recent-notes", recentNotesRoute.addRecentNote); apiRoute(GET, "/api/app-info", appInfoRoute.getAppInfo); apiRoute(GET, "/api/metrics", metricsRoute.getMetrics); + apiRoute(GET, "/api/system-info/rosetta-check", systemInfoRoute.rosettaCheck); // docker health check route(GET, "/api/health-check", [], () => ({ status: "ok" }), apiResultHandler); diff --git a/apps/server/src/services/utils.ts b/apps/server/src/services/utils.ts index 89aad1bbb..c02a325d8 100644 --- a/apps/server/src/services/utils.ts +++ b/apps/server/src/services/utils.ts @@ -23,6 +23,34 @@ export const isElectron = !!process.versions["electron"]; export const isDev = !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === "dev"); +/** + * Detects if the application is running under Rosetta 2 translation on Apple Silicon. + * This happens when an x64 version of the app is run on an M1/M2/M3 Mac. + * Uses the macOS sysctl.proc_translated to properly detect translation. + * @returns true if running under Rosetta 2, false otherwise + */ +export const isRunningUnderRosetta2 = () => { + if (!isMac) return false; + + try { + // Use child_process to check sysctl.proc_translated + // This is the proper way to detect Rosetta 2 translation + const { execSync } = require("child_process"); + const result = execSync("sysctl -n sysctl.proc_translated 2>/dev/null", { + encoding: "utf8", + timeout: 1000 + }).trim(); + + // 1 means the process is being translated by Rosetta 2 + // 0 means native execution + // If the sysctl doesn't exist (on Intel Macs), this will return empty/error + return result === "1"; + } catch (error) { + // If sysctl fails or doesn't exist (Intel Macs), not running under Rosetta 2 + return false; + } +}; + export function newEntityId() { return randomString(12); } @@ -395,6 +423,7 @@ export default { isElectron, isEmptyOrWhitespace, isMac, + isRunningUnderRosetta2, isStringNote, isWindows, md5, From 3041af7fe21bcdb287f56c5011a7d33c4d6275ae Mon Sep 17 00:00:00 2001 From: perf3ct Date: Wed, 11 Jun 2025 14:13:59 +0000 Subject: [PATCH 02/14] feat(client): also fix translations --- .../src/translations/en/translation.json | 9 +++ .../src/widgets/dialogs/rosetta_warning.ts | 62 ++++++++++--------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 44b50445b..2a8f743ef 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1918,5 +1918,14 @@ "title": "Appearance", "word_wrapping": "Word wrapping", "color-scheme": "Color scheme" + }, + "rosetta_warning": { + "title": "Performance Warning: Running Under Rosetta 2", + "message": "TriliumNext is currently running under Rosetta 2 translation, which means you're using the Intel (x64) version on an Apple Silicon Mac.", + "performance_impact": "This will significantly impact performance and battery life.", + "recommendation": "For the best experience, please download the native Apple Silicon (ARM64) version of TriliumNext from our releases page.", + "download_link": "Download Native Version", + "continue_anyway": "Continue Anyway", + "dont_show_again": "Don't show this warning again" } } diff --git a/apps/client/src/widgets/dialogs/rosetta_warning.ts b/apps/client/src/widgets/dialogs/rosetta_warning.ts index 1cd067869..cccfb195d 100644 --- a/apps/client/src/widgets/dialogs/rosetta_warning.ts +++ b/apps/client/src/widgets/dialogs/rosetta_warning.ts @@ -1,6 +1,7 @@ import BasicWidget from "../basic_widget.js"; import { Modal } from "bootstrap"; import utils from "../../services/utils.js"; +import { t } from "../../services/i18n.js"; const TPL = /*html*/` `; @@ -62,6 +57,15 @@ export default class RosettaWarningDialog extends BasicWidget { this.$downloadButton = this.$widget.find(".download-correct-version-button"); this.$continueButton = this.$widget.find(".continue-anyway-button"); + // Populate text using translation keys + this.$widget.find(".rosetta-warning-title").text(t("rosetta_warning.title")); + this.$widget.find(".rosetta-warning-performance-impact").text(t("rosetta_warning.performance_impact")); + this.$widget.find(".rosetta-warning-message").text(t("rosetta_warning.message")); + this.$widget.find(".rosetta-warning-recommendation").text(t("rosetta_warning.recommendation")); + this.$widget.find(".rosetta-warning-download-link").text(t("rosetta_warning.download_link")); + this.$widget.find(".rosetta-warning-continue-anyway").text(t("rosetta_warning.continue_anyway")); + this.$widget.find(".rosetta-warning-dont-show-again").text(t("rosetta_warning.dont_show_again")); + this.$downloadButton.on("click", () => { // Open the releases page where users can download the correct version if (utils.isElectron()) { From e401c8c930c8160f5acd83cfff3b97aef3ca152a Mon Sep 17 00:00:00 2001 From: perf3ct Date: Wed, 11 Jun 2025 14:30:33 +0000 Subject: [PATCH 03/14] fix(client): also move the logic from the server to the client lol --- apps/client/src/desktop.ts | 9 +++-- apps/client/src/services/rosetta_detection.ts | 34 +++++++++++++++++++ apps/server/src/routes/api/system_info.ts | 12 ------- apps/server/src/routes/routes.ts | 2 -- apps/server/src/services/utils.ts | 27 --------------- 5 files changed, 38 insertions(+), 46 deletions(-) create mode 100644 apps/client/src/services/rosetta_detection.ts delete mode 100644 apps/server/src/routes/api/system_info.ts diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index e762aa65b..88f3e0731 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -8,7 +8,7 @@ import electronContextMenu from "./menus/electron_context_menu.js"; import glob from "./services/glob.js"; import { t } from "./services/i18n.js"; import options from "./services/options.js"; -import server from "./services/server.js"; +import { isRunningUnderRosetta2 } from "./services/rosetta_detection.js"; import type ElectronRemote from "@electron/remote"; import type Electron from "electron"; import "./stylesheets/bootstrap.scss"; @@ -119,13 +119,12 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) { nativeTheme.themeSource = themeSource; } -async function checkRosetta2Warning() { +function checkRosetta2Warning() { if (!utils.isElectron()) return; try { - // Check if running under Rosetta 2 by calling the server - const response = await server.get("api/system-info/rosetta-check") as { isRunningUnderRosetta2: boolean }; - if (response.isRunningUnderRosetta2) { + // Check if running under Rosetta 2 directly on client + if (isRunningUnderRosetta2()) { // Trigger the Rosetta 2 warning dialog appContext.triggerCommand("showRosettaWarning", {}); } diff --git a/apps/client/src/services/rosetta_detection.ts b/apps/client/src/services/rosetta_detection.ts new file mode 100644 index 000000000..b3d0fccd2 --- /dev/null +++ b/apps/client/src/services/rosetta_detection.ts @@ -0,0 +1,34 @@ +import utils from "./utils.js"; + +/** + * Detects if the application is running under Rosetta 2 translation on Apple Silicon. + * This happens when an x64 version of the app is run on an M1/M2/M3 Mac. + * Uses the macOS sysctl.proc_translated to properly detect translation. + * @returns true if running under Rosetta 2, false otherwise + */ +export function isRunningUnderRosetta2(): boolean { + if (!utils.isElectron()) return false; + + const process = utils.dynamicRequire("process"); + const { execSync } = utils.dynamicRequire("child_process"); + + // Only check on macOS + if (process.platform !== "darwin") return false; + + try { + // Use sysctl.proc_translated to check if process is being translated by Rosetta 2 + // This is the proper way to detect Rosetta 2 translation + const result = execSync("sysctl -n sysctl.proc_translated 2>/dev/null", { + encoding: "utf8", + timeout: 1000 + }).trim(); + + // Returns "1" if running under Rosetta 2, "0" if native ARM64 + // Returns empty string or error on Intel Macs (where the key doesn't exist) + return result === "1"; + } catch (error) { + // If the command fails (e.g., on Intel Macs), assume not running under Rosetta 2 + console.debug("Could not check Rosetta 2 status:", error); + return false; + } +} diff --git a/apps/server/src/routes/api/system_info.ts b/apps/server/src/routes/api/system_info.ts deleted file mode 100644 index 4d49f4ca2..000000000 --- a/apps/server/src/routes/api/system_info.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { isRunningUnderRosetta2 } from "../../services/utils.js"; -import type { Request, Response } from "express"; - -function rosettaCheck(req: Request, res: Response) { - res.json({ - isRunningUnderRosetta2: isRunningUnderRosetta2() - }); -} - -export default { - rosettaCheck -}; diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index eee979859..b988ecb11 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -58,7 +58,6 @@ import ollamaRoute from "./api/ollama.js"; import openaiRoute from "./api/openai.js"; import anthropicRoute from "./api/anthropic.js"; import llmRoute from "./api/llm.js"; -import systemInfoRoute from "./api/system_info.js"; import etapiAuthRoutes from "../etapi/auth.js"; import etapiAppInfoRoutes from "../etapi/app_info.js"; @@ -239,7 +238,6 @@ function register(app: express.Application) { apiRoute(PST, "/api/recent-notes", recentNotesRoute.addRecentNote); apiRoute(GET, "/api/app-info", appInfoRoute.getAppInfo); apiRoute(GET, "/api/metrics", metricsRoute.getMetrics); - apiRoute(GET, "/api/system-info/rosetta-check", systemInfoRoute.rosettaCheck); // docker health check route(GET, "/api/health-check", [], () => ({ status: "ok" }), apiResultHandler); diff --git a/apps/server/src/services/utils.ts b/apps/server/src/services/utils.ts index c02a325d8..7555e1a7c 100644 --- a/apps/server/src/services/utils.ts +++ b/apps/server/src/services/utils.ts @@ -23,33 +23,7 @@ export const isElectron = !!process.versions["electron"]; export const isDev = !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === "dev"); -/** - * Detects if the application is running under Rosetta 2 translation on Apple Silicon. - * This happens when an x64 version of the app is run on an M1/M2/M3 Mac. - * Uses the macOS sysctl.proc_translated to properly detect translation. - * @returns true if running under Rosetta 2, false otherwise - */ -export const isRunningUnderRosetta2 = () => { - if (!isMac) return false; - try { - // Use child_process to check sysctl.proc_translated - // This is the proper way to detect Rosetta 2 translation - const { execSync } = require("child_process"); - const result = execSync("sysctl -n sysctl.proc_translated 2>/dev/null", { - encoding: "utf8", - timeout: 1000 - }).trim(); - - // 1 means the process is being translated by Rosetta 2 - // 0 means native execution - // If the sysctl doesn't exist (on Intel Macs), this will return empty/error - return result === "1"; - } catch (error) { - // If sysctl fails or doesn't exist (Intel Macs), not running under Rosetta 2 - return false; - } -}; export function newEntityId() { return randomString(12); @@ -423,7 +397,6 @@ export default { isElectron, isEmptyOrWhitespace, isMac, - isRunningUnderRosetta2, isStringNote, isWindows, md5, From f6bba436f4c46ccbddb811f46864571fcb473e8c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 12 Jun 2025 21:46:27 +0300 Subject: [PATCH 04/14] Revert "fix(client): also move the logic from the server to the client lol" This reverts commit e401c8c930c8160f5acd83cfff3b97aef3ca152a. --- apps/client/src/desktop.ts | 9 ++--- apps/client/src/services/rosetta_detection.ts | 34 ------------------- apps/server/src/routes/api/system_info.ts | 12 +++++++ apps/server/src/routes/routes.ts | 2 ++ apps/server/src/services/utils.ts | 27 +++++++++++++++ 5 files changed, 46 insertions(+), 38 deletions(-) delete mode 100644 apps/client/src/services/rosetta_detection.ts create mode 100644 apps/server/src/routes/api/system_info.ts diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index 88f3e0731..e762aa65b 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -8,7 +8,7 @@ import electronContextMenu from "./menus/electron_context_menu.js"; import glob from "./services/glob.js"; import { t } from "./services/i18n.js"; import options from "./services/options.js"; -import { isRunningUnderRosetta2 } from "./services/rosetta_detection.js"; +import server from "./services/server.js"; import type ElectronRemote from "@electron/remote"; import type Electron from "electron"; import "./stylesheets/bootstrap.scss"; @@ -119,12 +119,13 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) { nativeTheme.themeSource = themeSource; } -function checkRosetta2Warning() { +async function checkRosetta2Warning() { if (!utils.isElectron()) return; try { - // Check if running under Rosetta 2 directly on client - if (isRunningUnderRosetta2()) { + // Check if running under Rosetta 2 by calling the server + const response = await server.get("api/system-info/rosetta-check") as { isRunningUnderRosetta2: boolean }; + if (response.isRunningUnderRosetta2) { // Trigger the Rosetta 2 warning dialog appContext.triggerCommand("showRosettaWarning", {}); } diff --git a/apps/client/src/services/rosetta_detection.ts b/apps/client/src/services/rosetta_detection.ts deleted file mode 100644 index b3d0fccd2..000000000 --- a/apps/client/src/services/rosetta_detection.ts +++ /dev/null @@ -1,34 +0,0 @@ -import utils from "./utils.js"; - -/** - * Detects if the application is running under Rosetta 2 translation on Apple Silicon. - * This happens when an x64 version of the app is run on an M1/M2/M3 Mac. - * Uses the macOS sysctl.proc_translated to properly detect translation. - * @returns true if running under Rosetta 2, false otherwise - */ -export function isRunningUnderRosetta2(): boolean { - if (!utils.isElectron()) return false; - - const process = utils.dynamicRequire("process"); - const { execSync } = utils.dynamicRequire("child_process"); - - // Only check on macOS - if (process.platform !== "darwin") return false; - - try { - // Use sysctl.proc_translated to check if process is being translated by Rosetta 2 - // This is the proper way to detect Rosetta 2 translation - const result = execSync("sysctl -n sysctl.proc_translated 2>/dev/null", { - encoding: "utf8", - timeout: 1000 - }).trim(); - - // Returns "1" if running under Rosetta 2, "0" if native ARM64 - // Returns empty string or error on Intel Macs (where the key doesn't exist) - return result === "1"; - } catch (error) { - // If the command fails (e.g., on Intel Macs), assume not running under Rosetta 2 - console.debug("Could not check Rosetta 2 status:", error); - return false; - } -} diff --git a/apps/server/src/routes/api/system_info.ts b/apps/server/src/routes/api/system_info.ts new file mode 100644 index 000000000..4d49f4ca2 --- /dev/null +++ b/apps/server/src/routes/api/system_info.ts @@ -0,0 +1,12 @@ +import { isRunningUnderRosetta2 } from "../../services/utils.js"; +import type { Request, Response } from "express"; + +function rosettaCheck(req: Request, res: Response) { + res.json({ + isRunningUnderRosetta2: isRunningUnderRosetta2() + }); +} + +export default { + rosettaCheck +}; diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index b988ecb11..eee979859 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -58,6 +58,7 @@ import ollamaRoute from "./api/ollama.js"; import openaiRoute from "./api/openai.js"; import anthropicRoute from "./api/anthropic.js"; import llmRoute from "./api/llm.js"; +import systemInfoRoute from "./api/system_info.js"; import etapiAuthRoutes from "../etapi/auth.js"; import etapiAppInfoRoutes from "../etapi/app_info.js"; @@ -238,6 +239,7 @@ function register(app: express.Application) { apiRoute(PST, "/api/recent-notes", recentNotesRoute.addRecentNote); apiRoute(GET, "/api/app-info", appInfoRoute.getAppInfo); apiRoute(GET, "/api/metrics", metricsRoute.getMetrics); + apiRoute(GET, "/api/system-info/rosetta-check", systemInfoRoute.rosettaCheck); // docker health check route(GET, "/api/health-check", [], () => ({ status: "ok" }), apiResultHandler); diff --git a/apps/server/src/services/utils.ts b/apps/server/src/services/utils.ts index 7555e1a7c..c02a325d8 100644 --- a/apps/server/src/services/utils.ts +++ b/apps/server/src/services/utils.ts @@ -23,7 +23,33 @@ export const isElectron = !!process.versions["electron"]; export const isDev = !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === "dev"); +/** + * Detects if the application is running under Rosetta 2 translation on Apple Silicon. + * This happens when an x64 version of the app is run on an M1/M2/M3 Mac. + * Uses the macOS sysctl.proc_translated to properly detect translation. + * @returns true if running under Rosetta 2, false otherwise + */ +export const isRunningUnderRosetta2 = () => { + if (!isMac) return false; + try { + // Use child_process to check sysctl.proc_translated + // This is the proper way to detect Rosetta 2 translation + const { execSync } = require("child_process"); + const result = execSync("sysctl -n sysctl.proc_translated 2>/dev/null", { + encoding: "utf8", + timeout: 1000 + }).trim(); + + // 1 means the process is being translated by Rosetta 2 + // 0 means native execution + // If the sysctl doesn't exist (on Intel Macs), this will return empty/error + return result === "1"; + } catch (error) { + // If sysctl fails or doesn't exist (Intel Macs), not running under Rosetta 2 + return false; + } +}; export function newEntityId() { return randomString(12); @@ -397,6 +423,7 @@ export default { isElectron, isEmptyOrWhitespace, isMac, + isRunningUnderRosetta2, isStringNote, isWindows, md5, From b4d2d21620befabbe624e9738473b7fbad4bd274 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 12 Jun 2025 21:59:59 +0300 Subject: [PATCH 05/14] refactor(client): relocate architecture mismatch checks --- apps/client/src/components/app_context.ts | 10 ++++++++- apps/client/src/components/startup_checks.ts | 23 ++++++++++++++++++++ apps/client/src/desktop.ts | 20 +---------------- 3 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 apps/client/src/components/startup_checks.ts diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index cdaf1b137..520f9e68b 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -28,6 +28,7 @@ import type { NativeImage, TouchBar } from "electron"; import TouchBarComponent from "./touch_bar.js"; import type { CKTextEditor } from "@triliumnext/ckeditor5"; import type CodeMirror from "@triliumnext/codemirror"; +import { StartupChecks } from "./startup_checks.js"; interface Layout { getRootWidget: (appContext: AppContext) => RootWidget; @@ -474,7 +475,14 @@ export class AppContext extends Component { initComponents() { this.tabManager = new TabManager(); - this.components = [this.tabManager, new RootCommandExecutor(), new Entrypoints(), new MainTreeExecutors(), new ShortcutComponent()]; + this.components = [ + this.tabManager, + new RootCommandExecutor(), + new Entrypoints(), + new MainTreeExecutors(), + new ShortcutComponent(), + new StartupChecks() + ]; if (utils.isMobile()) { this.components.push(new MobileScreenSwitcherExecutor()); diff --git a/apps/client/src/components/startup_checks.ts b/apps/client/src/components/startup_checks.ts new file mode 100644 index 000000000..f40175afb --- /dev/null +++ b/apps/client/src/components/startup_checks.ts @@ -0,0 +1,23 @@ +import server from "../services/server"; +import Component from "./component"; + +export class StartupChecks extends Component { + + constructor() { + super(); + this.checkRosetta2Warning(); + } + + async checkRosetta2Warning() { + try { + // Check if running under Rosetta 2 by calling the server + const response = await server.get("system-info/rosetta-check") as { isRunningUnderRosetta2: boolean }; + if (response.isRunningUnderRosetta2) { + // Trigger the Rosetta 2 warning dialog + this.triggerCommand("showRosettaWarning", {}); + } + } catch (error) { + console.warn("Could not check Rosetta 2 status:", error); + } + } +} diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index e762aa65b..65e2e285a 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -23,10 +23,7 @@ bundleService.getWidgetBundlesByParent().then(async (widgetBundles) => { const DesktopLayout = (await import("./layouts/desktop_layout.js")).default; appContext.setLayout(new DesktopLayout(widgetBundles)); - appContext.start().then(() => { - // Check for Rosetta 2 after the app has fully started - checkRosetta2Warning(); - }).catch((e) => { + appContext.start().catch((e) => { toastService.showPersistent({ title: t("toast.critical-error.title"), icon: "alert", @@ -118,18 +115,3 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) { const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote; nativeTheme.themeSource = themeSource; } - -async function checkRosetta2Warning() { - if (!utils.isElectron()) return; - - try { - // Check if running under Rosetta 2 by calling the server - const response = await server.get("api/system-info/rosetta-check") as { isRunningUnderRosetta2: boolean }; - if (response.isRunningUnderRosetta2) { - // Trigger the Rosetta 2 warning dialog - appContext.triggerCommand("showRosettaWarning", {}); - } - } catch (error) { - console.warn("Could not check Rosetta 2 status:", error); - } -} From fe1f4a42949a8cdc91c88a9069808b232e9cc106 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 12 Jun 2025 22:03:22 +0300 Subject: [PATCH 06/14] refactor(client): remove contrasting title bar --- apps/client/src/widgets/dialogs/rosetta_warning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/widgets/dialogs/rosetta_warning.ts b/apps/client/src/widgets/dialogs/rosetta_warning.ts index cccfb195d..46ebb3053 100644 --- a/apps/client/src/widgets/dialogs/rosetta_warning.ts +++ b/apps/client/src/widgets/dialogs/rosetta_warning.ts @@ -7,7 +7,7 @@ const TPL = /*html*/`