mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-04-13 08:00:30 +08:00
Compare commits
11 Commits
bc4b59c66b
...
c7190a311e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7190a311e | ||
|
|
87ccffef7e | ||
|
|
dd16eabb2a | ||
|
|
4a868e53ae | ||
|
|
9e0f27aea3 | ||
|
|
a28f576d78 | ||
|
|
2cfcf8be66 | ||
|
|
e21558ac37 | ||
|
|
228e2cbffc | ||
|
|
ab58248d7b | ||
|
|
295c4400e9 |
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@ -196,12 +196,9 @@ jobs:
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm add -D electron@22.3.27
|
||||
# Downgrade packages incompatible with Node.js 16 (Electron 22)
|
||||
pnpm add express@4.21.2 chokidar@3.6.0
|
||||
(Get-Content electron-builder.yml) -replace 'windows', 'win7' | Set-Content electron-builder.yml
|
||||
# Electron 22 requires CJS format
|
||||
(Get-Content package.json) -replace '"type": "module"', '"type": "commonjs"' | Set-Content package.json
|
||||
(Get-Content electron.vite.config.ts) -replace 'plugins: \[externalizeDepsPlugin\(\)\]', "plugins: [externalizeDepsPlugin()],`n build: { rollupOptions: { output: { format: 'cjs' } } }" | Set-Content electron.vite.config.ts
|
||||
- name: Update Version for Dev Build
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
@ -213,6 +210,7 @@ jobs:
|
||||
env:
|
||||
npm_config_arch: ${{ matrix.arch }}
|
||||
npm_config_target_arch: ${{ matrix.arch }}
|
||||
LEGACY_BUILD: 'true'
|
||||
run: pnpm build:win --${{ matrix.arch }}
|
||||
- name: Add Portable Flag
|
||||
run: |
|
||||
@ -590,7 +588,7 @@ jobs:
|
||||
- mihomo-party-bin
|
||||
- mihomo-party
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: linux
|
||||
needs: updater
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -601,14 +599,14 @@ jobs:
|
||||
- name: Update Checksums
|
||||
if: matrix.pkgname == 'mihomo-party' || matrix.pkgname == 'mihomo-party-electron'
|
||||
run: |
|
||||
wget https://github.com/mihomo-party-org/mihomo-party/archive/refs/tags/$(echo ${{ github.ref }} | tr -d 'refs/tags/').tar.gz -O release.tar.gz
|
||||
wget https://github.com/${{ github.repository }}/archive/refs/tags/$(echo ${{ github.ref }} | tr -d 'refs/tags/').tar.gz -O release.tar.gz
|
||||
sed -i "s/sha256sums=.*/sha256sums=(\"$(sha256sum ./release.tar.gz | awk '{print $1}')\"/" aur/mihomo-party/PKGBUILD
|
||||
sed -i "s/sha256sums=.*/sha256sums=(\"$(sha256sum ./release.tar.gz | awk '{print $1}')\"/" aur/mihomo-party-electron/PKGBUILD
|
||||
- name: Update Checksums
|
||||
if: matrix.pkgname == 'mihomo-party-bin' || matrix.pkgname == 'mihomo-party-electron-bin'
|
||||
run: |
|
||||
wget https://github.com/mihomo-party-org/mihomo-party/releases/download/$(echo ${{ github.ref }} | tr -d 'refs/tags/')/mihomo-party-linux-$(echo ${{ github.ref }} | tr -d 'refs/tags/v')-amd64.deb -O amd64.deb
|
||||
wget https://github.com/mihomo-party-org/mihomo-party/releases/download/$(echo ${{ github.ref }} | tr -d 'refs/tags/')/mihomo-party-linux-$(echo ${{ github.ref }} | tr -d 'refs/tags/v')-arm64.deb -O arm64.deb
|
||||
wget https://github.com/${{ github.repository }}/releases/download/$(echo ${{ github.ref }} | tr -d 'refs/tags/')/clash-party-linux-$(echo ${{ github.ref }} | tr -d 'refs/tags/v')-amd64.deb -O amd64.deb
|
||||
wget https://github.com/${{ github.repository }}/releases/download/$(echo ${{ github.ref }} | tr -d 'refs/tags/')/clash-party-linux-$(echo ${{ github.ref }} | tr -d 'refs/tags/v')-arm64.deb -O arm64.deb
|
||||
sed -i "s/sha256sums_x86_64=.*/sha256sums_x86_64=(\"$(sha256sum ./amd64.deb | awk '{print $1}')\")/" aur/mihomo-party-bin/PKGBUILD
|
||||
sed -i "s/sha256sums_aarch64=.*/sha256sums_aarch64=(\"$(sha256sum ./arm64.deb | awk '{print $1}')\")/" aur/mihomo-party-bin/PKGBUILD
|
||||
sed -i "s/sha256sums_x86_64=.*/sha256sums_x86_64=(\"$(sha256sum ./amd64.deb | awk '{print $1}')\")/" aur/mihomo-party-electron-bin/PKGBUILD
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
# 1.9.1
|
||||
|
||||
## 修复 (Fix)
|
||||
|
||||
- 修复 Windows 下以管理员重启开启 TUN 时因单实例锁冲突导致的闪退问题
|
||||
- 修复托盘菜单开启 TUN 时管理员重启后继续执行导致的竞态问题
|
||||
- 修复关键资源文件复制失败时静默跳过导致内核启动异常的问题
|
||||
|
||||
# 1.9.0
|
||||
|
||||
## 新功能 (Feat)
|
||||
|
||||
@ -15,14 +15,22 @@ const monacoEditorPlugin = isObjectWithDefaultFunction(monacoEditorPluginModule)
|
||||
? monacoEditorPluginModule.default
|
||||
: monacoEditorPluginModule
|
||||
|
||||
// Win7 build: bundle all deps (Vite converts ESM→CJS), only externalize native modules
|
||||
const isLegacyBuild = process.env.LEGACY_BUILD === 'true'
|
||||
const legacyExternal = ['sysproxy-rs', 'electron']
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
plugins: [externalizeDepsPlugin()]
|
||||
plugins: isLegacyBuild ? [] : [externalizeDepsPlugin()],
|
||||
build: isLegacyBuild
|
||||
? { rollupOptions: { external: legacyExternal, output: { format: 'cjs' } } }
|
||||
: undefined
|
||||
},
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
plugins: isLegacyBuild ? [] : [externalizeDepsPlugin()],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: isLegacyBuild ? legacyExternal : undefined,
|
||||
output: {
|
||||
format: 'cjs',
|
||||
entryFileNames: '[name].cjs'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mihomo-party",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.1",
|
||||
"description": "Clash Party",
|
||||
"type": "module",
|
||||
"main": "./out/main/index.js",
|
||||
|
||||
@ -321,10 +321,10 @@ const SYSPROXY_RS_VERSION = 'v0.1.0'
|
||||
const SYSPROXY_RS_URL_PREFIX = `https://github.com/mihomo-party-org/sysproxy-rs-opti/releases/download/${SYSPROXY_RS_VERSION}`
|
||||
|
||||
function getSysproxyNodeName() {
|
||||
// 检测是否为 musl 系统(与 src/native/sysproxy/index.js 保持一致)
|
||||
const isMusl = (() => {
|
||||
if (platform !== 'linux') return false
|
||||
try {
|
||||
// 通过 ldd --version 输出判断是否为 musl
|
||||
const output = execSync('ldd --version 2>&1 || true').toString()
|
||||
return output.includes('musl')
|
||||
} catch {
|
||||
|
||||
@ -256,33 +256,39 @@ export async function restartAsAdmin(forTun: boolean = true): Promise<void> {
|
||||
throw new Error('This function is only available on Windows')
|
||||
}
|
||||
|
||||
// 先停止 Core,避免新旧进程冲突
|
||||
try {
|
||||
const { stopCore } = await import('./manager')
|
||||
managerLogger.info('Stopping core before admin restart...')
|
||||
await stopCore(true)
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
} catch (error) {
|
||||
managerLogger.warn('Failed to stop core before restart:', error)
|
||||
}
|
||||
|
||||
const exePath = process.execPath
|
||||
const args = process.argv.slice(1)
|
||||
const args = process.argv.slice(1).filter((arg) => arg !== '--admin-restart-for-tun')
|
||||
const restartArgs = forTun ? [...args, '--admin-restart-for-tun'] : args
|
||||
|
||||
const escapedExePath = exePath.replace(/'/g, "''")
|
||||
const argsString = restartArgs.map((arg) => arg.replace(/'/g, "''")).join("', '")
|
||||
|
||||
// 使用 Start-Sleep 延迟启动,确保旧进程完全退出后再启动新进程
|
||||
const command =
|
||||
restartArgs.length > 0
|
||||
? `powershell -NoProfile -Command "Start-Process -FilePath '${escapedExePath}' -ArgumentList '${argsString}' -Verb RunAs -Wait:$false; exit 0"`
|
||||
: `powershell -NoProfile -Command "Start-Process -FilePath '${escapedExePath}' -Verb RunAs -Wait:$false; exit 0"`
|
||||
? `powershell -NoProfile -Command "Start-Sleep -Milliseconds 1000; Start-Process -FilePath '${escapedExePath}' -ArgumentList '${argsString}' -Verb RunAs"`
|
||||
: `powershell -NoProfile -Command "Start-Sleep -Milliseconds 1000; Start-Process -FilePath '${escapedExePath}' -Verb RunAs"`
|
||||
|
||||
managerLogger.info('Restarting as administrator with command', command)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(command, { windowsHide: true }, (error, _stdout, stderr) => {
|
||||
if (error) {
|
||||
managerLogger.error('PowerShell execution error', error)
|
||||
managerLogger.error('stderr', stderr)
|
||||
reject(new Error(`Failed to restart as administrator: ${error.message}`))
|
||||
return
|
||||
}
|
||||
managerLogger.info('PowerShell command executed successfully, quitting app')
|
||||
setTimeout(() => app.quit(), 500)
|
||||
resolve()
|
||||
})
|
||||
// 先启动 PowerShell(它会等待 1 秒),然后立即退出当前进程
|
||||
exec(command, { windowsHide: true }, (error) => {
|
||||
if (error) {
|
||||
managerLogger.error('Failed to start PowerShell for admin restart', error)
|
||||
}
|
||||
})
|
||||
managerLogger.info('PowerShell command started, quitting app immediately')
|
||||
app.exit(0)
|
||||
}
|
||||
|
||||
export async function requestTunPermissions(): Promise<void> {
|
||||
@ -342,21 +348,15 @@ export async function validateTunPermissionsOnStartup(_restartCore: () => Promis
|
||||
const hasPermissions = await checkMihomoCorePermissions()
|
||||
|
||||
if (!hasPermissions) {
|
||||
managerLogger.warn('TUN is enabled but insufficient permissions detected, prompting user...')
|
||||
const confirmed = await showTunPermissionDialog()
|
||||
if (confirmed) {
|
||||
await restartAsAdmin()
|
||||
return
|
||||
}
|
||||
|
||||
managerLogger.warn('User declined admin restart, auto-disabling TUN...')
|
||||
// 启动时没有权限,静默禁用 TUN,不弹窗打扰用户
|
||||
managerLogger.warn('TUN is enabled but insufficient permissions detected, auto-disabling TUN...')
|
||||
await patchControledMihomoConfig({ tun: { enable: false } })
|
||||
|
||||
const { mainWindow } = await import('../index')
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
|
||||
managerLogger.info('TUN auto-disabled due to insufficient permissions')
|
||||
managerLogger.info('TUN auto-disabled due to insufficient permissions on startup')
|
||||
} else {
|
||||
managerLogger.info('TUN permissions validated successfully')
|
||||
}
|
||||
|
||||
@ -87,13 +87,13 @@ async function checkHighPrivilegeCoreEarly(): Promise<void> {
|
||||
if (choice === 0) {
|
||||
try {
|
||||
await restartAsAdmin(false)
|
||||
process.exit(0)
|
||||
app.exit(0)
|
||||
} catch (error) {
|
||||
safeShowErrorBox('common.error.adminRequired', `${error}`)
|
||||
process.exit(1)
|
||||
app.exit(1)
|
||||
}
|
||||
} else {
|
||||
process.exit(0)
|
||||
app.exit(0)
|
||||
}
|
||||
} catch (e) {
|
||||
mainLogger.error('Failed to check high privilege core', e)
|
||||
@ -151,12 +151,14 @@ app.whenReady().then(async () => {
|
||||
|
||||
try {
|
||||
initCoreWatcher()
|
||||
const [startPromise] = await startCore()
|
||||
startPromise.then(async () => {
|
||||
await initProfileUpdater()
|
||||
await initWebdavBackupScheduler()
|
||||
await checkAdminRestartForTun()
|
||||
})
|
||||
const startPromises = await startCore()
|
||||
if (startPromises.length > 0) {
|
||||
startPromises[0].then(async () => {
|
||||
await initProfileUpdater()
|
||||
await initWebdavBackupScheduler()
|
||||
await checkAdminRestartForTun()
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
||||
}
|
||||
|
||||
@ -39,6 +39,8 @@ import { trayLogger } from '../utils/logger'
|
||||
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
||||
|
||||
export let tray: Tray | null = null
|
||||
// macOS 流量显示状态,避免异步读取配置导致的时序问题
|
||||
let macTrafficIconEnabled = false
|
||||
|
||||
export const buildContextMenu = async (): Promise<Menu> => {
|
||||
// 添加调试日志
|
||||
@ -231,6 +233,7 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
await restartAsAdmin()
|
||||
return
|
||||
} catch (error) {
|
||||
await trayLogger.error('Failed to restart as admin from tray', error)
|
||||
item.checked = false
|
||||
@ -250,6 +253,9 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
||||
}
|
||||
} catch (error) {
|
||||
await trayLogger.warn('Permission check failed in tray', error)
|
||||
item.checked = false
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
return
|
||||
}
|
||||
|
||||
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||
@ -392,7 +398,8 @@ export async function createTray(): Promise<void> {
|
||||
}
|
||||
// 移除旧监听器防止累积
|
||||
ipcMain.removeAllListeners('trayIconUpdate')
|
||||
ipcMain.on('trayIconUpdate', async (_, png: string) => {
|
||||
ipcMain.on('trayIconUpdate', async (_, png: string, enabled: boolean) => {
|
||||
macTrafficIconEnabled = enabled
|
||||
const image = nativeImage.createFromDataURL(png).resize({ height: 16 })
|
||||
image.setTemplateImage(true)
|
||||
tray?.setImage(image)
|
||||
@ -524,14 +531,15 @@ const getIconPaths = () => {
|
||||
|
||||
export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void {
|
||||
if (!tray) return
|
||||
// macOS 流量显示开启时,由 trayIconUpdate 负责图标更新
|
||||
if (process.platform === 'darwin' && macTrafficIconEnabled) return
|
||||
|
||||
const status = calculateTrayIconStatus(sysProxyEnabled, tunEnabled)
|
||||
const iconPaths = getIconPaths()
|
||||
|
||||
getAppConfig().then(({ disableTrayIconColor = false, showTraffic = false }) => {
|
||||
getAppConfig().then(({ disableTrayIconColor = false }) => {
|
||||
if (!tray) return
|
||||
// macOS 开启流量显示时,由 trayIconUpdate 负责图标更新
|
||||
if (process.platform === 'darwin' && showTraffic) return
|
||||
if (process.platform === 'darwin' && macTrafficIconEnabled) return
|
||||
const iconPath = disableTrayIconColor ? iconPaths.white : iconPaths[status]
|
||||
try {
|
||||
if (process.platform === 'darwin') {
|
||||
@ -550,10 +558,10 @@ export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: bo
|
||||
|
||||
export async function updateTrayIcon(): Promise<void> {
|
||||
if (!tray) return
|
||||
// macOS 流量显示开启时,由 trayIconUpdate 负责图标更新
|
||||
if (process.platform === 'darwin' && macTrafficIconEnabled) return
|
||||
|
||||
const { disableTrayIconColor = false, showTraffic = false } = await getAppConfig()
|
||||
// macOS 开启流量显示时,由 trayIconUpdate 负责图标更新
|
||||
if (process.platform === 'darwin' && showTraffic) return
|
||||
const { disableTrayIconColor = false } = await getAppConfig()
|
||||
const status = await getTrayIconStatus()
|
||||
const iconPaths = getIconPaths()
|
||||
const iconPath = disableTrayIconColor ? iconPaths.white : iconPaths[status]
|
||||
|
||||
@ -56,10 +56,22 @@ function getTaskXml(asAdmin: boolean): string {
|
||||
export async function checkAutoRun(): Promise<boolean> {
|
||||
if (process.platform === 'win32') {
|
||||
const execPromise = promisify(exec)
|
||||
// 先检查任务计划程序
|
||||
try {
|
||||
const { stdout } = await execPromise(
|
||||
`chcp 437 && %SystemRoot%\\System32\\schtasks.exe /query /tn "${appName}"`
|
||||
)
|
||||
if (stdout.includes(appName)) {
|
||||
return true
|
||||
}
|
||||
} catch {
|
||||
// 任务计划程序中不存在,继续检查注册表
|
||||
}
|
||||
|
||||
// 检查注册表备用方案
|
||||
try {
|
||||
const regPath = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
|
||||
const { stdout } = await execPromise(`reg query "${regPath}" /v "${appName}"`)
|
||||
return stdout.includes(appName)
|
||||
} catch {
|
||||
return false
|
||||
@ -87,17 +99,45 @@ export async function enableAutoRun(): Promise<void> {
|
||||
const { checkAdminPrivileges } = await import('../core/manager')
|
||||
const isAdmin = await checkAdminPrivileges()
|
||||
await writeFile(taskFilePath, Buffer.from(`\ufeff${getTaskXml(isAdmin)}`, 'utf-16le'))
|
||||
|
||||
let taskCreated = false
|
||||
|
||||
if (isAdmin) {
|
||||
await execPromise(
|
||||
`%SystemRoot%\\System32\\schtasks.exe /create /tn "${appName}" /xml "${taskFilePath}" /f`
|
||||
)
|
||||
try {
|
||||
await execPromise(
|
||||
`%SystemRoot%\\System32\\schtasks.exe /create /tn "${appName}" /xml "${taskFilePath}" /f`
|
||||
)
|
||||
taskCreated = true
|
||||
} catch (error) {
|
||||
await managerLogger.warn('Failed to create scheduled task as admin:', error)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await execPromise(
|
||||
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/create', '/tn', '${appName}', '/xml', '${taskFilePath}', '/f' -WindowStyle Hidden"`
|
||||
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/create', '/tn', '${appName}', '/xml', '${taskFilePath}', '/f' -WindowStyle Hidden -Wait"`
|
||||
)
|
||||
// 验证任务是否创建成功
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
const created = await checkAutoRun()
|
||||
taskCreated = created
|
||||
if (!created) {
|
||||
await managerLogger.warn('Scheduled task creation may have failed or been rejected')
|
||||
}
|
||||
} catch {
|
||||
await managerLogger.info('Maybe the user rejected the UAC dialog?')
|
||||
await managerLogger.info('Scheduled task creation failed, trying registry fallback')
|
||||
}
|
||||
}
|
||||
|
||||
// 任务计划程序失败时使用注册表备用方案(适用于 Windows IoT LTSC 等受限环境)
|
||||
if (!taskCreated) {
|
||||
await managerLogger.info('Using registry fallback for auto-run')
|
||||
try {
|
||||
const regPath = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
|
||||
const regValue = `"${exePath()}"`
|
||||
await execPromise(`reg add "${regPath}" /v "${appName}" /t REG_SZ /d ${regValue} /f`)
|
||||
await managerLogger.info('Registry auto-run entry created successfully')
|
||||
} catch (regError) {
|
||||
await managerLogger.error('Failed to create registry auto-run entry:', regError)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,16 +177,26 @@ export async function disableAutoRun(): Promise<void> {
|
||||
const execPromise = promisify(exec)
|
||||
const { checkAdminPrivileges } = await import('../core/manager')
|
||||
const isAdmin = await checkAdminPrivileges()
|
||||
if (isAdmin) {
|
||||
await execPromise(`%SystemRoot%\\System32\\schtasks.exe /delete /tn "${appName}" /f`)
|
||||
} else {
|
||||
try {
|
||||
|
||||
// 删除任务计划程序中的任务
|
||||
try {
|
||||
if (isAdmin) {
|
||||
await execPromise(`%SystemRoot%\\System32\\schtasks.exe /delete /tn "${appName}" /f`)
|
||||
} else {
|
||||
await execPromise(
|
||||
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/delete', '/tn', '${appName}', '/f' -WindowStyle Hidden"`
|
||||
`powershell -NoProfile -Command "Start-Process schtasks -Verb RunAs -ArgumentList '/delete', '/tn', '${appName}', '/f' -WindowStyle Hidden -Wait"`
|
||||
)
|
||||
} catch {
|
||||
await managerLogger.info('Maybe the user rejected the UAC dialog?')
|
||||
}
|
||||
} catch {
|
||||
// 任务可能不存在,忽略错误
|
||||
}
|
||||
|
||||
// 同时删除注册表备用方案
|
||||
try {
|
||||
const regPath = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
|
||||
await execPromise(`reg delete "${regPath}" /v "${appName}" /f`)
|
||||
} catch {
|
||||
// 注册表项可能不存在,忽略错误
|
||||
}
|
||||
}
|
||||
if (process.platform === 'darwin') {
|
||||
|
||||
@ -97,10 +97,15 @@ async function enableSysProxy(): Promise<void> {
|
||||
}
|
||||
} else {
|
||||
// Windows / Linux 直接使用 sysproxy-rs
|
||||
if (mode === 'auto') {
|
||||
triggerAutoProxy(true, `http://${proxyHost}:${pacPort}/pac`)
|
||||
} else {
|
||||
triggerManualProxy(true, proxyHost, port, bypass.join(','))
|
||||
try {
|
||||
if (mode === 'auto') {
|
||||
triggerAutoProxy(true, `http://${proxyHost}:${pacPort}/pac`)
|
||||
} else {
|
||||
triggerManualProxy(true, proxyHost, port, bypass.join(','))
|
||||
}
|
||||
} catch (error) {
|
||||
await proxyLogger.error('Failed to enable system proxy', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,8 +119,13 @@ async function disableSysProxy(): Promise<void> {
|
||||
)
|
||||
} else {
|
||||
// Windows / Linux 直接使用 sysproxy-rs
|
||||
triggerAutoProxy(false, '')
|
||||
triggerManualProxy(false, '', 0, '')
|
||||
try {
|
||||
triggerAutoProxy(false, '')
|
||||
triggerManualProxy(false, '', 0, '')
|
||||
} catch (error) {
|
||||
await proxyLogger.error('Failed to disable system proxy', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -189,8 +189,9 @@ async function initFiles(): Promise<void> {
|
||||
await cp(sourcePath, targetPath, { recursive: true, force: true })
|
||||
} catch (error: unknown) {
|
||||
const code = (error as NodeJS.ErrnoException).code
|
||||
if (code === 'EPERM' || code === 'EBUSY') {
|
||||
await initLogger.warn(`Skipping ${file}: file is in use`)
|
||||
// 文件被占用或权限问题,如果目标已存在则跳过
|
||||
if ((code === 'EPERM' || code === 'EBUSY' || code === 'EACCES') && existsSync(targetPath)) {
|
||||
await initLogger.warn(`Skipping ${file}: file is in use or permission denied`)
|
||||
return
|
||||
}
|
||||
throw error
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
const { existsSync, readFileSync } = require('fs')
|
||||
const { existsSync } = require('fs')
|
||||
const { join, dirname } = require('path')
|
||||
|
||||
const { platform, arch } = process
|
||||
@ -7,17 +7,19 @@ let nativeBinding = null
|
||||
let loadError = null
|
||||
|
||||
function isMusl() {
|
||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
||||
try {
|
||||
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
||||
} catch {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// 优先使用 process.report(Node.js 12+,最可靠)
|
||||
if (process.report && typeof process.report.getReport === 'function') {
|
||||
const { glibcVersionRuntime } = process.report.getReport().header
|
||||
return !glibcVersionRuntime
|
||||
}
|
||||
// 备选:检查 ldd --version 输出
|
||||
try {
|
||||
const { execSync } = require('child_process')
|
||||
const output = execSync('ldd --version 2>&1 || true').toString()
|
||||
return output.includes('musl')
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function getBindingName() {
|
||||
|
||||
@ -161,7 +161,7 @@ const ConnCard: React.FC<Props> = (props) => {
|
||||
}
|
||||
} else if (hasShowTrafficRef.current) {
|
||||
// 只在从 showTraffic=true 切换到 false 时恢复一次原始图标
|
||||
window.electron.ipcRenderer.send('trayIconUpdate', trayIconBase64)
|
||||
window.electron.ipcRenderer.send('trayIconUpdate', trayIconBase64, false)
|
||||
hasShowTrafficRef.current = false
|
||||
}
|
||||
}
|
||||
@ -305,7 +305,7 @@ const drawSvg = async (
|
||||
currentDownloadRef.current = download
|
||||
const svg = `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 36"><image height="36" width="36" href="${trayIconBase64}"/><text x="140" y="15" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end">${calcTraffic(upload)}/s</text><text x="140" y="34" font-size="18" font-family="PingFang SC" font-weight="bold" text-anchor="end">${calcTraffic(download)}/s</text></svg>`
|
||||
const image = await loadImage(svg)
|
||||
window.electron.ipcRenderer.send('trayIconUpdate', image)
|
||||
window.electron.ipcRenderer.send('trayIconUpdate', image, true)
|
||||
}
|
||||
|
||||
const loadImage = (url: string): Promise<string> => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user