mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-03 15:08:25 +00:00
134
electron/main.ts
134
electron/main.ts
@@ -171,6 +171,118 @@ const AUTO_UPDATE_ENABLED =
|
||||
process.env.AUTO_UPDATE_ENABLED === '1' ||
|
||||
(process.env.AUTO_UPDATE_ENABLED == null && !process.env.VITE_DEV_SERVER_URL)
|
||||
|
||||
const getLaunchAtStartupUnsupportedReason = (): string | null => {
|
||||
if (process.platform !== 'win32' && process.platform !== 'darwin') {
|
||||
return '当前平台暂不支持开机自启动'
|
||||
}
|
||||
if (!app.isPackaged) {
|
||||
return '仅安装后的 Windows / macOS 版本支持开机自启动'
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const isLaunchAtStartupSupported = (): boolean => getLaunchAtStartupUnsupportedReason() == null
|
||||
|
||||
const getStoredLaunchAtStartupPreference = (): boolean | undefined => {
|
||||
const value = configService?.get('launchAtStartup')
|
||||
return typeof value === 'boolean' ? value : undefined
|
||||
}
|
||||
|
||||
const getSystemLaunchAtStartup = (): boolean => {
|
||||
if (!isLaunchAtStartupSupported()) return false
|
||||
try {
|
||||
return app.getLoginItemSettings().openAtLogin === true
|
||||
} catch (error) {
|
||||
console.error('[WeFlow] 读取开机自启动状态失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const buildLaunchAtStartupSettings = (enabled: boolean): Parameters<typeof app.setLoginItemSettings>[0] =>
|
||||
process.platform === 'win32'
|
||||
? { openAtLogin: enabled, path: process.execPath }
|
||||
: { openAtLogin: enabled }
|
||||
|
||||
const setSystemLaunchAtStartup = (enabled: boolean): { success: boolean; enabled: boolean; error?: string } => {
|
||||
try {
|
||||
app.setLoginItemSettings(buildLaunchAtStartupSettings(enabled))
|
||||
const effectiveEnabled = app.getLoginItemSettings().openAtLogin === true
|
||||
if (effectiveEnabled !== enabled) {
|
||||
return {
|
||||
success: false,
|
||||
enabled: effectiveEnabled,
|
||||
error: '系统未接受该开机自启动设置'
|
||||
}
|
||||
}
|
||||
return { success: true, enabled: effectiveEnabled }
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
enabled: getSystemLaunchAtStartup(),
|
||||
error: `设置开机自启动失败: ${String((error as Error)?.message || error)}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getLaunchAtStartupStatus = (): { enabled: boolean; supported: boolean; reason?: string } => {
|
||||
const unsupportedReason = getLaunchAtStartupUnsupportedReason()
|
||||
if (unsupportedReason) {
|
||||
return {
|
||||
enabled: getStoredLaunchAtStartupPreference() === true,
|
||||
supported: false,
|
||||
reason: unsupportedReason
|
||||
}
|
||||
}
|
||||
return {
|
||||
enabled: getSystemLaunchAtStartup(),
|
||||
supported: true
|
||||
}
|
||||
}
|
||||
|
||||
const applyLaunchAtStartupPreference = (
|
||||
enabled: boolean
|
||||
): { success: boolean; enabled: boolean; supported: boolean; reason?: string; error?: string } => {
|
||||
const unsupportedReason = getLaunchAtStartupUnsupportedReason()
|
||||
if (unsupportedReason) {
|
||||
return {
|
||||
success: false,
|
||||
enabled: getStoredLaunchAtStartupPreference() === true,
|
||||
supported: false,
|
||||
reason: unsupportedReason
|
||||
}
|
||||
}
|
||||
|
||||
const result = setSystemLaunchAtStartup(enabled)
|
||||
configService?.set('launchAtStartup', result.enabled)
|
||||
return {
|
||||
...result,
|
||||
supported: true
|
||||
}
|
||||
}
|
||||
|
||||
const syncLaunchAtStartupPreference = () => {
|
||||
if (!configService) return
|
||||
|
||||
const unsupportedReason = getLaunchAtStartupUnsupportedReason()
|
||||
if (unsupportedReason) return
|
||||
|
||||
const storedPreference = getStoredLaunchAtStartupPreference()
|
||||
const systemEnabled = getSystemLaunchAtStartup()
|
||||
|
||||
if (typeof storedPreference !== 'boolean') {
|
||||
configService.set('launchAtStartup', systemEnabled)
|
||||
return
|
||||
}
|
||||
|
||||
if (storedPreference === systemEnabled) return
|
||||
|
||||
const result = setSystemLaunchAtStartup(storedPreference)
|
||||
configService.set('launchAtStartup', result.enabled)
|
||||
if (!result.success && result.error) {
|
||||
console.error('[WeFlow] 同步开机自启动设置失败:', result.error)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用白名单过滤 PATH,避免被第三方目录中的旧版 VC++ 运行库劫持。
|
||||
// 仅保留系统目录(Windows/System32/SysWOW64)和应用自身目录(可执行目录、resources)。
|
||||
function sanitizePathEnv() {
|
||||
@@ -1250,7 +1362,12 @@ function registerIpcHandlers() {
|
||||
})
|
||||
|
||||
ipcMain.handle('config:set', async (_, key: string, value: any) => {
|
||||
const result = configService?.set(key as any, value)
|
||||
let result: unknown
|
||||
if (key === 'launchAtStartup') {
|
||||
result = applyLaunchAtStartupPreference(value === true)
|
||||
} else {
|
||||
result = configService?.set(key as any, value)
|
||||
}
|
||||
if (key === 'updateChannel') {
|
||||
applyAutoUpdateChannel('settings')
|
||||
}
|
||||
@@ -1259,6 +1376,12 @@ function registerIpcHandlers() {
|
||||
})
|
||||
|
||||
ipcMain.handle('config:clear', async () => {
|
||||
if (isLaunchAtStartupSupported() && getSystemLaunchAtStartup()) {
|
||||
const result = setSystemLaunchAtStartup(false)
|
||||
if (!result.success && result.error) {
|
||||
console.error('[WeFlow] 清空配置时关闭开机自启动失败:', result.error)
|
||||
}
|
||||
}
|
||||
configService?.clear()
|
||||
messagePushService.handleConfigCleared()
|
||||
return true
|
||||
@@ -1301,6 +1424,14 @@ function registerIpcHandlers() {
|
||||
return app.getVersion()
|
||||
})
|
||||
|
||||
ipcMain.handle('app:getLaunchAtStartupStatus', async () => {
|
||||
return getLaunchAtStartupStatus()
|
||||
})
|
||||
|
||||
ipcMain.handle('app:setLaunchAtStartup', async (_, enabled: boolean) => {
|
||||
return applyLaunchAtStartupPreference(enabled === true)
|
||||
})
|
||||
|
||||
ipcMain.handle('app:checkWayland', async () => {
|
||||
if (process.platform !== 'linux') return false;
|
||||
|
||||
@@ -2881,6 +3012,7 @@ app.whenReady().then(async () => {
|
||||
updateSplashProgress(5, '正在加载配置...')
|
||||
configService = new ConfigService()
|
||||
applyAutoUpdateChannel('startup')
|
||||
syncLaunchAtStartupPreference()
|
||||
|
||||
// 将用户主题配置推送给 Splash 窗口
|
||||
if (splashWindow && !splashWindow.isDestroyed()) {
|
||||
|
||||
@@ -53,6 +53,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
app: {
|
||||
getDownloadsPath: () => ipcRenderer.invoke('app:getDownloadsPath'),
|
||||
getVersion: () => ipcRenderer.invoke('app:getVersion'),
|
||||
getLaunchAtStartupStatus: () => ipcRenderer.invoke('app:getLaunchAtStartupStatus'),
|
||||
setLaunchAtStartup: (enabled: boolean) => ipcRenderer.invoke('app:setLaunchAtStartup', enabled),
|
||||
checkForUpdates: () => ipcRenderer.invoke('app:checkForUpdates'),
|
||||
downloadAndInstall: () => ipcRenderer.invoke('app:downloadAndInstall'),
|
||||
ignoreUpdate: (version: string) => ipcRenderer.invoke('app:ignoreUpdate', version),
|
||||
|
||||
@@ -27,6 +27,7 @@ interface ConfigSchema {
|
||||
themeId: string
|
||||
language: string
|
||||
logEnabled: boolean
|
||||
launchAtStartup?: boolean
|
||||
llmModelPath: string
|
||||
whisperModelName: string
|
||||
whisperModelDir: string
|
||||
|
||||
@@ -138,6 +138,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const [notificationPosition, setNotificationPosition] = useState<'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' | 'top-center'>('top-right')
|
||||
const [notificationFilterMode, setNotificationFilterMode] = useState<'all' | 'whitelist' | 'blacklist'>('all')
|
||||
const [notificationFilterList, setNotificationFilterList] = useState<string[]>([])
|
||||
const [launchAtStartup, setLaunchAtStartup] = useState(false)
|
||||
const [launchAtStartupSupported, setLaunchAtStartupSupported] = useState(isWindows || isMac)
|
||||
const [launchAtStartupReason, setLaunchAtStartupReason] = useState('')
|
||||
const [windowCloseBehavior, setWindowCloseBehavior] = useState<configService.WindowCloseBehavior>('ask')
|
||||
const [quoteLayout, setQuoteLayout] = useState<configService.QuoteLayout>('quote-top')
|
||||
const [updateChannel, setUpdateChannel] = useState<configService.UpdateChannel>('stable')
|
||||
@@ -162,6 +165,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const [isFetchingDbKey, setIsFetchingDbKey] = useState(false)
|
||||
const [isFetchingImageKey, setIsFetchingImageKey] = useState(false)
|
||||
const [isCheckingUpdate, setIsCheckingUpdate] = useState(false)
|
||||
const [isUpdatingLaunchAtStartup, setIsUpdatingLaunchAtStartup] = useState(false)
|
||||
const [appVersion, setAppVersion] = useState('')
|
||||
const [message, setMessage] = useState<{ text: string; success: boolean } | null>(null)
|
||||
const [showDecryptKey, setShowDecryptKey] = useState(false)
|
||||
@@ -337,6 +341,7 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
const savedNotificationFilterMode = await configService.getNotificationFilterMode()
|
||||
const savedNotificationFilterList = await configService.getNotificationFilterList()
|
||||
const savedMessagePushEnabled = await configService.getMessagePushEnabled()
|
||||
const savedLaunchAtStartupStatus = await window.electronAPI.app.getLaunchAtStartupStatus()
|
||||
const savedWindowCloseBehavior = await configService.getWindowCloseBehavior()
|
||||
const savedQuoteLayout = await configService.getQuoteLayout()
|
||||
const savedUpdateChannel = await configService.getUpdateChannel()
|
||||
@@ -386,6 +391,9 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
setNotificationFilterMode(savedNotificationFilterMode)
|
||||
setNotificationFilterList(savedNotificationFilterList)
|
||||
setMessagePushEnabled(savedMessagePushEnabled)
|
||||
setLaunchAtStartup(savedLaunchAtStartupStatus.enabled)
|
||||
setLaunchAtStartupSupported(savedLaunchAtStartupStatus.supported)
|
||||
setLaunchAtStartupReason(savedLaunchAtStartupStatus.reason || '')
|
||||
setWindowCloseBehavior(savedWindowCloseBehavior)
|
||||
setQuoteLayout(savedQuoteLayout)
|
||||
if (savedUpdateChannel) {
|
||||
@@ -428,6 +436,29 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
|
||||
|
||||
|
||||
const handleLaunchAtStartupChange = async (enabled: boolean) => {
|
||||
if (isUpdatingLaunchAtStartup) return
|
||||
|
||||
try {
|
||||
setIsUpdatingLaunchAtStartup(true)
|
||||
const result = await window.electronAPI.app.setLaunchAtStartup(enabled)
|
||||
setLaunchAtStartup(result.enabled)
|
||||
setLaunchAtStartupSupported(result.supported)
|
||||
setLaunchAtStartupReason(result.reason || '')
|
||||
|
||||
if (result.success) {
|
||||
showMessage(enabled ? '已开启开机自启动' : '已关闭开机自启动', true)
|
||||
return
|
||||
}
|
||||
|
||||
showMessage(result.error || result.reason || '设置开机自启动失败', false)
|
||||
} catch (e: any) {
|
||||
showMessage(`设置开机自启动失败: ${e?.message || String(e)}`, false)
|
||||
} finally {
|
||||
setIsUpdatingLaunchAtStartup(false)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshWhisperStatus = async (modelDirValue = whisperModelDir) => {
|
||||
try {
|
||||
const result = await window.electronAPI.whisper?.getModelStatus()
|
||||
@@ -1199,6 +1230,39 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
|
||||
|
||||
<div className="divider" />
|
||||
|
||||
<div className="form-group">
|
||||
<label>开机自启动</label>
|
||||
<span className="form-hint">
|
||||
{launchAtStartupSupported
|
||||
? '开启后,登录系统时会自动启动 WeFlow。'
|
||||
: launchAtStartupReason || '当前环境暂不支持开机自启动。'}
|
||||
</span>
|
||||
<div className="log-toggle-line">
|
||||
<span className="log-status">
|
||||
{isUpdatingLaunchAtStartup
|
||||
? '保存中...'
|
||||
: launchAtStartupSupported
|
||||
? (launchAtStartup ? '已开启' : '已关闭')
|
||||
: '当前不可用'}
|
||||
</span>
|
||||
<label className="switch" htmlFor="launch-at-startup-toggle">
|
||||
<input
|
||||
id="launch-at-startup-toggle"
|
||||
className="switch-input"
|
||||
type="checkbox"
|
||||
checked={launchAtStartup}
|
||||
disabled={!launchAtStartupSupported || isUpdatingLaunchAtStartup}
|
||||
onChange={(e) => {
|
||||
void handleLaunchAtStartupChange(e.target.checked)
|
||||
}}
|
||||
/>
|
||||
<span className="switch-slider" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="divider" />
|
||||
|
||||
<div className="form-group">
|
||||
<label>关闭主窗口时</label>
|
||||
<span className="form-hint">设置点击关闭按钮后的默认行为;选择“每次询问”时会弹出关闭确认。</span>
|
||||
|
||||
@@ -13,6 +13,7 @@ export const CONFIG_KEYS = {
|
||||
LAST_SESSION: 'lastSession',
|
||||
WINDOW_BOUNDS: 'windowBounds',
|
||||
CACHE_PATH: 'cachePath',
|
||||
LAUNCH_AT_STARTUP: 'launchAtStartup',
|
||||
|
||||
EXPORT_PATH: 'exportPath',
|
||||
AGREEMENT_ACCEPTED: 'agreementAccepted',
|
||||
@@ -258,6 +259,18 @@ export async function setLogEnabled(enabled: boolean): Promise<void> {
|
||||
await config.set(CONFIG_KEYS.LOG_ENABLED, enabled)
|
||||
}
|
||||
|
||||
// 获取开机自启动偏好
|
||||
export async function getLaunchAtStartup(): Promise<boolean | null> {
|
||||
const value = await config.get(CONFIG_KEYS.LAUNCH_AT_STARTUP)
|
||||
if (typeof value === 'boolean') return value
|
||||
return null
|
||||
}
|
||||
|
||||
// 设置开机自启动偏好
|
||||
export async function setLaunchAtStartup(enabled: boolean): Promise<void> {
|
||||
await config.set(CONFIG_KEYS.LAUNCH_AT_STARTUP, enabled)
|
||||
}
|
||||
|
||||
// 获取 LLM 模型路径
|
||||
export async function getLlmModelPath(): Promise<string | null> {
|
||||
const value = await config.get(CONFIG_KEYS.LLM_MODEL_PATH)
|
||||
|
||||
8
src/types/electron.d.ts
vendored
8
src/types/electron.d.ts
vendored
@@ -56,6 +56,14 @@ export interface ElectronAPI {
|
||||
app: {
|
||||
getDownloadsPath: () => Promise<string>
|
||||
getVersion: () => Promise<string>
|
||||
getLaunchAtStartupStatus: () => Promise<{ enabled: boolean; supported: boolean; reason?: string }>
|
||||
setLaunchAtStartup: (enabled: boolean) => Promise<{
|
||||
success: boolean
|
||||
enabled: boolean
|
||||
supported: boolean
|
||||
reason?: string
|
||||
error?: string
|
||||
}>
|
||||
checkForUpdates: () => Promise<{ hasUpdate: boolean; version?: string; releaseNotes?: string }>
|
||||
downloadAndInstall: () => Promise<void>
|
||||
ignoreUpdate: (version: string) => Promise<{ success: boolean }>
|
||||
|
||||
Reference in New Issue
Block a user