diff --git a/changelog.md b/changelog.md index ec0fd4b..ad339c5 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ ### 新功能 (Feat) - 支持 cron 表达式以自定义订阅更新频率(#766) - 修复权限检查并优化TUN与自启联动(#977) + - 托盘图标能根据代理状态实时变化颜色 ### 修复 (Fix) - Windows 下当前运行内核权限检测(之前没有正确检测管理员权限运行的内核) - Windows 下 开机自启 按钮卡顿问题 隐藏运行黑框 现在申请权限会弹通知 @@ -48,4 +49,4 @@ ### 新功能 (Feat) - 重构 域名嗅探 卡片模块,改为“覆写”逻辑,当开关打开后,使用 嗅探覆写 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置 - 订阅/覆写卡片可右键呼出菜单 -- MacOS 下“轻触(tap)”触控板可进行开关操作(之前必须“按下(click)”) \ No newline at end of file +- MacOS 下“轻触(tap)”触控板可进行开关操作(之前必须“按下(click)”) diff --git a/resources/icon_blue.ico b/resources/icon_blue.ico new file mode 100644 index 0000000..7562a2e Binary files /dev/null and b/resources/icon_blue.ico differ diff --git a/resources/icon_blue.png b/resources/icon_blue.png new file mode 100644 index 0000000..933f20f Binary files /dev/null and b/resources/icon_blue.png differ diff --git a/resources/icon_green.ico b/resources/icon_green.ico new file mode 100644 index 0000000..f087ece Binary files /dev/null and b/resources/icon_green.ico differ diff --git a/resources/icon_green.png b/resources/icon_green.png new file mode 100644 index 0000000..201baee Binary files /dev/null and b/resources/icon_green.png differ diff --git a/resources/icon_red.ico b/resources/icon_red.ico new file mode 100644 index 0000000..07c85a0 Binary files /dev/null and b/resources/icon_red.ico differ diff --git a/resources/icon_red.png b/resources/icon_red.png new file mode 100644 index 0000000..6d135b5 Binary files /dev/null and b/resources/icon_red.png differ diff --git a/src/main/core/mihomoApi.ts b/src/main/core/mihomoApi.ts index 9227da5..2c61e61 100644 --- a/src/main/core/mihomoApi.ts +++ b/src/main/core/mihomoApi.ts @@ -169,7 +169,9 @@ export const mihomoUpgrade = async (): Promise => { } // Smart 内核 API -export const mihomoSmartGroupWeights = async (groupName: string): Promise> => { +export const mihomoSmartGroupWeights = async ( + groupName: string +): Promise> => { const instance = await getAxios() return await instance.get(`/group/${encodeURIComponent(groupName)}/weights`) } @@ -362,3 +364,27 @@ const mihomoConnections = async (): Promise => { } } } + +export async function SysProxyStatus(): Promise { + const appConfig = await getAppConfig() + return appConfig.sysProxy.enable +} + +export const TunStatus = async (): Promise => { + const config = await getControledMihomoConfig() + return config?.tun?.enable === true +} + +export async function getTrayIconStatus(): Promise<'white' | 'blue' | 'green' | 'red'> { + const [sysProxyEnabled, tunEnabled] = await Promise.all([SysProxyStatus(), TunStatus()]) + + if (sysProxyEnabled && tunEnabled) { + return 'red' // 系统代理 + TUN 同时启用(警告状态) + } else if (sysProxyEnabled) { + return 'blue' // 仅系统代理启用 + } else if (tunEnabled) { + return 'green' // 仅 TUN 启用 + } else { + return 'white' // 全关 + } +} \ No newline at end of file diff --git a/src/main/resolve/tray.ts b/src/main/resolve/tray.ts index 329244e..7ec25cc 100644 --- a/src/main/resolve/tray.ts +++ b/src/main/resolve/tray.ts @@ -7,14 +7,14 @@ import { patchControledMihomoConfig } from '../config' import icoIcon from '../../../resources/icon.ico?asset' +import icoIconBlue from '../../../resources/icon_blue.ico?asset' +import icoIconRed from '../../../resources/icon_red.ico?asset' +import icoIconGreen from '../../../resources/icon_green.ico?asset' import pngIcon from '../../../resources/icon.png?asset' -import templateIcon from '../../../resources/iconTemplate.png?asset' -import { - mihomoChangeProxy, - mihomoCloseAllConnections, - mihomoGroups, - patchMihomoConfig -} from '../core/mihomoApi' +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 { mihomoChangeProxy, mihomoCloseAllConnections, mihomoGroups, patchMihomoConfig, getTrayIconStatus } 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' @@ -328,7 +328,8 @@ export async function createTray(): Promise { tray.setContextMenu(menu) } if (process.platform === 'darwin') { - const icon = nativeImage.createFromPath(templateIcon).resize({ height: 16 }) + const iconPaths = getIconPaths() + const icon = nativeImage.createFromPath(iconPaths.white).resize({ height: 16 }) icon.setTemplateImage(true) tray = new Tray(icon) } @@ -337,6 +338,9 @@ export async function createTray(): Promise { } tray?.setToolTip('Mihomo Party') tray?.setIgnoreDoubleClickEvents(true) + + await updateTrayIcon() + if (process.platform === 'darwin') { if (!useDockIcon) { hideDockIcon() @@ -377,6 +381,7 @@ async function updateTrayMenu(): Promise { if (process.platform === 'linux') { tray?.setContextMenu(menu) } + await updateTrayIcon() } export async function copyEnv(type: 'bash' | 'cmd' | 'powershell'): Promise { @@ -429,3 +434,43 @@ export async function hideDockIcon(): Promise { app.dock.hide() } } + +const getIconPaths = () => { + if (process.platform === 'win32') { + return { + white: icoIcon, + blue: icoIconBlue, + green: icoIconGreen, + red: icoIconRed + } + } else { + return { + white: pngIcon, + blue: pngIconBlue, + green: pngIconGreen, + red: pngIconRed + } + } +} + +export async function updateTrayIcon(): Promise { + if (!tray) return + + const status = await getTrayIconStatus() + const iconPaths = getIconPaths() + const iconPath = iconPaths[status] + + try { + if (process.platform === 'darwin') { + const icon = nativeImage.createFromPath(iconPath).resize({ height: 16 }) + icon.setTemplateImage(true) + tray.setImage(icon) + } else if (process.platform === 'win32') { + tray.setImage(iconPath) + } else if (process.platform === 'linux') { + tray.setImage(iconPath) + } + } catch (error) { + console.error('更新托盘图标失败:', error) + } +} \ No newline at end of file diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index 2441498..af57626 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 } from '../resolve/tray' +import { closeTrayIcon, copyEnv, showTrayIcon, updateTrayIcon } from '../resolve/tray' import { registerShortcut } from '../resolve/shortcut' import { closeMainWindow, mainWindow, showMainWindow, triggerMainWindow } from '..' import { @@ -259,6 +259,7 @@ export function registerIpcMainHandlers(): void { }) ipcMain.handle('showTrayIcon', () => ipcErrorWrapper(showTrayIcon)()) ipcMain.handle('closeTrayIcon', () => ipcErrorWrapper(closeTrayIcon)()) + ipcMain.handle('updateTrayIcon', () => ipcErrorWrapper(updateTrayIcon)()) 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 e312cac..e941fce 100644 --- a/src/renderer/src/components/sider/sysproxy-switcher.tsx +++ b/src/renderer/src/components/sider/sysproxy-switcher.tsx @@ -2,7 +2,7 @@ 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 } from '@renderer/utils/ipc' +import { triggerSysProxy, updateTrayIcon } from '@renderer/utils/ipc' import { AiOutlineGlobal } from 'react-icons/ai' import React from 'react' import { useSortable } from '@dnd-kit/sortable' @@ -43,6 +43,7 @@ const SysproxySwitcher: React.FC = (props) => { window.electron.ipcRenderer.send('updateFloatingWindow') window.electron.ipcRenderer.send('updateTrayMenu') + await updateTrayIcon() } catch (e) { await patchAppConfig({ sysProxy: { enable: previousState } }) alert(e) diff --git a/src/renderer/src/components/sider/tun-switcher.tsx b/src/renderer/src/components/sider/tun-switcher.tsx index 4e95df7..9697e3a 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 } from '@renderer/utils/ipc' +import { restartCore, updateTrayIcon } from '@renderer/utils/ipc' import { useSortable } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' import React from 'react' @@ -84,6 +84,7 @@ const TunSwitcher: React.FC = (props) => { await restartCore() window.electron.ipcRenderer.send('updateFloatingWindow') window.electron.ipcRenderer.send('updateTrayMenu') + await updateTrayIcon() } if (iconOnly) { diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index fa081ab..359f98d 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -402,6 +402,10 @@ export async function closeTrayIcon(): Promise { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('closeTrayIcon')) } +export async function updateTrayIcon(): Promise { + return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('updateTrayIcon')) +} + export async function showMainWindow(): Promise { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showMainWindow')) }