diff --git a/src/main/core/manager.ts b/src/main/core/manager.ts index 590fb5b..101de38 100644 --- a/src/main/core/manager.ts +++ b/src/main/core/manager.ts @@ -18,7 +18,7 @@ import { patchControledMihomoConfig, manageSmartOverride } from '../config' -import { app, dialog, ipcMain, net } from 'electron' +import { app, ipcMain, net } from 'electron' import { startMihomoTraffic, startMihomoConnections, @@ -290,10 +290,6 @@ async function checkProfile(): Promise { } } -/** - * 检查TUN模式所需的权限 - * @returns Promise 是否有足够权限 - */ export async function checkTunPermissions(): Promise { const { core = 'mihomo' } = await getAppConfig() const corePath = mihomoCorePath(core) @@ -310,7 +306,6 @@ export async function checkTunPermissions(): Promise { } if (process.platform === 'darwin' || process.platform === 'linux') { - // Unix系统检查核心文件是否有setuid权限 const { stat } = await import('fs/promises') const stats = await stat(corePath) return (stats.mode & 0o4000) !== 0 && stats.uid === 0 @@ -322,9 +317,6 @@ export async function checkTunPermissions(): Promise { return false } -/** - * 为TUN模式获取必要的权限 - */ export async function grantTunPermissions(): Promise { const { core = 'mihomo' } = await getAppConfig() const corePath = mihomoCorePath(core) @@ -350,8 +342,6 @@ export async function grantTunPermissions(): Promise { } } -// Windows 管理员权限 - export async function checkAdminPrivileges(): Promise { if (process.platform !== 'win32') { return true @@ -366,7 +356,7 @@ export async function checkAdminPrivileges(): Promise { } } -export async function requestAdminPrivileges(): Promise { +export async function restartAsAdmin(): Promise { if (process.platform !== 'win32') { throw new Error('This function is only available on Windows') } @@ -376,27 +366,101 @@ export async function requestAdminPrivileges(): Promise { const args = process.argv.slice(1) try { + const restartArgs = [...args, '--admin-restart-for-tun'] const escapedExePath = exePath.replace(/'/g, "''") - const escapedArgs = args.map(arg => `'${arg.replace(/'/g, "''")}'`).join(', ') + const escapedArgs = restartArgs.map(arg => `'${arg.replace(/'/g, "''")}'`).join(', ') let command: string - if (args.length > 0) { + if (restartArgs.length > 0) { command = `powershell -WindowStyle Hidden -Command "Start-Process -FilePath '${escapedExePath}' -ArgumentList ${escapedArgs} -Verb RunAs"` } else { command = `powershell -WindowStyle Hidden -Command "Start-Process -FilePath '${escapedExePath}' -Verb RunAs"` } - console.log('Requesting admin privileges with command:', command) - + console.log('Restarting as administrator with command:', command) await execPromise(command, { windowsHide: true }) - - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise(resolve => setTimeout(resolve, 1500)) const { app } = await import('electron') app.quit() } catch (error) { - console.error('Admin privilege request failed:', error) - throw new Error(`Failed to request admin privileges: ${error}`) + console.error('Failed to restart as administrator:', error) + throw new Error(`Failed to restart as administrator: ${error}`) + } +} + + + +export async function checkMihomoCorePermissions(): Promise { + const { core = 'mihomo' } = await getAppConfig() + const corePath = mihomoCorePath(core) + + try { + if (process.platform === 'win32') { + // Windows权限检查 + return await checkAdminPrivileges() + } + + if (process.platform === 'darwin' || process.platform === 'linux') { + const { stat } = await import('fs/promises') + const stats = await stat(corePath) + return (stats.mode & 0o4000) !== 0 && stats.uid === 0 + } + } catch { + return false + } + + return false +} + +// TUN模式获取权限 +export async function requestTunPermissions(): Promise { + if (process.platform === 'win32') { + await restartAsAdmin() + } else { + const hasPermissions = await checkMihomoCorePermissions() + if (!hasPermissions) { + await grantTunPermissions() + } + } +} + +export async function checkAdminRestartForTun(): Promise { + if (process.argv.includes('--admin-restart-for-tun')) { + console.log('Detected admin restart for TUN mode, auto-enabling TUN...') + + try { + if (process.platform === 'win32') { + const hasAdminPrivileges = await checkAdminPrivileges() + if (hasAdminPrivileges) { + await patchControledMihomoConfig({ tun: { enable: true }, dns: { enable: true } }) + await restartCore() + + console.log('TUN mode auto-enabled after admin restart') + + const { mainWindow } = await import('../index') + mainWindow?.webContents.send('controledMihomoConfigUpdated') + ipcMain.emit('updateTrayMenu') + } else { + console.warn('Admin restart detected but no admin privileges found') + } + } + } catch (error) { + console.error('Failed to auto-enable TUN after admin restart:', error) + } + } else if (process.platform === 'win32') { + try { + const hasAdminPrivileges = await checkAdminPrivileges() + const { tun } = await getControledMihomoConfig() + + if (hasAdminPrivileges && !tun?.enable) { + console.log('Running with admin privileges but TUN is disabled') + const { mainWindow } = await import('../index') + mainWindow?.webContents.send('adminPrivilegesDetected', { tunEnabled: false }) + } + } catch (error) { + console.error('Failed to check admin privileges on startup:', error) + } } } diff --git a/src/main/index.ts b/src/main/index.ts index e578a91..cb3d1b8 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -3,7 +3,7 @@ import { registerIpcMainHandlers } from './utils/ipc' import windowStateKeeper from 'electron-window-state' import { app, shell, BrowserWindow, Menu, dialog, Notification, powerMonitor } from 'electron' import { addProfileItem, getAppConfig, patchAppConfig } from './config' -import { quitWithoutCore, startCore, stopCore } from './core/manager' +import { quitWithoutCore, startCore, stopCore, checkAdminRestartForTun } from './core/manager' import { triggerSysProxy } from './sys/sysproxy' import icon from '../../resources/icon.png?asset' import { createTray, hideDockIcon, showDockIcon } from './resolve/tray' @@ -173,6 +173,8 @@ app.whenReady().then(async () => { const [startPromise] = await startCore() startPromise.then(async () => { await initProfileUpdater() + // 上次是否为了开启 TUN 而重启 + await checkAdminRestartForTun() }) } catch (e) { showSafeErrorBox('mihomo.error.coreStartFailed', `${e}`) diff --git a/src/main/resolve/tray.ts b/src/main/resolve/tray.ts index be1740a..4446c5d 100644 --- a/src/main/resolve/tray.ts +++ b/src/main/resolve/tray.ts @@ -19,7 +19,7 @@ import { mainWindow, showMainWindow, triggerMainWindow } from '..' import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron' import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs' import { triggerSysProxy } from '../sys/sysproxy' -import { quitWithoutCore, restartCore, checkTunPermissions, grantTunPermissions, checkAdminPrivileges, requestAdminPrivileges } from '../core/manager' +import { quitWithoutCore, restartCore, checkMihomoCorePermissions, requestTunPermissions, restartAsAdmin } from '../core/manager' import { floatingWindow, triggerFloatingWindow } from './floatingWindow' import { t } from 'i18next' @@ -178,42 +178,33 @@ export const buildContextMenu = async (): Promise => { const enable = item.checked try { if (enable) { - // Windows 管理员权限 - if (process.platform === 'win32') { - try { - const isAdmin = await checkAdminPrivileges() - if (!isAdmin) { - try { - await requestAdminPrivileges() - return - } catch (error) { - console.error('Failed to request admin privileges:', error) - item.checked = false - ipcMain.emit('updateTrayMenu') - return - } - } - } catch (error) { - console.warn('Admin check failed:', error) - } - } + // 检查权限 + try { + const hasPermissions = await checkMihomoCorePermissions() - if (process.platform !== 'win32') { - try { - const hasPermissions = await checkTunPermissions() - if (!hasPermissions) { + if (!hasPermissions) { + if (process.platform === 'win32') { try { - await grantTunPermissions() + await restartAsAdmin() } catch (error) { - console.error('Failed to grant TUN permissions:', error) + console.error('Failed to restart as admin from tray:', error) + item.checked = false + ipcMain.emit('updateTrayMenu') + return + } + } else { + try { + await requestTunPermissions() + } catch (error) { + console.error('Failed to grant TUN permissions from tray:', error) item.checked = false ipcMain.emit('updateTrayMenu') return } } - } catch (error) { - console.warn('Permission check failed:', error) } + } catch (error) { + console.warn('Permission check failed in tray:', error) } await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } }) diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index 15b5c95..55a0e41 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -63,7 +63,9 @@ import { grantTunPermissions, manualGrantCorePermition, checkAdminPrivileges, - requestAdminPrivileges + restartAsAdmin, + checkMihomoCorePermissions, + requestTunPermissions } from '../core/manager' import { triggerSysProxy } from '../sys/sysproxy' import { checkUpdate, downloadAndInstallUpdate } from '../resolve/autoUpdater' @@ -195,7 +197,9 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('triggerSysProxy', (_e, enable) => ipcErrorWrapper(triggerSysProxy)(enable)) ipcMain.handle('manualGrantCorePermition', () => ipcErrorWrapper(manualGrantCorePermition)()) ipcMain.handle('checkAdminPrivileges', () => ipcErrorWrapper(checkAdminPrivileges)()) - ipcMain.handle('requestAdminPrivileges', () => ipcErrorWrapper(requestAdminPrivileges)()) + ipcMain.handle('restartAsAdmin', () => ipcErrorWrapper(restartAsAdmin)()) + ipcMain.handle('checkMihomoCorePermissions', () => ipcErrorWrapper(checkMihomoCorePermissions)()) + ipcMain.handle('requestTunPermissions', () => ipcErrorWrapper(requestTunPermissions)()) ipcMain.handle('checkTunPermissions', () => ipcErrorWrapper(checkTunPermissions)()) ipcMain.handle('grantTunPermissions', () => ipcErrorWrapper(grantTunPermissions)()) diff --git a/src/renderer/src/components/sider/tun-switcher.tsx b/src/renderer/src/components/sider/tun-switcher.tsx index 88b6d54..7312758 100644 --- a/src/renderer/src/components/sider/tun-switcher.tsx +++ b/src/renderer/src/components/sider/tun-switcher.tsx @@ -38,40 +38,40 @@ const TunSwitcher: React.FC = (props) => { const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null const onChange = async (enable: boolean): Promise => { if (enable) { - // Windows下检查管理员权限 - if (window.electron.process.platform === 'win32') { - try { - const isAdmin = await window.electron.ipcRenderer.invoke('checkAdminPrivileges') - if (!isAdmin) { + try { + // 检查内核权限 + const hasPermissions = await window.electron.ipcRenderer.invoke('checkMihomoCorePermissions') + + if (!hasPermissions) { + if (window.electron.process.platform === 'win32') { const confirmed = confirm(t('tun.permissions.required')) if (confirmed) { try { - const notification = new Notification(t('tun.permissions.requesting')) - - await window.electron.ipcRenderer.invoke('requestAdminPrivileges') + const notification = new Notification(t('tun.permissions.restarting')) + await window.electron.ipcRenderer.invoke('restartAsAdmin') notification.close() return } catch (error) { - console.error('Failed to request admin privileges:', error) + console.error('Failed to restart as admin:', error) alert(t('tun.permissions.failed') + ': ' + error) return } } else { return } + } else { + // macOS/Linux下尝试自动获取权限 + try { + await window.electron.ipcRenderer.invoke('requestTunPermissions') + } catch (error) { + console.warn('Permission grant failed:', error) + alert(t('tun.permissions.failed') + ': ' + error) + return + } } - } catch (error) { - console.warn('Admin check failed:', error) - } - } - - // macOS/Linux 获取权限 - if (window.electron.process.platform !== 'win32') { - try { - await window.electron.ipcRenderer.invoke('manualGrantCorePermition') - } catch (error) { - console.warn('Permission grant failed:', error) } + } catch (error) { + console.warn('Permission check failed:', error) } await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } }) diff --git a/src/renderer/src/locales/en-US.json b/src/renderer/src/locales/en-US.json index 87023a9..dcbb164 100644 --- a/src/renderer/src/locales/en-US.json +++ b/src/renderer/src/locales/en-US.json @@ -316,10 +316,11 @@ "tun.notifications.coreAuthSuccess": "Core Authorization Successful", "tun.notifications.firewallResetSuccess": "Firewall Reset Successful", "tun.error.tunPermissionDenied": "TUN interface start failed, please try to manually grant core permissions", - "tun.permissions.required": "TUN mode requires administrator privileges. Grant permissions now?", + "tun.permissions.required": "TUN mode requires administrator privileges. Restart the application now to get permissions?", "tun.permissions.failed": "Permission authorization failed", "tun.permissions.windowsRestart": "On Windows, you need to restart the application as administrator to use TUN mode", "tun.permissions.requesting": "Requesting administrator privileges, please click 'Yes' in the UAC dialog...", + "tun.permissions.restarting": "Restarting application with administrator privileges, please click 'Yes' in the UAC dialog...", "dns.title": "DNS Settings", "dns.enable": "Enable DNS", "dns.enhancedMode.title": "Domain Mapping Mode", diff --git a/src/renderer/src/locales/zh-CN.json b/src/renderer/src/locales/zh-CN.json index 594a35b..cafad9a 100644 --- a/src/renderer/src/locales/zh-CN.json +++ b/src/renderer/src/locales/zh-CN.json @@ -316,10 +316,11 @@ "tun.notifications.coreAuthSuccess": "内核授权成功", "tun.notifications.firewallResetSuccess": "防火墙重设成功", "tun.error.tunPermissionDenied": "虚拟网卡启动失败,请尝试手动授予内核权限", - "tun.permissions.required": "启用TUN模式需要管理员权限,是否现在授权?", + "tun.permissions.required": "启用TUN模式需要管理员权限,是否现在重启应用获取权限?", "tun.permissions.failed": "权限授权失败", "tun.permissions.windowsRestart": "Windows下需要以管理员身份重新启动应用才能使用TUN模式", "tun.permissions.requesting": "正在请求管理员权限,请在UAC对话框中点击'是'...", + "tun.permissions.restarting": "正在以管理员权限重启应用,请在UAC对话框中点击'是'...", "dns.title": "DNS 设置", "dns.enable": "启用 DNS", "dns.enhancedMode.title": "域名映射模式",