From 78ec7f98223a3ce3c91a099f7dace280435147a4 Mon Sep 17 00:00:00 2001 From: Memory <134070804+Memory2314@users.noreply.github.com> Date: Wed, 27 Aug 2025 12:59:28 +0800 Subject: [PATCH] opt: tray icon updating --- src/main/core/mihomoApi.ts | 9 +++++--- src/main/resolve/tray.ts | 23 ++++++++++++++++++- src/main/utils/ipc.ts | 5 +++- .../components/sider/sysproxy-switcher.tsx | 12 ++++++++-- .../src/components/sider/tun-switcher.tsx | 7 +++++- src/renderer/src/utils/ipc.ts | 4 ++++ 6 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/main/core/mihomoApi.ts b/src/main/core/mihomoApi.ts index 2c61e61..c5c2888 100644 --- a/src/main/core/mihomoApi.ts +++ b/src/main/core/mihomoApi.ts @@ -375,9 +375,7 @@ export const TunStatus = async (): Promise => { 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) } \ No newline at end of file diff --git a/src/main/resolve/tray.ts b/src/main/resolve/tray.ts index 563625b..b409169 100644 --- a/src/main/resolve/tray.ts +++ b/src/main/resolve/tray.ts @@ -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 { if (!tray) return diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index af57626..f1391f9 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -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) diff --git a/src/renderer/src/components/sider/sysproxy-switcher.tsx b/src/renderer/src/components/sider/sysproxy-switcher.tsx index e941fce..81efd7e 100644 --- a/src/renderer/src/components/sider/sysproxy-switcher.tsx +++ b/src/renderer/src/components/sider/sysproxy-switcher.tsx @@ -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) => { 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) => { const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null const onChange = async (enable: boolean): Promise => { 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) => { await updateTrayIcon() } catch (e) { await patchAppConfig({ sysProxy: { enable: previousState } }) + // 回滚图标 + updateTrayIconImmediate(previousState, tunEnabled) alert(e) } } diff --git a/src/renderer/src/components/sider/tun-switcher.tsx b/src/renderer/src/components/sider/tun-switcher.tsx index 9697e3a..0389b1a 100644 --- a/src/renderer/src/components/sider/tun-switcher.tsx +++ b/src/renderer/src/components/sider/tun-switcher.tsx @@ -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) => { 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) => { }) const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null const onChange = async (enable: boolean): Promise => { + updateTrayIconImmediate(sysProxyEnabled, enable) if (enable) { try { // 检查内核权限 @@ -54,9 +56,11 @@ const TunSwitcher: React.FC = (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) => { } catch (error) { console.warn('Permission grant failed:', error) await window.electron.ipcRenderer.invoke('showErrorDialog', t('tun.permissions.failed'), String(error)) + updateTrayIconImmediate(sysProxyEnabled, false) return } } diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index 359f98d..7f09253 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -406,6 +406,10 @@ export async function updateTrayIcon(): Promise { 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 { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showMainWindow')) }