feat(desktop): Linux AppImage releases and desktop feature parity (#2398)
* feat(electron): add Linux AppImage releases * ci: cache Linux OpenCode CLI artifacts * fix(ci): await Linux release inventory check * fix(electron): add frameless window controls on Linux desktop Linux AppImages were created without native WM decorations and without in-app controls, leaving users unable to close the window with a mouse. Treat Linux like Windows: frameless BrowserWindow plus the existing WindowsWindowControls header buttons and app-menu entry. macOS keeps hidden title bar with traffic lights unchanged. Shared usesFramelessElectronChrome() helper drives main window, mini chat, header insets, and titlebar controls. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * Linux desktop feature parity: Open in, background start, tray, multi-window (#2392) * feat(electron): Linux parity for Open in, background start, and tray Enable Linux desktop feature parity with macOS/Windows: open projects in the default file manager and discovered apps, XDG autostart with --background launches, system tray (including minimize-to-tray), and tray sync from the renderer. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * fix(electron): allow packaged UI protocol navigations on Linux Prevent openchamber-ui:// navigations from being handed to shell.openExternal, which fails on Linux and blocked desktop UI flows. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * fix(ui): surface Linux tray settings in settings search Include isLinux in settings search runtime context so minimize-to-tray is discoverable on Linux desktop, matching Windows search behavior. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * fix(electron): stop Linux AppImage Waiting-for-OpenCode flicker Sync updated boot-outcome init scripts to all BrowserWindows after desktop_hosts_set, and prefer state.initScript on dom-ready so chooser reloads inject local/ok instead of a stale not-configured outcome. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * fix(desktop): restore Linux AppImage updater feed and error UX (#2396) Treat missing latest-linux*.yml (404) as no update available instead of a hard failure, and stop swallowing updater capability/download errors in the desktop bridge so About/sidebar can show actionable messages. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * docs: credit Linux AppImage contributors in changelog Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> --------- Co-authored-by: jibanez-staticduo <staticduo@gmail.com> Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
co-authored by
Cursor Agent
Serhii Dziupin
jibanez-staticduo
parent
ddbb3c1db0
commit
18b58bdb6b
+196
-33
@@ -17,6 +17,18 @@ import { sanitizeRuntimeRequestHeaders } from './runtime-request-headers.mjs';
|
||||
import { assertUpdaterCapability } from './updater-capability.mjs';
|
||||
import { checkForDesktopUpdate } from './updater-check.mjs';
|
||||
import { resolveUpdaterFeed } from './updater-feed.mjs';
|
||||
import {
|
||||
buildLinuxInstalledApps,
|
||||
buildLinuxOpenSpecs,
|
||||
fetchLinuxAppIcons,
|
||||
filterLinuxInstalledApps,
|
||||
readLinuxDesktopEntries,
|
||||
} from './linux-app-discovery.mjs';
|
||||
import {
|
||||
readLinuxAutostartEnabled,
|
||||
setLinuxAutostartEnabled,
|
||||
} from './linux-autostart.mjs';
|
||||
import { unsupportedAppSpecificOpenError, validateLocalPath } from './path-open-utils.mjs';
|
||||
import { mintOutsideFileGrant } from '@openchamber/web/server/lib/fs/routes.js';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
@@ -44,6 +56,9 @@ const getLoginItemOptions = () => {
|
||||
};
|
||||
|
||||
const readLoginItemSettings = () => {
|
||||
if (process.platform === 'linux') {
|
||||
return null;
|
||||
}
|
||||
if (process.platform !== 'darwin' && process.platform !== 'win32') return null;
|
||||
try {
|
||||
return app.getLoginItemSettings(getLoginItemOptions());
|
||||
@@ -182,6 +197,7 @@ const GITHUB_FEATURE_REQUEST_URL = 'https://github.com/openchamber/openchamber/i
|
||||
const DISCORD_INVITE_URL = 'https://discord.gg/ZYRSdnwwKA';
|
||||
const INSTALLED_APPS_CACHE_TTL_SECS = 60 * 60 * 24;
|
||||
const INSTALLED_APPS_CACHE_FILE = 'discovered-apps.json';
|
||||
const LINUX_DESKTOP_ENTRIES_CACHE_TTL_MS = 30_000;
|
||||
const OPENCODE_SHUTDOWN_GRACE_MS = 100;
|
||||
const { autoUpdater } = updaterPkg;
|
||||
|
||||
@@ -244,7 +260,7 @@ const readDesktopKeepAwakeStatus = () => {
|
||||
};
|
||||
|
||||
const readDesktopMinimizeToTrayStatus = () => {
|
||||
const supported = process.platform === 'win32';
|
||||
const supported = process.platform === 'win32' || process.platform === 'linux';
|
||||
return {
|
||||
supported,
|
||||
enabled: supported && readSettingsRoot().desktopMinimizeToTrayEnabled === true,
|
||||
@@ -252,7 +268,7 @@ const readDesktopMinimizeToTrayStatus = () => {
|
||||
};
|
||||
|
||||
const shouldHideMainWindowToTray = (browserWindow) => {
|
||||
if (process.platform !== 'win32') return false;
|
||||
if (process.platform !== 'win32' && process.platform !== 'linux') return false;
|
||||
if (!state.trayController) return false;
|
||||
if (!browserWindow || browserWindow.isDestroyed()) return false;
|
||||
if (browserWindow.__ocMiniChat === true) return false;
|
||||
@@ -1540,6 +1556,18 @@ const buildInitScript = (localOrigin, bootOutcome, apiBaseUrl = '', clientToken
|
||||
].join('');
|
||||
};
|
||||
|
||||
// Keep per-window init scripts aligned with state. Chooser/onboarding reloads after
|
||||
// desktop_hosts_set; if only state.initScript is updated, dom-ready reinjects a stale
|
||||
// not-configured outcome and the UI flickers on "Waiting for OpenCode".
|
||||
const syncInitScriptToWindows = (initScript = state.initScript) => {
|
||||
if (!initScript) return;
|
||||
for (const win of BrowserWindow.getAllWindows()) {
|
||||
if (!win.isDestroyed()) {
|
||||
win.__ocInitScript = initScript;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const computeBootOutcome = ({ envTargetUrl, probe, config, localAvailable }) => {
|
||||
const availability = { localAvailable };
|
||||
if (envTargetUrl) {
|
||||
@@ -2400,6 +2428,7 @@ const createBrowserWindow = ({ label, restoreGeometry, url, runtimeConfig = {} }
|
||||
try {
|
||||
const url = new URL(raw);
|
||||
if (url.protocol === 'devtools:') return true;
|
||||
if (url.protocol === `${UI_PROTOCOL}:`) return true;
|
||||
if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
|
||||
if (state.localOrigin) {
|
||||
try {
|
||||
@@ -2447,8 +2476,11 @@ const createBrowserWindow = ({ label, restoreGeometry, url, runtimeConfig = {} }
|
||||
});
|
||||
|
||||
browserWindow.webContents.on('dom-ready', () => {
|
||||
const initScript = browserWindow.__ocInitScript || state.initScript;
|
||||
// Prefer authoritative state script so hosts_set updates survive reloads even if a
|
||||
// window still holds a pre-activation / not-configured __ocInitScript.
|
||||
const initScript = state.initScript || browserWindow.__ocInitScript;
|
||||
if (initScript) {
|
||||
browserWindow.__ocInitScript = initScript;
|
||||
void browserWindow.webContents.executeJavaScript(initScript).catch(() => {});
|
||||
}
|
||||
});
|
||||
@@ -2499,6 +2531,7 @@ const activateMainWindow = async (url, localOrigin, bootOutcome, runtimeConfig =
|
||||
rendererRuntimeConfig.clientToken,
|
||||
rendererRuntimeConfig.requestHeaders,
|
||||
);
|
||||
syncInitScriptToWindows(state.initScript);
|
||||
|
||||
const mainWindow = state.mainWindow;
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
@@ -2722,8 +2755,9 @@ const createMiniChatWindow = async ({ mode, sessionId = '', directory = '', proj
|
||||
void shell.openExternal(url).catch(() => {});
|
||||
});
|
||||
browserWindow.webContents.on('dom-ready', () => {
|
||||
const initScript = browserWindow.__ocInitScript || state.initScript;
|
||||
const initScript = state.initScript || browserWindow.__ocInitScript;
|
||||
if (initScript) {
|
||||
browserWindow.__ocInitScript = initScript;
|
||||
void browserWindow.webContents.executeJavaScript(initScript).catch(() => {});
|
||||
}
|
||||
});
|
||||
@@ -3007,6 +3041,74 @@ const buildInstalledApps = async (apps) => {
|
||||
return results;
|
||||
};
|
||||
|
||||
let linuxDesktopEntriesCache = { expiresAt: 0, entries: null };
|
||||
|
||||
const getLinuxDesktopEntries = async () => {
|
||||
const now = Date.now();
|
||||
if (linuxDesktopEntriesCache.entries && linuxDesktopEntriesCache.expiresAt > now) {
|
||||
return linuxDesktopEntriesCache.entries;
|
||||
}
|
||||
const entries = await readLinuxDesktopEntries();
|
||||
linuxDesktopEntriesCache = { entries, expiresAt: now + LINUX_DESKTOP_ENTRIES_CACHE_TTL_MS };
|
||||
return entries;
|
||||
};
|
||||
|
||||
const buildPlatformInstalledApps = async (apps) => {
|
||||
if (process.platform === 'linux') {
|
||||
return buildLinuxInstalledApps(apps);
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
return buildWindowsInstalledApps(apps);
|
||||
}
|
||||
return buildInstalledApps(apps);
|
||||
};
|
||||
|
||||
const spawnDetachedLinux = (program, args) => new Promise((resolve, reject) => {
|
||||
const child = spawn(program, args, {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
});
|
||||
let settled = false;
|
||||
const finish = (callback, value) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
callback(value);
|
||||
};
|
||||
child.once('error', (error) => finish(reject, error));
|
||||
child.once('spawn', () => {
|
||||
child.unref();
|
||||
finish(resolve);
|
||||
});
|
||||
});
|
||||
|
||||
const runLinuxSpecChain = async (specs, appName) => {
|
||||
if (!Array.isArray(specs) || specs.length === 0) {
|
||||
throw new Error(`Failed to open in ${appName}: no launch candidates`);
|
||||
}
|
||||
|
||||
const failures = [];
|
||||
for (const spec of specs) {
|
||||
if (spec.kind === 'default') {
|
||||
if (spec.targetKind === 'file') {
|
||||
shell.showItemInFolder(spec.targetPath);
|
||||
return;
|
||||
}
|
||||
const errorMessage = await shell.openPath(spec.targetPath);
|
||||
if (!errorMessage) return;
|
||||
failures.push(`default opener: ${errorMessage}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await spawnDetachedLinux(spec.program, spec.args);
|
||||
return;
|
||||
} catch (error) {
|
||||
failures.push(`${spec.program}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
throw new Error(`Failed to open in ${appName}: ${failures.join('; ')}`);
|
||||
};
|
||||
|
||||
const parseSshConfigImports = () => {
|
||||
const sshConfigPath = path.join(os.homedir(), '.ssh', 'config');
|
||||
if (!fs.existsSync(sshConfigPath)) return [];
|
||||
@@ -3488,12 +3590,23 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
return APP_VERSION;
|
||||
|
||||
case 'desktop_get_launch_at_login': {
|
||||
if (process.platform === 'linux') {
|
||||
return { supported: true, enabled: await readLinuxAutostartEnabled() };
|
||||
}
|
||||
if (process.platform !== 'darwin' && process.platform !== 'win32') return { supported: false, enabled: false };
|
||||
const settings = app.getLoginItemSettings(getLoginItemOptions());
|
||||
return { supported: true, enabled: settings.openAtLogin === true };
|
||||
}
|
||||
|
||||
case 'desktop_set_launch_at_login': {
|
||||
if (process.platform === 'linux') {
|
||||
const enabled = args.enabled === true;
|
||||
return setLinuxAutostartEnabled({
|
||||
enabled,
|
||||
appName: app.getName(),
|
||||
backgroundArg: BACKGROUND_START_ARG,
|
||||
});
|
||||
}
|
||||
if (process.platform !== 'darwin' && process.platform !== 'win32') return { supported: false, enabled: false };
|
||||
const enabled = args.enabled === true;
|
||||
const settingsArgs = {
|
||||
@@ -3512,7 +3625,7 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
}
|
||||
|
||||
case 'desktop_set_minimize_to_tray': {
|
||||
if (process.platform !== 'win32') return { supported: false, enabled: false };
|
||||
if (process.platform !== 'win32' && process.platform !== 'linux') return { supported: false, enabled: false };
|
||||
const enabled = args.enabled === true;
|
||||
await mutateSettingsRoot((root) => {
|
||||
root.desktopMinimizeToTrayEnabled = enabled;
|
||||
@@ -3690,13 +3803,19 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
case 'desktop_open_path': {
|
||||
const targetPath = typeof args.path === 'string' ? args.path.trim() : '';
|
||||
const appName = typeof args.app === 'string' ? args.app.trim() : '';
|
||||
if (!targetPath) throw new Error('Path is required');
|
||||
const validated = await validateLocalPath(targetPath);
|
||||
if (process.platform === 'darwin') {
|
||||
const openArgs = appName ? ['-a', appName, targetPath] : [targetPath];
|
||||
const openArgs = appName ? ['-a', appName, validated.path] : [validated.path];
|
||||
spawn('open', openArgs, { detached: true, stdio: 'ignore' }).unref();
|
||||
return null;
|
||||
}
|
||||
await shell.openPath(targetPath);
|
||||
if (appName && process.platform !== 'linux' && process.platform !== 'win32') {
|
||||
throw new Error(unsupportedAppSpecificOpenError('paths'));
|
||||
}
|
||||
const errorMessage = await shell.openPath(validated.path);
|
||||
if (errorMessage) {
|
||||
throw new Error(`Failed to open path: ${errorMessage}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3714,18 +3833,16 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
}
|
||||
|
||||
case 'desktop_reveal_path': {
|
||||
const targetPath = typeof args.path === 'string' ? args.path.trim() : '';
|
||||
if (!targetPath) {
|
||||
throw new Error('Path is required');
|
||||
}
|
||||
|
||||
const stats = await fsp.stat(targetPath).catch(() => null);
|
||||
if (stats?.isDirectory()) {
|
||||
await shell.openPath(targetPath);
|
||||
const validated = await validateLocalPath(typeof args.path === 'string' ? args.path.trim() : '');
|
||||
if (validated.stats.isDirectory()) {
|
||||
const errorMessage = await shell.openPath(validated.path);
|
||||
if (errorMessage) {
|
||||
throw new Error(`Failed to reveal path: ${errorMessage}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
shell.showItemInFolder(targetPath);
|
||||
shell.showItemInFolder(validated.path);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3736,19 +3853,31 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
if (!projectPath || !appId || !appName) {
|
||||
throw new Error('Project path, app id, and app name are required');
|
||||
}
|
||||
const validated = await validateLocalPath(projectPath, 'Project path');
|
||||
if (process.platform === 'win32') {
|
||||
if (appId === 'finder') {
|
||||
const error = await shell.openPath(projectPath);
|
||||
const error = await shell.openPath(validated.path);
|
||||
if (error) throw new Error(error);
|
||||
return null;
|
||||
}
|
||||
runSpecChain(buildWindowsOpenProjectSpecs({ projectPath, appId, appName }), appName);
|
||||
runSpecChain(buildWindowsOpenProjectSpecs({ projectPath: validated.path, appId, appName }), appName);
|
||||
return null;
|
||||
}
|
||||
if (process.platform === 'linux') {
|
||||
const entries = await getLinuxDesktopEntries();
|
||||
await runLinuxSpecChain(buildLinuxOpenSpecs({
|
||||
targetPath: validated.path,
|
||||
appId,
|
||||
appName,
|
||||
targetKind: 'project',
|
||||
entries,
|
||||
}), appName);
|
||||
return null;
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
throw new Error('desktop_open_in_app is only supported on macOS and Windows');
|
||||
throw new Error(unsupportedAppSpecificOpenError('projects'));
|
||||
}
|
||||
runSpecChain(buildOpenProjectSpecs({ projectPath, appId, appName }), appName);
|
||||
runSpecChain(buildOpenProjectSpecs({ projectPath: validated.path, appId, appName }), appName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3759,14 +3888,26 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
if (!filePath || !appId || !appName) {
|
||||
throw new Error('File path, app id, and app name are required');
|
||||
}
|
||||
const validated = await validateLocalPath(filePath, 'File path');
|
||||
if (process.platform === 'win32') {
|
||||
runSpecChain(buildWindowsOpenFileSpecs({ filePath, appId, appName }), appName);
|
||||
runSpecChain(buildWindowsOpenFileSpecs({ filePath: validated.path, appId, appName }), appName);
|
||||
return null;
|
||||
}
|
||||
if (process.platform === 'linux') {
|
||||
const entries = await getLinuxDesktopEntries();
|
||||
await runLinuxSpecChain(buildLinuxOpenSpecs({
|
||||
targetPath: validated.path,
|
||||
appId,
|
||||
appName,
|
||||
targetKind: 'file',
|
||||
entries,
|
||||
}), appName);
|
||||
return null;
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
throw new Error('desktop_open_file_in_app is only supported on macOS and Windows');
|
||||
throw new Error(unsupportedAppSpecificOpenError('files'));
|
||||
}
|
||||
runSpecChain(buildOpenFileSpecs({ filePath, appId, appName }), appName);
|
||||
runSpecChain(buildOpenFileSpecs({ filePath: validated.path, appId, appName }), appName);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -3774,8 +3915,11 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
if (process.platform === 'win32') {
|
||||
return (await buildWindowsInstalledApps(args.apps)).map((app) => app.name);
|
||||
}
|
||||
if (process.platform === 'linux') {
|
||||
return filterLinuxInstalledApps(args.apps);
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
throw new Error('desktop_filter_installed_apps is only supported on macOS');
|
||||
throw new Error('desktop_filter_installed_apps is only supported on macOS, Windows, and Linux');
|
||||
}
|
||||
if (!Array.isArray(args.apps)) return [];
|
||||
const results = await Promise.all(
|
||||
@@ -3799,8 +3943,11 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
}
|
||||
return results;
|
||||
}
|
||||
if (process.platform === 'linux') {
|
||||
return fetchLinuxAppIcons(Array.isArray(args.apps) ? args.apps : []);
|
||||
}
|
||||
if (process.platform !== 'darwin') {
|
||||
throw new Error('desktop_fetch_app_icons is only supported on macOS');
|
||||
throw new Error('desktop_fetch_app_icons is only supported on macOS, Windows, and Linux');
|
||||
}
|
||||
const names = Array.isArray(args.apps) ? args.apps : [];
|
||||
const results = [];
|
||||
@@ -3825,14 +3972,12 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
const hasCache = Boolean(cache);
|
||||
const isCacheStale = !cache || (now - Number(cache.updatedAt || 0)) > INSTALLED_APPS_CACHE_TTL_SECS;
|
||||
const refresh = async () => {
|
||||
const apps = process.platform === 'win32'
|
||||
? await buildWindowsInstalledApps(args.apps)
|
||||
: await buildInstalledApps(Array.isArray(args.apps) ? args.apps : []);
|
||||
const apps = await buildPlatformInstalledApps(Array.isArray(args.apps) ? args.apps : []);
|
||||
await fsp.mkdir(path.dirname(cachePath), { recursive: true });
|
||||
await fsp.writeFile(cachePath, JSON.stringify({ updatedAt: now, apps }, null, 2));
|
||||
emitToAllWindows('openchamber:installed-apps-updated', apps);
|
||||
};
|
||||
if (process.platform !== 'darwin' && process.platform !== 'win32') {
|
||||
if (process.platform !== 'darwin' && process.platform !== 'win32' && process.platform !== 'linux') {
|
||||
return { apps: [], hasCache: false, isCacheStale: false, supported: false };
|
||||
}
|
||||
if (!hasCache || isCacheStale || args.force === true) {
|
||||
@@ -3862,6 +4007,7 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
||||
localAvailable: Boolean(state.sidecarUrl || state.localOrigin),
|
||||
});
|
||||
state.initScript = buildInitScript(state.localOrigin, state.bootOutcome, state.apiBaseUrl, state.clientToken, state.requestHeaders || {});
|
||||
syncInitScriptToWindows(state.initScript);
|
||||
log.info('[electron] hosts config updated, recomputed bootOutcome', state.bootOutcome);
|
||||
return null;
|
||||
}
|
||||
@@ -4662,8 +4808,10 @@ const resolveTraySurface = () => {
|
||||
const trayIconAssets = () => {
|
||||
const dir = path.join(resourceRoot(), 'icons', 'tray');
|
||||
const statusDir = path.join(dir, 'status');
|
||||
if (process.platform === 'win32') {
|
||||
const iconPath = getWindowIconPath() || path.join(resourceRoot(), 'icons', 'icon.ico');
|
||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||
const iconPath = process.platform === 'linux'
|
||||
? (getWindowIconPath() || path.join(resourceRoot(), 'icons', 'icon.png'))
|
||||
: (getWindowIconPath() || path.join(resourceRoot(), 'icons', 'icon.ico'));
|
||||
return {
|
||||
idleIconPath: iconPath,
|
||||
unseenIconPath: iconPath,
|
||||
@@ -4695,7 +4843,7 @@ const trayIconAssets = () => {
|
||||
};
|
||||
|
||||
const setupTray = () => {
|
||||
if (!['darwin', 'win32'].includes(process.platform) || state.trayController) return;
|
||||
if (!['darwin', 'win32', 'linux'].includes(process.platform) || state.trayController) return;
|
||||
if (process.platform === 'darwin' && readSettingsRoot().desktopMacMenuBarEnabled === false) return;
|
||||
const assets = trayIconAssets();
|
||||
if (!fs.existsSync(assets.idleIconPath)) {
|
||||
@@ -4920,6 +5068,21 @@ app.whenReady().then(async () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (process.platform === 'linux' && app.isPackaged) {
|
||||
try {
|
||||
const enabled = await readLinuxAutostartEnabled();
|
||||
if (enabled) {
|
||||
await setLinuxAutostartEnabled({
|
||||
enabled: true,
|
||||
appName: app.getName(),
|
||||
backgroundArg: BACKGROUND_START_ARG,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
log.warn('[electron] failed to reconcile Linux autostart entry', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBackgroundStart) {
|
||||
const { localOrigin, bootOutcome, apiBaseUrl, clientToken, requestHeaders } = await resolveInitialUrl();
|
||||
state.localOrigin = localOrigin;
|
||||
|
||||
Reference in New Issue
Block a user