opt: tray icon updating

This commit is contained in:
Memory 2025-08-27 12:59:28 +08:00 committed by GitHub
parent 4dbf054334
commit 78ec7f9822
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 52 additions and 8 deletions

View File

@ -375,9 +375,7 @@ export const TunStatus = async (): Promise<boolean> => {
return config?.tun?.enable === true
}
export async function getTrayIconStatus(): Promise<'white' | 'blue' | 'green' | 'red'> {
const [sysProxyEnabled, tunEnabled] = await Promise.all([SysProxyStatus(), TunStatus()])
export function calculateTrayIconStatus(sysProxyEnabled: boolean, tunEnabled: boolean): 'white' | 'blue' | 'green' | 'red' {
if (sysProxyEnabled && tunEnabled) {
return 'red' // 系统代理 + TUN 同时启用(警告状态)
} else if (sysProxyEnabled) {
@ -387,4 +385,9 @@ export async function getTrayIconStatus(): Promise<'white' | 'blue' | 'green' |
} else {
return 'white' // 全关
}
}
export async function getTrayIconStatus(): Promise<'white' | 'blue' | 'green' | 'red'> {
const [sysProxyEnabled, tunEnabled] = await Promise.all([SysProxyStatus(), TunStatus()])
return calculateTrayIconStatus(sysProxyEnabled, tunEnabled)
}

View File

@ -15,7 +15,7 @@ import pngIconBlue from '../../../resources/icon_blue.png?asset'
import pngIconRed from '../../../resources/icon_red.png?asset'
import pngIconGreen from '../../../resources/icon_green.png?asset'
import templateIcon from '../../../resources/iconTemplate.png?asset'
import { mihomoChangeProxy, mihomoCloseAllConnections, mihomoGroups, patchMihomoConfig, getTrayIconStatus } from '../core/mihomoApi'
import { mihomoChangeProxy, mihomoCloseAllConnections, mihomoGroups, patchMihomoConfig, getTrayIconStatus, calculateTrayIconStatus } from '../core/mihomoApi'
import { mainWindow, showMainWindow, triggerMainWindow } from '..'
import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
@ -458,6 +458,27 @@ const getIconPaths = () => {
}
}
export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void {
if (!tray) return
const status = calculateTrayIconStatus(sysProxyEnabled, tunEnabled)
const iconPaths = getIconPaths()
const iconPath = iconPaths[status]
try {
if (process.platform === 'darwin') {
const icon = nativeImage.createFromPath(iconPath).resize({ height: 16 })
tray.setImage(icon)
} else if (process.platform === 'win32') {
tray.setImage(iconPath)
} else if (process.platform === 'linux') {
tray.setImage(iconPath)
}
} catch (error) {
console.error('更新托盘图标失败:', error)
}
}
export async function updateTrayIcon(): Promise<void> {
if (!tray) return

View File

@ -84,7 +84,7 @@ import {
import { getRuntimeConfig, getRuntimeConfigStr } from '../core/factory'
import { listWebdavBackups, webdavBackup, webdavDelete, webdavRestore } from '../resolve/backup'
import { getInterfaces } from '../sys/interface'
import { closeTrayIcon, copyEnv, showTrayIcon, updateTrayIcon } from '../resolve/tray'
import { closeTrayIcon, copyEnv, showTrayIcon, updateTrayIcon, updateTrayIconImmediate } from '../resolve/tray'
import { registerShortcut } from '../resolve/shortcut'
import { closeMainWindow, mainWindow, showMainWindow, triggerMainWindow } from '..'
import {
@ -260,6 +260,9 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('showTrayIcon', () => ipcErrorWrapper(showTrayIcon)())
ipcMain.handle('closeTrayIcon', () => ipcErrorWrapper(closeTrayIcon)())
ipcMain.handle('updateTrayIcon', () => ipcErrorWrapper(updateTrayIcon)())
ipcMain.handle('updateTrayIconImmediate', (_e, sysProxyEnabled, tunEnabled) => {
updateTrayIconImmediate(sysProxyEnabled, tunEnabled)
})
ipcMain.handle('showMainWindow', showMainWindow)
ipcMain.handle('closeMainWindow', closeMainWindow)
ipcMain.handle('triggerMainWindow', triggerMainWindow)

View File

@ -2,7 +2,8 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
import BorderSwitch from '@renderer/components/base/border-swtich'
import { useLocation, useNavigate } from 'react-router-dom'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import { triggerSysProxy, updateTrayIcon } from '@renderer/utils/ipc'
import { triggerSysProxy, updateTrayIcon, updateTrayIconImmediate } from '@renderer/utils/ipc'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { AiOutlineGlobal } from 'react-icons/ai'
import React from 'react'
import { useSortable } from '@dnd-kit/sortable'
@ -20,7 +21,9 @@ const SysproxySwitcher: React.FC<Props> = (props) => {
const navigate = useNavigate()
const match = location.pathname.includes('/sysproxy')
const { appConfig, patchAppConfig } = useAppConfig()
const { controledMihomoConfig } = useControledMihomoConfig()
const { sysProxy, sysproxyCardStatus = 'col-span-1' } = appConfig || {}
const { tun } = controledMihomoConfig || {}
const { enable } = sysProxy || {}
const {
attributes,
@ -35,8 +38,11 @@ const SysproxySwitcher: React.FC<Props> = (props) => {
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
const onChange = async (enable: boolean): Promise<void> => {
const previousState = !enable
const tunEnabled = tun?.enable ?? false
// 立即更新图标
updateTrayIconImmediate(enable, tunEnabled)
// 立即更新UI
try {
await patchAppConfig({ sysProxy: { enable } })
await triggerSysProxy(enable)
@ -46,6 +52,8 @@ const SysproxySwitcher: React.FC<Props> = (props) => {
await updateTrayIcon()
} catch (e) {
await patchAppConfig({ sysProxy: { enable: previousState } })
// 回滚图标
updateTrayIconImmediate(previousState, tunEnabled)
alert(e)
}
}

View File

@ -3,7 +3,7 @@ import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-c
import BorderSwitch from '@renderer/components/base/border-swtich'
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
import { useLocation, useNavigate } from 'react-router-dom'
import { restartCore, updateTrayIcon } from '@renderer/utils/ipc'
import { restartCore, updateTrayIcon, updateTrayIconImmediate } from '@renderer/utils/ipc'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import React from 'react'
@ -22,6 +22,7 @@ const TunSwitcher: React.FC<Props> = (props) => {
const match = location.pathname.includes('/tun') || false
const { appConfig } = useAppConfig()
const { tunCardStatus = 'col-span-1' } = appConfig || {}
const sysProxyEnabled = appConfig?.sysProxy?.enable ?? false
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { tun } = controledMihomoConfig || {}
const { enable } = tun || {}
@ -37,6 +38,7 @@ const TunSwitcher: React.FC<Props> = (props) => {
})
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
const onChange = async (enable: boolean): Promise<void> => {
updateTrayIconImmediate(sysProxyEnabled, enable)
if (enable) {
try {
// 检查内核权限
@ -54,9 +56,11 @@ const TunSwitcher: React.FC<Props> = (props) => {
} catch (error) {
console.error('Failed to restart as admin:', error)
await window.electron.ipcRenderer.invoke('showErrorDialog', t('tun.permissions.failed'), String(error))
updateTrayIconImmediate(sysProxyEnabled, false)
return
}
} else {
updateTrayIconImmediate(sysProxyEnabled, false)
return
}
} else {
@ -66,6 +70,7 @@ const TunSwitcher: React.FC<Props> = (props) => {
} catch (error) {
console.warn('Permission grant failed:', error)
await window.electron.ipcRenderer.invoke('showErrorDialog', t('tun.permissions.failed'), String(error))
updateTrayIconImmediate(sysProxyEnabled, false)
return
}
}

View File

@ -406,6 +406,10 @@ export async function updateTrayIcon(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateTrayIcon'))
}
export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: boolean): void {
window.electron.ipcRenderer.invoke('updateTrayIconImmediate', sysProxyEnabled, tunEnabled)
}
export async function showMainWindow(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showMainWindow'))
}