// @effect-diagnostics nodeBuiltinImport:off - pre-ready Electron setup reads settings and prepares the Linux desktop entry synchronously before app services are available. import * as NodeFS from "node:fs"; import * as NodeOS from "node:os"; import * as NodePath from "node:path"; import * as Context from "effect/Context"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Electron from "electron"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import * as DesktopEarlyElectronStartup from "./DesktopEarlyElectronStartup.ts"; import { resolveDesktopAppBranding } from "./DesktopEnvironment.ts"; import { renderUrlHandlerDesktopEntry } from "./DesktopLinuxUrlHandler.ts"; import * as ElectronProtocol from "../electron/ElectronProtocol.ts"; export interface DesktopPreReadyCommandLineReader { readonly hasSwitch: (switchName: string) => boolean; readonly getSwitchValue: (switchName: string) => string; } function readCommandLineSwitchValue( commandLine: DesktopPreReadyCommandLineReader, switchName: string, ): string | null { if (!commandLine.hasSwitch(switchName)) { return null; } const value = commandLine.getSwitchValue(switchName).trim(); return value.length > 0 ? value : null; } export const resolveEarlyLinuxElectronOptionsFromProcess = (): DesktopEarlyElectronStartup.EarlyLinuxElectronOptions => DesktopEarlyElectronStartup.resolveEarlyLinuxElectronOptions({ env: process.env, homeDirectory: NodeOS.homedir(), joinPath: NodePath.posix.join, readFileString: (path) => NodeFS.readFileSync(path, "utf8"), }); export class DesktopPreReadyElectronOptions extends Context.Service< DesktopPreReadyElectronOptions, { readonly linux: DesktopEarlyElectronStartup.EarlyLinuxElectronOptions | null; readonly linuxPasswordStoreCommandLine: string | null; } >()("@t3tools/desktop/app/DesktopPreReadyPlatform/DesktopPreReadyElectronOptions") {} /** @public Service construction is part of the canonical Effect module API. */ export const make = Effect.gen(function* () { const platform = yield* HostProcessPlatform; return yield* Effect.sync((): DesktopPreReadyElectronOptions["Service"] => { const linuxPasswordStoreCommandLine = platform === "linux" ? readCommandLineSwitchValue(Electron.app.commandLine, "password-store") : null; const linux = platform === "linux" ? resolveEarlyLinuxElectronOptionsFromProcess() : null; if (linux !== null) { // The portal also requires a valid desktop entry. An AppImage update may // have removed the executable referenced by the previous launch's entry. try { const applicationsDir = NodePath.posix.join( process.env.XDG_DATA_HOME?.trim() || NodePath.posix.join(NodeOS.homedir(), ".local", "share"), "applications", ); NodeFS.mkdirSync(applicationsDir, { recursive: true }); NodeFS.writeFileSync( NodePath.posix.join(applicationsDir, linux.linuxDesktopEntryName), renderUrlHandlerDesktopEntry({ displayName: resolveDesktopAppBranding({ isDevelopment: linux.isDevelopment, appVersion: Electron.app.getVersion(), }).displayName, execTarget: process.env.APPIMAGE?.trim() || process.execPath, scheme: ElectronProtocol.getDesktopScheme(linux.isDevelopment), }), "utf8", ); } catch { // The URL handler retries with the full environment and logs failures. } // Chromium caches its portal registration during startup. Set the identity // before any asynchronous work can initialize it with Electron's default. Electron.app.setDesktopName(linux.linuxDesktopEntryName); Electron.app.commandLine.appendSwitch("class", linux.linuxWmClass); if (linux.passwordStore !== null && linuxPasswordStoreCommandLine === null) { Electron.app.commandLine.appendSwitch("password-store", linux.passwordStore); } } return { linux, linuxPasswordStoreCommandLine }; }); }).pipe(Effect.withSpan("desktop.electron.configureBeforeReady")); // Keep Electron's strict pre-ready setup isolated so later runtime layers cannot // observe app readiness before scheme privileges and command-line switches exist. export const layer = Layer.mergeAll( ElectronProtocol.layerSchemePrivileges, Layer.effect(DesktopPreReadyElectronOptions, make), );