feat(setttings): support auto launch config (#415)
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Haze <hazeone@users.noreply.github.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import { ensureClawXContext, repairClawXOnlyBootstrapFiles } from '../utils/open
|
||||
import { autoInstallCliIfNeeded, generateCompletionCache, installCompletionToProfile } from '../utils/openclaw-cli';
|
||||
import { isQuitting, setQuitting } from './app-state';
|
||||
import { applyProxySettings } from './proxy';
|
||||
import { syncLaunchAtStartupSettingFromStore } from './launch-at-startup';
|
||||
import { getSetting } from '../utils/store';
|
||||
import { ensureBuiltinSkillsInstalled, ensurePreinstalledSkillsInstalled } from '../utils/skill-config';
|
||||
import { startHostApiServer } from '../api/server';
|
||||
@@ -162,6 +163,7 @@ async function initialize(): Promise<void> {
|
||||
|
||||
// Apply persisted proxy settings before creating windows or network requests.
|
||||
await applyProxySettings();
|
||||
await syncLaunchAtStartupSettingFromStore();
|
||||
|
||||
// Set application menu
|
||||
createMenu();
|
||||
|
||||
@@ -37,6 +37,7 @@ import { getProviderConfig } from '../utils/provider-registry';
|
||||
import { deviceOAuthManager, OAuthProviderType } from '../utils/device-oauth';
|
||||
import { browserOAuthManager, type BrowserOAuthProviderType } from '../utils/browser-oauth';
|
||||
import { applyProxySettings } from './proxy';
|
||||
import { syncLaunchAtStartupSettingFromStore } from './launch-at-startup';
|
||||
import { proxyAwareFetch } from '../utils/proxy-fetch';
|
||||
import { getRecentTokenUsageHistory } from '../utils/token-usage';
|
||||
import { getProviderService } from '../services/providers/provider-service';
|
||||
@@ -224,6 +225,10 @@ function isProxyKey(key: keyof AppSettings): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isLaunchAtStartupKey(key: keyof AppSettings): boolean {
|
||||
return key === 'launchAtStartup';
|
||||
}
|
||||
|
||||
function registerUnifiedRequestHandlers(gatewayManager: GatewayManager): void {
|
||||
const providerService = getProviderService();
|
||||
const handleProxySettingsChange = async () => {
|
||||
@@ -694,6 +699,9 @@ function registerUnifiedRequestHandlers(gatewayManager: GatewayManager): void {
|
||||
if (isProxyKey(key)) {
|
||||
await handleProxySettingsChange();
|
||||
}
|
||||
if (isLaunchAtStartupKey(key)) {
|
||||
await syncLaunchAtStartupSettingFromStore();
|
||||
}
|
||||
data = { success: true };
|
||||
break;
|
||||
}
|
||||
@@ -706,6 +714,9 @@ function registerUnifiedRequestHandlers(gatewayManager: GatewayManager): void {
|
||||
if (entries.some(([key]) => isProxyKey(key))) {
|
||||
await handleProxySettingsChange();
|
||||
}
|
||||
if (entries.some(([key]) => isLaunchAtStartupKey(key))) {
|
||||
await syncLaunchAtStartupSettingFromStore();
|
||||
}
|
||||
data = { success: true };
|
||||
break;
|
||||
}
|
||||
@@ -713,6 +724,7 @@ function registerUnifiedRequestHandlers(gatewayManager: GatewayManager): void {
|
||||
await resetSettings();
|
||||
const settings = await getAllSettings();
|
||||
await handleProxySettingsChange();
|
||||
await syncLaunchAtStartupSettingFromStore();
|
||||
data = { success: true, settings };
|
||||
break;
|
||||
}
|
||||
@@ -2239,6 +2251,9 @@ function registerSettingsHandlers(gatewayManager: GatewayManager): void {
|
||||
) {
|
||||
await handleProxySettingsChange();
|
||||
}
|
||||
if (key === 'launchAtStartup') {
|
||||
await syncLaunchAtStartupSettingFromStore();
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
});
|
||||
@@ -2259,6 +2274,9 @@ function registerSettingsHandlers(gatewayManager: GatewayManager): void {
|
||||
)) {
|
||||
await handleProxySettingsChange();
|
||||
}
|
||||
if (entries.some(([key]) => key === 'launchAtStartup')) {
|
||||
await syncLaunchAtStartupSettingFromStore();
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
});
|
||||
@@ -2267,6 +2285,7 @@ function registerSettingsHandlers(gatewayManager: GatewayManager): void {
|
||||
await resetSettings();
|
||||
const settings = await getAllSettings();
|
||||
await handleProxySettingsChange();
|
||||
await syncLaunchAtStartupSettingFromStore();
|
||||
return { success: true, settings };
|
||||
});
|
||||
}
|
||||
|
||||
85
electron/main/launch-at-startup.ts
Normal file
85
electron/main/launch-at-startup.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { app } from 'electron';
|
||||
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { logger } from '../utils/logger';
|
||||
import { getSetting } from '../utils/store';
|
||||
|
||||
const LINUX_AUTOSTART_FILE = join('.config', 'autostart', 'clawx.desktop');
|
||||
|
||||
function quoteDesktopArg(value: string): string {
|
||||
if (!value) return '""';
|
||||
const escaped = value.replace(/(["\\`$])/g, '\\$1');
|
||||
if (/[\s"'\\`$]/.test(value)) {
|
||||
return `"${escaped}"`;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function getLinuxExecCommand(): string {
|
||||
if (app.isPackaged) {
|
||||
return quoteDesktopArg(process.execPath);
|
||||
}
|
||||
|
||||
const launchArgs = process.argv.slice(1).filter(Boolean);
|
||||
const cmdParts = [process.execPath, ...launchArgs].map(quoteDesktopArg);
|
||||
return cmdParts.join(' ');
|
||||
}
|
||||
|
||||
function getLinuxDesktopEntry(): string {
|
||||
return [
|
||||
'[Desktop Entry]',
|
||||
'Type=Application',
|
||||
'Version=1.0',
|
||||
'Name=ClawX',
|
||||
'Comment=ClawX - AI Assistant',
|
||||
`Exec=${getLinuxExecCommand()}`,
|
||||
'Terminal=false',
|
||||
'Categories=Utility;',
|
||||
'X-GNOME-Autostart-enabled=true',
|
||||
'',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
async function applyLinuxLaunchAtStartup(enabled: boolean): Promise<void> {
|
||||
const targetPath = join(app.getPath('home'), LINUX_AUTOSTART_FILE);
|
||||
if (enabled) {
|
||||
await mkdir(dirname(targetPath), { recursive: true });
|
||||
await writeFile(targetPath, getLinuxDesktopEntry(), 'utf8');
|
||||
logger.info(`Launch-at-startup enabled via desktop entry: ${targetPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await rm(targetPath, { force: true });
|
||||
logger.info(`Launch-at-startup disabled and desktop entry removed: ${targetPath}`);
|
||||
}
|
||||
|
||||
function applyWindowsOrMacLaunchAtStartup(enabled: boolean): void {
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: enabled,
|
||||
openAsHidden: false,
|
||||
});
|
||||
logger.info(`Launch-at-startup ${enabled ? 'enabled' : 'disabled'} via login items`);
|
||||
}
|
||||
|
||||
export async function applyLaunchAtStartupSetting(enabled: boolean): Promise<void> {
|
||||
try {
|
||||
if (process.platform === 'linux') {
|
||||
await applyLinuxLaunchAtStartup(enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32' || process.platform === 'darwin') {
|
||||
applyWindowsOrMacLaunchAtStartup(enabled);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn(`Launch-at-startup unsupported on platform: ${process.platform}`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to apply launch-at-startup=${enabled}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncLaunchAtStartupSettingFromStore(): Promise<void> {
|
||||
const launchAtStartup = await getSetting('launchAtStartup');
|
||||
await applyLaunchAtStartupSetting(Boolean(launchAtStartup));
|
||||
}
|
||||
Reference in New Issue
Block a user