mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-04-16 01:20:35 +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: |
|
run: |
|
||||||
pnpm install
|
pnpm install
|
||||||
pnpm add -D electron@22.3.27
|
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
|
(Get-Content electron-builder.yml) -replace 'windows', 'win7' | Set-Content electron-builder.yml
|
||||||
# Electron 22 requires CJS format
|
# Electron 22 requires CJS format
|
||||||
(Get-Content package.json) -replace '"type": "module"', '"type": "commonjs"' | Set-Content package.json
|
(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
|
- name: Update Version for Dev Build
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
env:
|
env:
|
||||||
@ -213,6 +210,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
npm_config_arch: ${{ matrix.arch }}
|
npm_config_arch: ${{ matrix.arch }}
|
||||||
npm_config_target_arch: ${{ matrix.arch }}
|
npm_config_target_arch: ${{ matrix.arch }}
|
||||||
|
LEGACY_BUILD: 'true'
|
||||||
run: pnpm build:win --${{ matrix.arch }}
|
run: pnpm build:win --${{ matrix.arch }}
|
||||||
- name: Add Portable Flag
|
- name: Add Portable Flag
|
||||||
run: |
|
run: |
|
||||||
@ -590,7 +588,7 @@ jobs:
|
|||||||
- mihomo-party-bin
|
- mihomo-party-bin
|
||||||
- mihomo-party
|
- mihomo-party
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
needs: linux
|
needs: updater
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@ -601,14 +599,14 @@ jobs:
|
|||||||
- name: Update Checksums
|
- name: Update Checksums
|
||||||
if: matrix.pkgname == 'mihomo-party' || matrix.pkgname == 'mihomo-party-electron'
|
if: matrix.pkgname == 'mihomo-party' || matrix.pkgname == 'mihomo-party-electron'
|
||||||
run: |
|
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/PKGBUILD
|
||||||
sed -i "s/sha256sums=.*/sha256sums=(\"$(sha256sum ./release.tar.gz | awk '{print $1}')\"/" aur/mihomo-party-electron/PKGBUILD
|
sed -i "s/sha256sums=.*/sha256sums=(\"$(sha256sum ./release.tar.gz | awk '{print $1}')\"/" aur/mihomo-party-electron/PKGBUILD
|
||||||
- name: Update Checksums
|
- name: Update Checksums
|
||||||
if: matrix.pkgname == 'mihomo-party-bin' || matrix.pkgname == 'mihomo-party-electron-bin'
|
if: matrix.pkgname == 'mihomo-party-bin' || matrix.pkgname == 'mihomo-party-electron-bin'
|
||||||
run: |
|
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/${{ 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/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')-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_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_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
|
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
|
# 1.9.0
|
||||||
|
|
||||||
## 新功能 (Feat)
|
## 新功能 (Feat)
|
||||||
|
|||||||
@ -15,14 +15,22 @@ const monacoEditorPlugin = isObjectWithDefaultFunction(monacoEditorPluginModule)
|
|||||||
? monacoEditorPluginModule.default
|
? monacoEditorPluginModule.default
|
||||||
: monacoEditorPluginModule
|
: 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({
|
export default defineConfig({
|
||||||
main: {
|
main: {
|
||||||
plugins: [externalizeDepsPlugin()]
|
plugins: isLegacyBuild ? [] : [externalizeDepsPlugin()],
|
||||||
|
build: isLegacyBuild
|
||||||
|
? { rollupOptions: { external: legacyExternal, output: { format: 'cjs' } } }
|
||||||
|
: undefined
|
||||||
},
|
},
|
||||||
preload: {
|
preload: {
|
||||||
plugins: [externalizeDepsPlugin()],
|
plugins: isLegacyBuild ? [] : [externalizeDepsPlugin()],
|
||||||
build: {
|
build: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
external: isLegacyBuild ? legacyExternal : undefined,
|
||||||
output: {
|
output: {
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
entryFileNames: '[name].cjs'
|
entryFileNames: '[name].cjs'
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mihomo-party",
|
"name": "mihomo-party",
|
||||||
"version": "1.9.0",
|
"version": "1.9.1",
|
||||||
"description": "Clash Party",
|
"description": "Clash Party",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./out/main/index.js",
|
"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}`
|
const SYSPROXY_RS_URL_PREFIX = `https://github.com/mihomo-party-org/sysproxy-rs-opti/releases/download/${SYSPROXY_RS_VERSION}`
|
||||||
|
|
||||||
function getSysproxyNodeName() {
|
function getSysproxyNodeName() {
|
||||||
|
// 检测是否为 musl 系统(与 src/native/sysproxy/index.js 保持一致)
|
||||||
const isMusl = (() => {
|
const isMusl = (() => {
|
||||||
if (platform !== 'linux') return false
|
if (platform !== 'linux') return false
|
||||||
try {
|
try {
|
||||||
// 通过 ldd --version 输出判断是否为 musl
|
|
||||||
const output = execSync('ldd --version 2>&1 || true').toString()
|
const output = execSync('ldd --version 2>&1 || true').toString()
|
||||||
return output.includes('musl')
|
return output.includes('musl')
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@ -256,33 +256,39 @@ export async function restartAsAdmin(forTun: boolean = true): Promise<void> {
|
|||||||
throw new Error('This function is only available on Windows')
|
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 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 restartArgs = forTun ? [...args, '--admin-restart-for-tun'] : args
|
||||||
|
|
||||||
const escapedExePath = exePath.replace(/'/g, "''")
|
const escapedExePath = exePath.replace(/'/g, "''")
|
||||||
const argsString = restartArgs.map((arg) => arg.replace(/'/g, "''")).join("', '")
|
const argsString = restartArgs.map((arg) => arg.replace(/'/g, "''")).join("', '")
|
||||||
|
|
||||||
|
// 使用 Start-Sleep 延迟启动,确保旧进程完全退出后再启动新进程
|
||||||
const command =
|
const command =
|
||||||
restartArgs.length > 0
|
restartArgs.length > 0
|
||||||
? `powershell -NoProfile -Command "Start-Process -FilePath '${escapedExePath}' -ArgumentList '${argsString}' -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-Process -FilePath '${escapedExePath}' -Verb RunAs -Wait:$false; exit 0"`
|
: `powershell -NoProfile -Command "Start-Sleep -Milliseconds 1000; Start-Process -FilePath '${escapedExePath}' -Verb RunAs"`
|
||||||
|
|
||||||
managerLogger.info('Restarting as administrator with command', command)
|
managerLogger.info('Restarting as administrator with command', command)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
// 先启动 PowerShell(它会等待 1 秒),然后立即退出当前进程
|
||||||
exec(command, { windowsHide: true }, (error, _stdout, stderr) => {
|
exec(command, { windowsHide: true }, (error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
managerLogger.error('PowerShell execution error', error)
|
managerLogger.error('Failed to start PowerShell for admin restart', 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()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
managerLogger.info('PowerShell command started, quitting app immediately')
|
||||||
|
app.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requestTunPermissions(): Promise<void> {
|
export async function requestTunPermissions(): Promise<void> {
|
||||||
@ -342,21 +348,15 @@ export async function validateTunPermissionsOnStartup(_restartCore: () => Promis
|
|||||||
const hasPermissions = await checkMihomoCorePermissions()
|
const hasPermissions = await checkMihomoCorePermissions()
|
||||||
|
|
||||||
if (!hasPermissions) {
|
if (!hasPermissions) {
|
||||||
managerLogger.warn('TUN is enabled but insufficient permissions detected, prompting user...')
|
// 启动时没有权限,静默禁用 TUN,不弹窗打扰用户
|
||||||
const confirmed = await showTunPermissionDialog()
|
managerLogger.warn('TUN is enabled but insufficient permissions detected, auto-disabling TUN...')
|
||||||
if (confirmed) {
|
|
||||||
await restartAsAdmin()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
managerLogger.warn('User declined admin restart, auto-disabling TUN...')
|
|
||||||
await patchControledMihomoConfig({ tun: { enable: false } })
|
await patchControledMihomoConfig({ tun: { enable: false } })
|
||||||
|
|
||||||
const { mainWindow } = await import('../index')
|
const { mainWindow } = await import('../index')
|
||||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||||
ipcMain.emit('updateTrayMenu')
|
ipcMain.emit('updateTrayMenu')
|
||||||
|
|
||||||
managerLogger.info('TUN auto-disabled due to insufficient permissions')
|
managerLogger.info('TUN auto-disabled due to insufficient permissions on startup')
|
||||||
} else {
|
} else {
|
||||||
managerLogger.info('TUN permissions validated successfully')
|
managerLogger.info('TUN permissions validated successfully')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,13 +87,13 @@ async function checkHighPrivilegeCoreEarly(): Promise<void> {
|
|||||||
if (choice === 0) {
|
if (choice === 0) {
|
||||||
try {
|
try {
|
||||||
await restartAsAdmin(false)
|
await restartAsAdmin(false)
|
||||||
process.exit(0)
|
app.exit(0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
safeShowErrorBox('common.error.adminRequired', `${error}`)
|
safeShowErrorBox('common.error.adminRequired', `${error}`)
|
||||||
process.exit(1)
|
app.exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
process.exit(0)
|
app.exit(0)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
mainLogger.error('Failed to check high privilege core', e)
|
mainLogger.error('Failed to check high privilege core', e)
|
||||||
@ -151,12 +151,14 @@ app.whenReady().then(async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
initCoreWatcher()
|
initCoreWatcher()
|
||||||
const [startPromise] = await startCore()
|
const startPromises = await startCore()
|
||||||
startPromise.then(async () => {
|
if (startPromises.length > 0) {
|
||||||
await initProfileUpdater()
|
startPromises[0].then(async () => {
|
||||||
await initWebdavBackupScheduler()
|
await initProfileUpdater()
|
||||||
await checkAdminRestartForTun()
|
await initWebdavBackupScheduler()
|
||||||
})
|
await checkAdminRestartForTun()
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,8 @@ import { trayLogger } from '../utils/logger'
|
|||||||
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
||||||
|
|
||||||
export let tray: Tray | null = null
|
export let tray: Tray | null = null
|
||||||
|
// macOS 流量显示状态,避免异步读取配置导致的时序问题
|
||||||
|
let macTrafficIconEnabled = false
|
||||||
|
|
||||||
export const buildContextMenu = async (): Promise<Menu> => {
|
export const buildContextMenu = async (): Promise<Menu> => {
|
||||||
// 添加调试日志
|
// 添加调试日志
|
||||||
@ -231,6 +233,7 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
|||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
try {
|
try {
|
||||||
await restartAsAdmin()
|
await restartAsAdmin()
|
||||||
|
return
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await trayLogger.error('Failed to restart as admin from tray', error)
|
await trayLogger.error('Failed to restart as admin from tray', error)
|
||||||
item.checked = false
|
item.checked = false
|
||||||
@ -250,6 +253,9 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await trayLogger.warn('Permission check failed in tray', error)
|
await trayLogger.warn('Permission check failed in tray', error)
|
||||||
|
item.checked = false
|
||||||
|
ipcMain.emit('updateTrayMenu')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||||
@ -392,7 +398,8 @@ export async function createTray(): Promise<void> {
|
|||||||
}
|
}
|
||||||
// 移除旧监听器防止累积
|
// 移除旧监听器防止累积
|
||||||
ipcMain.removeAllListeners('trayIconUpdate')
|
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 })
|
const image = nativeImage.createFromDataURL(png).resize({ height: 16 })
|
||||||
image.setTemplateImage(true)
|
image.setTemplateImage(true)
|
||||||
tray?.setImage(image)
|
tray?.setImage(image)
|
||||||
@ -524,14 +531,15 @@ const getIconPaths = () => {
|
|||||||
|
|
||||||
export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void {
|
export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void {
|
||||||
if (!tray) return
|
if (!tray) return
|
||||||
|
// macOS 流量显示开启时,由 trayIconUpdate 负责图标更新
|
||||||
|
if (process.platform === 'darwin' && macTrafficIconEnabled) return
|
||||||
|
|
||||||
const status = calculateTrayIconStatus(sysProxyEnabled, tunEnabled)
|
const status = calculateTrayIconStatus(sysProxyEnabled, tunEnabled)
|
||||||
const iconPaths = getIconPaths()
|
const iconPaths = getIconPaths()
|
||||||
|
|
||||||
getAppConfig().then(({ disableTrayIconColor = false, showTraffic = false }) => {
|
getAppConfig().then(({ disableTrayIconColor = false }) => {
|
||||||
if (!tray) return
|
if (!tray) return
|
||||||
// macOS 开启流量显示时,由 trayIconUpdate 负责图标更新
|
if (process.platform === 'darwin' && macTrafficIconEnabled) return
|
||||||
if (process.platform === 'darwin' && showTraffic) return
|
|
||||||
const iconPath = disableTrayIconColor ? iconPaths.white : iconPaths[status]
|
const iconPath = disableTrayIconColor ? iconPaths.white : iconPaths[status]
|
||||||
try {
|
try {
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
@ -550,10 +558,10 @@ export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: bo
|
|||||||
|
|
||||||
export async function updateTrayIcon(): Promise<void> {
|
export async function updateTrayIcon(): Promise<void> {
|
||||||
if (!tray) return
|
if (!tray) return
|
||||||
|
// macOS 流量显示开启时,由 trayIconUpdate 负责图标更新
|
||||||
|
if (process.platform === 'darwin' && macTrafficIconEnabled) return
|
||||||
|
|
||||||
const { disableTrayIconColor = false, showTraffic = false } = await getAppConfig()
|
const { disableTrayIconColor = false } = await getAppConfig()
|
||||||
// macOS 开启流量显示时,由 trayIconUpdate 负责图标更新
|
|
||||||
if (process.platform === 'darwin' && showTraffic) return
|
|
||||||
const status = await getTrayIconStatus()
|
const status = await getTrayIconStatus()
|
||||||
const iconPaths = getIconPaths()
|
const iconPaths = getIconPaths()
|
||||||
const iconPath = disableTrayIconColor ? iconPaths.white : iconPaths[status]
|
const iconPath = disableTrayIconColor ? iconPaths.white : iconPaths[status]
|
||||||
|
|||||||
@ -56,10 +56,22 @@ function getTaskXml(asAdmin: boolean): string {
|
|||||||
export async function checkAutoRun(): Promise<boolean> {
|
export async function checkAutoRun(): Promise<boolean> {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
const execPromise = promisify(exec)
|
const execPromise = promisify(exec)
|
||||||
|
// 先检查任务计划程序
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execPromise(
|
const { stdout } = await execPromise(
|
||||||
`chcp 437 && %SystemRoot%\\System32\\schtasks.exe /query /tn "${appName}"`
|
`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)
|
return stdout.includes(appName)
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
@ -87,17 +99,45 @@ export async function enableAutoRun(): Promise<void> {
|
|||||||
const { checkAdminPrivileges } = await import('../core/manager')
|
const { checkAdminPrivileges } = await import('../core/manager')
|
||||||
const isAdmin = await checkAdminPrivileges()
|
const isAdmin = await checkAdminPrivileges()
|
||||||
await writeFile(taskFilePath, Buffer.from(`\ufeff${getTaskXml(isAdmin)}`, 'utf-16le'))
|
await writeFile(taskFilePath, Buffer.from(`\ufeff${getTaskXml(isAdmin)}`, 'utf-16le'))
|
||||||
|
|
||||||
|
let taskCreated = false
|
||||||
|
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
await execPromise(
|
try {
|
||||||
`%SystemRoot%\\System32\\schtasks.exe /create /tn "${appName}" /xml "${taskFilePath}" /f`
|
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 {
|
} else {
|
||||||
try {
|
try {
|
||||||
await execPromise(
|
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 {
|
} 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 execPromise = promisify(exec)
|
||||||
const { checkAdminPrivileges } = await import('../core/manager')
|
const { checkAdminPrivileges } = await import('../core/manager')
|
||||||
const isAdmin = await checkAdminPrivileges()
|
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(
|
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') {
|
if (process.platform === 'darwin') {
|
||||||
|
|||||||
@ -97,10 +97,15 @@ async function enableSysProxy(): Promise<void> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Windows / Linux 直接使用 sysproxy-rs
|
// Windows / Linux 直接使用 sysproxy-rs
|
||||||
if (mode === 'auto') {
|
try {
|
||||||
triggerAutoProxy(true, `http://${proxyHost}:${pacPort}/pac`)
|
if (mode === 'auto') {
|
||||||
} else {
|
triggerAutoProxy(true, `http://${proxyHost}:${pacPort}/pac`)
|
||||||
triggerManualProxy(true, proxyHost, port, bypass.join(','))
|
} 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 {
|
} else {
|
||||||
// Windows / Linux 直接使用 sysproxy-rs
|
// Windows / Linux 直接使用 sysproxy-rs
|
||||||
triggerAutoProxy(false, '')
|
try {
|
||||||
triggerManualProxy(false, '', 0, '')
|
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 })
|
await cp(sourcePath, targetPath, { recursive: true, force: true })
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const code = (error as NodeJS.ErrnoException).code
|
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
|
return
|
||||||
}
|
}
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
const { existsSync, readFileSync } = require('fs')
|
const { existsSync } = require('fs')
|
||||||
const { join, dirname } = require('path')
|
const { join, dirname } = require('path')
|
||||||
|
|
||||||
const { platform, arch } = process
|
const { platform, arch } = process
|
||||||
@ -7,17 +7,19 @@ let nativeBinding = null
|
|||||||
let loadError = null
|
let loadError = null
|
||||||
|
|
||||||
function isMusl() {
|
function isMusl() {
|
||||||
if (!process.report || typeof process.report.getReport !== 'function') {
|
// 优先使用 process.report(Node.js 12+,最可靠)
|
||||||
try {
|
if (process.report && typeof process.report.getReport === 'function') {
|
||||||
const lddPath = require('child_process').execSync('which ldd').toString().trim()
|
|
||||||
return readFileSync(lddPath, 'utf8').includes('musl')
|
|
||||||
} catch {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const { glibcVersionRuntime } = process.report.getReport().header
|
const { glibcVersionRuntime } = process.report.getReport().header
|
||||||
return !glibcVersionRuntime
|
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() {
|
function getBindingName() {
|
||||||
|
|||||||
@ -161,7 +161,7 @@ const ConnCard: React.FC<Props> = (props) => {
|
|||||||
}
|
}
|
||||||
} else if (hasShowTrafficRef.current) {
|
} else if (hasShowTrafficRef.current) {
|
||||||
// 只在从 showTraffic=true 切换到 false 时恢复一次原始图标
|
// 只在从 showTraffic=true 切换到 false 时恢复一次原始图标
|
||||||
window.electron.ipcRenderer.send('trayIconUpdate', trayIconBase64)
|
window.electron.ipcRenderer.send('trayIconUpdate', trayIconBase64, false)
|
||||||
hasShowTrafficRef.current = false
|
hasShowTrafficRef.current = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,7 +305,7 @@ const drawSvg = async (
|
|||||||
currentDownloadRef.current = download
|
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 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)
|
const image = await loadImage(svg)
|
||||||
window.electron.ipcRenderer.send('trayIconUpdate', image)
|
window.electron.ipcRenderer.send('trayIconUpdate', image, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadImage = (url: string): Promise<string> => {
|
const loadImage = (url: string): Promise<string> => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user