mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/mihomo-party-org/clash-party.git
synced 2026-09-20 08:03:39 +08:00
feat: support tray icons by state (#1962)
Co-authored-by: Eugene <laueugene23@gmail.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { existsSync } from 'fs'
|
||||
import { extname } from 'path'
|
||||
import { execFileSync } from 'child_process'
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'fs'
|
||||
import { tmpdir } from 'os'
|
||||
import { extname, join } from 'path'
|
||||
import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
|
||||
import { t } from 'i18next'
|
||||
import {
|
||||
@@ -47,6 +49,7 @@ let trayMenu: Menu | null = null
|
||||
let macTrafficIconEnabled = false
|
||||
type TrayIconStatus = 'white' | 'blue' | 'green' | 'red'
|
||||
type TrayImage = Electron.NativeImage | string
|
||||
type CustomTrayIconKey = keyof ICustomTrayIcons
|
||||
const customTrayIconSize = 16
|
||||
const customTrayIconScaleFactors = [1, 1.25, 1.5, 2, 2.5, 3]
|
||||
|
||||
@@ -425,8 +428,9 @@ export async function createTray(): Promise<void> {
|
||||
ipcMain.removeAllListeners('trayIconUpdate')
|
||||
ipcMain.on('trayIconUpdate', async (_, png: string, enabled: boolean) => {
|
||||
macTrafficIconEnabled = enabled
|
||||
const { customTrayIcon = '' } = await getAppConfig()
|
||||
const customIcon = createCustomTrayImage(customTrayIcon)
|
||||
const appConfig = await getAppConfig()
|
||||
const status = await getTrayIconStatus()
|
||||
const customIcon = createCustomTrayImageForStatus(appConfig, status)
|
||||
if (customIcon) {
|
||||
tray?.setImage(customIcon)
|
||||
await updateTrayToolTip(undefined, undefined, true)
|
||||
@@ -611,6 +615,29 @@ function createMultiScaleTrayImage(icon: Electron.NativeImage): Electron.NativeI
|
||||
return fallback
|
||||
}
|
||||
|
||||
function createMacIconImage(iconPath: string): Electron.NativeImage | null {
|
||||
if (process.platform !== 'darwin') return null
|
||||
if (!['.ico', '.icns'].includes(extname(iconPath).toLowerCase())) return null
|
||||
|
||||
let tempDir = ''
|
||||
try {
|
||||
tempDir = mkdtempSync(join(tmpdir(), 'clash-party-tray-icon-'))
|
||||
const pngPath = join(tempDir, 'icon.png')
|
||||
execFileSync('sips', ['-s', 'format', 'png', iconPath, '--out', pngPath], {
|
||||
stdio: 'ignore',
|
||||
timeout: 5000
|
||||
})
|
||||
const icon = nativeImage.createFromBuffer(readFileSync(pngPath))
|
||||
return icon.isEmpty() ? null : icon
|
||||
} catch {
|
||||
return null
|
||||
} finally {
|
||||
if (tempDir) {
|
||||
rmSync(tempDir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createCustomTrayImage(customTrayIcon: string): TrayImage | null {
|
||||
if (!customTrayIcon) return null
|
||||
|
||||
@@ -623,10 +650,13 @@ function createCustomTrayImage(customTrayIcon: string): TrayImage | null {
|
||||
|
||||
if (!existsSync(customTrayIcon)) return null
|
||||
|
||||
const icon = nativeImage.createFromPath(customTrayIcon)
|
||||
const iconExt = extname(customTrayIcon).toLowerCase()
|
||||
let icon = nativeImage.createFromPath(customTrayIcon)
|
||||
if (icon.isEmpty()) {
|
||||
icon = createMacIconImage(customTrayIcon) || nativeImage.createEmpty()
|
||||
}
|
||||
if (icon.isEmpty()) return null
|
||||
|
||||
const iconExt = extname(customTrayIcon).toLowerCase()
|
||||
if (process.platform === 'win32' && iconExt === '.ico') {
|
||||
return customTrayIcon
|
||||
}
|
||||
@@ -637,6 +667,47 @@ function createCustomTrayImage(customTrayIcon: string): TrayImage | null {
|
||||
return createMultiScaleTrayImage(icon)
|
||||
}
|
||||
|
||||
function hasCustomTrayIcons(customTrayIcons?: ICustomTrayIcons): boolean {
|
||||
return Boolean(customTrayIcons && Object.values(customTrayIcons).some(Boolean))
|
||||
}
|
||||
|
||||
function getCustomTrayIconKey(status: TrayIconStatus): CustomTrayIconKey {
|
||||
switch (status) {
|
||||
case 'blue':
|
||||
return 'sysProxy'
|
||||
case 'green':
|
||||
return 'tun'
|
||||
case 'red':
|
||||
return 'tun'
|
||||
case 'white':
|
||||
default:
|
||||
return 'common'
|
||||
}
|
||||
}
|
||||
|
||||
function getCustomTrayIconForStatus(
|
||||
appConfig: IAppConfig,
|
||||
status: TrayIconStatus
|
||||
): string | undefined {
|
||||
const { customTrayIcon = '', customTrayIcons = {} } = appConfig
|
||||
const iconKey = getCustomTrayIconKey(status)
|
||||
|
||||
if (customTrayIcons[iconKey]) return customTrayIcons[iconKey]
|
||||
|
||||
if (status === 'red') {
|
||||
return customTrayIcons.tun || customTrayIcons.sysProxy || customTrayIcon
|
||||
}
|
||||
|
||||
return customTrayIcon
|
||||
}
|
||||
|
||||
function createCustomTrayImageForStatus(
|
||||
appConfig: IAppConfig,
|
||||
status: TrayIconStatus
|
||||
): TrayImage | null {
|
||||
return createCustomTrayImage(getCustomTrayIconForStatus(appConfig, status) || '')
|
||||
}
|
||||
|
||||
async function updateTrayToolTip(
|
||||
sysProxyEnabled?: boolean,
|
||||
tunEnabled?: boolean,
|
||||
@@ -647,7 +718,9 @@ async function updateTrayToolTip(
|
||||
const [{ mode, tun }, appConfig] = await Promise.all([getControledMihomoConfig(), getAppConfig()])
|
||||
const sysProxy = sysProxyEnabled ?? appConfig.sysProxy.enable
|
||||
const tunStatus = tunEnabled ?? tun?.enable === true
|
||||
const isCustomIcon = customIconEnabled ?? Boolean(appConfig.customTrayIcon)
|
||||
const isCustomIcon =
|
||||
customIconEnabled ??
|
||||
Boolean(appConfig.customTrayIcon || hasCustomTrayIcons(appConfig.customTrayIcons))
|
||||
|
||||
const modeLabel =
|
||||
mode === 'global'
|
||||
@@ -687,10 +760,11 @@ export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: bo
|
||||
const status = calculateTrayIconStatus(sysProxyEnabled, tunEnabled)
|
||||
const iconPaths = getIconPaths()
|
||||
|
||||
getAppConfig().then(async ({ disableTrayIconColor = false, customTrayIcon = '' }) => {
|
||||
getAppConfig().then(async (appConfig) => {
|
||||
if (!tray) return
|
||||
try {
|
||||
const customIcon = createCustomTrayImage(customTrayIcon)
|
||||
const { disableTrayIconColor = false } = appConfig
|
||||
const customIcon = createCustomTrayImageForStatus(appConfig, status)
|
||||
if (customIcon) {
|
||||
tray.setImage(customIcon)
|
||||
await updateTrayToolTip(sysProxyEnabled, tunEnabled, true)
|
||||
@@ -713,12 +787,13 @@ export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: bo
|
||||
export async function updateTrayIcon(): Promise<void> {
|
||||
if (!tray) return
|
||||
|
||||
const { disableTrayIconColor = false, customTrayIcon = '' } = await getAppConfig()
|
||||
const appConfig = await getAppConfig()
|
||||
const { disableTrayIconColor = false } = appConfig
|
||||
const status = await getTrayIconStatus()
|
||||
const iconPaths = getIconPaths()
|
||||
|
||||
try {
|
||||
const customIcon = createCustomTrayImage(customTrayIcon)
|
||||
const customIcon = createCustomTrayImageForStatus(appConfig, status)
|
||||
if (customIcon) {
|
||||
tray.setImage(customIcon)
|
||||
await updateTrayToolTip(undefined, undefined, true)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { exec, execFile, spawn } from 'child_process'
|
||||
import { readFile } from 'fs/promises'
|
||||
import path from 'path'
|
||||
import { promisify } from 'util'
|
||||
import { app, dialog, nativeTheme, shell } from 'electron'
|
||||
import { app, dialog, nativeImage, nativeTheme, shell } from 'electron'
|
||||
import i18next from 'i18next'
|
||||
import {
|
||||
dataDir,
|
||||
@@ -32,6 +32,11 @@ export async function readTextFile(filePath: string): Promise<string> {
|
||||
|
||||
export async function readImageFileDataURL(filePath: string): Promise<string> {
|
||||
const ext = path.extname(filePath).toLowerCase()
|
||||
if (['.ico', '.icns'].includes(ext)) {
|
||||
const icon = nativeImage.createFromPath(filePath)
|
||||
if (!icon.isEmpty()) return icon.toDataURL()
|
||||
}
|
||||
|
||||
const mimeType =
|
||||
ext === '.jpg' || ext === '.jpeg'
|
||||
? 'image/jpeg'
|
||||
@@ -39,7 +44,11 @@ export async function readImageFileDataURL(filePath: string): Promise<string> {
|
||||
? 'image/webp'
|
||||
: ext === '.gif'
|
||||
? 'image/gif'
|
||||
: 'image/png'
|
||||
: ext === '.ico'
|
||||
? 'image/x-icon'
|
||||
: ext === '.icns'
|
||||
? 'image/icns'
|
||||
: 'image/png'
|
||||
const data = await readFile(filePath)
|
||||
|
||||
return `data:${mimeType};base64,${data.toString('base64')}`
|
||||
|
||||
@@ -32,6 +32,7 @@ export const defaultConfig: IAppConfig = {
|
||||
trayProxyGroupStyle: 'default',
|
||||
disableTrayIconColor: false,
|
||||
customTrayIcon: '',
|
||||
customTrayIcons: {},
|
||||
maxLogDays: 7,
|
||||
maxLogFileSize: 10,
|
||||
disableAppLog: false,
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { toast } from '@renderer/components/base/toast'
|
||||
import { Button, Input, Select, SelectItem, Switch, Tab, Tabs, Tooltip } from '@heroui/react'
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem,
|
||||
Switch,
|
||||
Tab,
|
||||
Tabs,
|
||||
Tooltip
|
||||
} from '@heroui/react'
|
||||
import { BiCopy, BiSolidFileImport } from 'react-icons/bi'
|
||||
import useSWR from 'swr'
|
||||
import {
|
||||
@@ -27,7 +37,7 @@ import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||
import debounce from '@renderer/utils/debounce'
|
||||
import { platform } from '@renderer/utils/init'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { IoIosHelpCircle, IoMdCloudDownload } from 'react-icons/io'
|
||||
import { IoIosArrowDown, IoIosHelpCircle, IoMdCloudDownload } from 'react-icons/io'
|
||||
import { MdEditDocument } from 'react-icons/md'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SettingItem from '../base/base-setting-item'
|
||||
@@ -37,6 +47,9 @@ import CSSEditorModal from './css-editor-modal'
|
||||
import TrayIconCropModal from './tray-icon-crop-modal'
|
||||
|
||||
const rasterTrayIconPattern = /\.(png|jpe?g|webp)$/i
|
||||
const macTrayIconPattern = /\.(ico|icns)$/i
|
||||
type TrayIconCropTarget = 'custom' | keyof ICustomTrayIcons
|
||||
const customTrayIconStateKeys: (keyof ICustomTrayIcons)[] = ['common', 'sysProxy', 'tun']
|
||||
|
||||
const GeneralConfig: React.FC = () => {
|
||||
const { t, i18n } = useTranslation()
|
||||
@@ -47,6 +60,8 @@ const GeneralConfig: React.FC = () => {
|
||||
const [fetching, setFetching] = useState(false)
|
||||
const [isRelaunching, setIsRelaunching] = useState(false)
|
||||
const [trayIconCropDataURL, setTrayIconCropDataURL] = useState('')
|
||||
const [trayIconCropTarget, setTrayIconCropTarget] = useState<TrayIconCropTarget>('custom')
|
||||
const [trayIconDrawerOpen, setTrayIconDrawerOpen] = useState(false)
|
||||
const [showHardwareAccelConfirm, setShowHardwareAccelConfirm] = useState(false)
|
||||
const [pendingHardwareAccelValue, setPendingHardwareAccelValue] = useState(false)
|
||||
const { setTheme } = useTheme()
|
||||
@@ -61,6 +76,7 @@ const GeneralConfig: React.FC = () => {
|
||||
swapTrayClick = false,
|
||||
disableTrayIconColor = false,
|
||||
customTrayIcon = '',
|
||||
customTrayIcons = {},
|
||||
disableAnimations = false,
|
||||
showFloatingWindow: showFloating = false,
|
||||
spinFloatingIcon = true,
|
||||
@@ -88,6 +104,76 @@ const GeneralConfig: React.FC = () => {
|
||||
})
|
||||
}, [])
|
||||
|
||||
const hasCustomTrayIcons = Boolean(customTrayIcon || Object.values(customTrayIcons).some(Boolean))
|
||||
|
||||
const getTrayIconDisplayText = (icon?: string): string => {
|
||||
if (!icon) return t('common.default')
|
||||
return icon.startsWith('data:image/') ? t('settings.customTrayIconBase64') : icon
|
||||
}
|
||||
|
||||
const patchTrayIcon = async (target: TrayIconCropTarget, icon: string): Promise<void> => {
|
||||
if (target === 'custom') {
|
||||
await patchAppConfig({ customTrayIcon: icon })
|
||||
} else {
|
||||
await patchAppConfig({
|
||||
customTrayIcons: {
|
||||
...customTrayIcons,
|
||||
[target]: icon
|
||||
}
|
||||
})
|
||||
}
|
||||
await updateTrayIcon()
|
||||
}
|
||||
|
||||
const selectTrayIcon = async (target: TrayIconCropTarget): Promise<void> => {
|
||||
const files = await getFilePath(
|
||||
['png', 'jpg', 'jpeg', 'webp', 'ico', 'icns'],
|
||||
t('settings.customTrayIconSelect'),
|
||||
t('settings.customTrayIcon')
|
||||
)
|
||||
if (!files?.[0]) return
|
||||
if (
|
||||
rasterTrayIconPattern.test(files[0]) ||
|
||||
(platform === 'darwin' && macTrayIconPattern.test(files[0]))
|
||||
) {
|
||||
setTrayIconCropTarget(target)
|
||||
setTrayIconCropDataURL(await readImageFileDataURL(files[0]))
|
||||
return
|
||||
}
|
||||
await patchTrayIcon(target, files[0])
|
||||
}
|
||||
|
||||
const resetTrayIcon = async (target: TrayIconCropTarget): Promise<void> => {
|
||||
await patchTrayIcon(target, '')
|
||||
}
|
||||
|
||||
const renderTrayIconPicker = (
|
||||
target: TrayIconCropTarget,
|
||||
label: string,
|
||||
icon: string | undefined
|
||||
): React.ReactNode => (
|
||||
<div
|
||||
key={target}
|
||||
className="grid min-w-0 grid-cols-[7rem_minmax(0,1fr)_auto_auto] items-center gap-2 rounded-md px-2 py-1.5"
|
||||
>
|
||||
<span className="shrink-0 text-sm text-default-600">{label}</span>
|
||||
<span
|
||||
className="min-w-0 truncate text-right text-xs text-default-500"
|
||||
title={getTrayIconDisplayText(icon)}
|
||||
>
|
||||
{getTrayIconDisplayText(icon)}
|
||||
</span>
|
||||
<Button size="sm" variant="flat" onPress={() => selectTrayIcon(target)}>
|
||||
{t(icon ? 'settings.changeTrayIcon' : 'settings.selectTrayIcon')}
|
||||
</Button>
|
||||
{icon && (
|
||||
<Button size="sm" variant="light" onPress={() => resetTrayIcon(target)}>
|
||||
{t('common.default')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{openCSSEditor && (
|
||||
@@ -128,9 +214,8 @@ const GeneralConfig: React.FC = () => {
|
||||
imageDataURL={trayIconCropDataURL}
|
||||
onCancel={() => setTrayIconCropDataURL('')}
|
||||
onConfirm={async (dataURL) => {
|
||||
await patchAppConfig({ customTrayIcon: dataURL })
|
||||
await patchTrayIcon(trayIconCropTarget, dataURL)
|
||||
setTrayIconCropDataURL('')
|
||||
await updateTrayIcon()
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -405,7 +490,7 @@ const GeneralConfig: React.FC = () => {
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={disableTrayIconColor}
|
||||
isDisabled={Boolean(customTrayIcon)}
|
||||
isDisabled={hasCustomTrayIcons}
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ disableTrayIconColor: v })
|
||||
await updateTrayIcon()
|
||||
@@ -421,57 +506,50 @@ const GeneralConfig: React.FC = () => {
|
||||
</Button>
|
||||
</Tooltip>
|
||||
}
|
||||
divider
|
||||
>
|
||||
<div className="flex items-center justify-end gap-2 min-w-0 max-w-[65%]">
|
||||
{customTrayIcon && (
|
||||
<span
|
||||
className="truncate text-xs text-default-500"
|
||||
title={
|
||||
customTrayIcon.startsWith('data:image/')
|
||||
? t('settings.customTrayIconBase64')
|
||||
: customTrayIcon
|
||||
}
|
||||
>
|
||||
{customTrayIcon.startsWith('data:image/')
|
||||
? t('settings.customTrayIconBase64')
|
||||
: customTrayIcon}
|
||||
</span>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="flat"
|
||||
onPress={async () => {
|
||||
const files = await getFilePath(
|
||||
['png', 'jpg', 'jpeg', 'webp', 'ico', 'icns'],
|
||||
t('settings.customTrayIconSelect'),
|
||||
t('settings.customTrayIcon')
|
||||
)
|
||||
if (!files?.[0]) return
|
||||
if (rasterTrayIconPattern.test(files[0])) {
|
||||
setTrayIconCropDataURL(await readImageFileDataURL(files[0]))
|
||||
return
|
||||
}
|
||||
await patchAppConfig({ customTrayIcon: files[0] })
|
||||
await updateTrayIcon()
|
||||
}}
|
||||
<div className="flex min-w-0 max-w-[68%] items-center justify-end gap-2">
|
||||
<span
|
||||
className="min-w-0 truncate text-xs text-default-500"
|
||||
title={getTrayIconDisplayText(customTrayIcon)}
|
||||
>
|
||||
{getTrayIconDisplayText(customTrayIcon)}
|
||||
</span>
|
||||
<Button size="sm" variant="flat" onPress={() => selectTrayIcon('custom')}>
|
||||
{t(customTrayIcon ? 'settings.changeTrayIcon' : 'settings.selectTrayIcon')}
|
||||
</Button>
|
||||
{customTrayIcon && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
onPress={async () => {
|
||||
await patchAppConfig({ customTrayIcon: '' })
|
||||
await updateTrayIcon()
|
||||
}}
|
||||
>
|
||||
<Button size="sm" variant="light" onPress={() => resetTrayIcon('custom')}>
|
||||
{t('common.default')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
endContent={
|
||||
<IoIosArrowDown
|
||||
className={`text-sm transition-transform ${trayIconDrawerOpen ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
}
|
||||
onPress={() => setTrayIconDrawerOpen((v) => !v)}
|
||||
>
|
||||
{t('settings.customTrayIconStates')}
|
||||
</Button>
|
||||
</div>
|
||||
</SettingItem>
|
||||
{trayIconDrawerOpen && (
|
||||
<div className="mb-2 ml-4 mr-1 mt-2 rounded-lg border border-default-200 bg-default-50/40 p-2">
|
||||
<div className="flex flex-col gap-1">
|
||||
{customTrayIconStateKeys.map((key) =>
|
||||
renderTrayIconPicker(
|
||||
key,
|
||||
t(`settings.customTrayIcon.${key}`),
|
||||
customTrayIcons[key]
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Divider className="my-2" />
|
||||
</>
|
||||
)}
|
||||
{platform !== 'linux' && (
|
||||
|
||||
@@ -100,10 +100,15 @@
|
||||
"settings.trayProxyGroupStyleSubmenu": "Submenu",
|
||||
"settings.disableTrayIconColor": "Disable Tray Icon Color Changes",
|
||||
"settings.customTrayIcon": "Custom Tray Icon",
|
||||
"settings.customTrayIconTooltip": "When set, the tray always uses this icon. System proxy, TUN, and mode state are annotated in the tray tooltip.",
|
||||
"settings.customTrayIconTooltip": "Set a fallback tray icon or separate icons for Off, System Proxy, and TUN. Unset states fall back to the fallback icon or the default icon.",
|
||||
"settings.customTrayIconSelect": "Select Tray Icon",
|
||||
"settings.cropTrayIcon": "Crop Tray Icon",
|
||||
"settings.customTrayIconBase64": "Cropped icon stored",
|
||||
"settings.customTrayIconFallback": "Fallback",
|
||||
"settings.customTrayIconStates": "State Icons",
|
||||
"settings.customTrayIcon.common": "Off",
|
||||
"settings.customTrayIcon.sysProxy": "System Proxy",
|
||||
"settings.customTrayIcon.tun": "TUN",
|
||||
"settings.selectTrayIcon": "Select Icon",
|
||||
"settings.changeTrayIcon": "Change Icon",
|
||||
"settings.disableAnimations": "Disable Animation Effects",
|
||||
|
||||
@@ -95,10 +95,15 @@
|
||||
"settings.trayProxyGroupStyleSubmenu": "زیرمنو",
|
||||
"settings.disableTrayIconColor": "غیرفعال کردن تغییر رنگ آیکون تری",
|
||||
"settings.customTrayIcon": "آیکون سفارشی سیستمتری",
|
||||
"settings.customTrayIconTooltip": "وقتی تنظیم شود، تری همیشه از این آیکون استفاده میکند. وضعیت پروکسی سیستم، TUN و حالت در راهنمای تری نمایش داده میشود.",
|
||||
"settings.customTrayIconTooltip": "یک آیکون جایگزین یا آیکونهای جداگانه برای خاموش، پراکسی سیستم و TUN تنظیم کنید. وضعیتهای تنظیمنشده از آیکون جایگزین یا پیشفرض استفاده میکنند.",
|
||||
"settings.customTrayIconSelect": "انتخاب آیکون تری",
|
||||
"settings.cropTrayIcon": "برش آیکون تری",
|
||||
"settings.customTrayIconBase64": "آیکون برشخورده ذخیره شد",
|
||||
"settings.customTrayIconFallback": "جایگزین",
|
||||
"settings.customTrayIconStates": "آیکونهای وضعیت",
|
||||
"settings.customTrayIcon.common": "خاموش",
|
||||
"settings.customTrayIcon.sysProxy": "پراکسی سیستم",
|
||||
"settings.customTrayIcon.tun": "TUN",
|
||||
"settings.selectTrayIcon": "انتخاب آیکون",
|
||||
"settings.changeTrayIcon": "تغییر آیکون",
|
||||
"settings.disableAnimations": "غیرفعال کردن جلوههای انیمیشن",
|
||||
|
||||
@@ -97,10 +97,15 @@
|
||||
"settings.trayProxyGroupStyleSubmenu": "Подменю",
|
||||
"settings.disableTrayIconColor": "Отключить смену цвета значка в трее",
|
||||
"settings.customTrayIcon": "Пользовательский значок в трее",
|
||||
"settings.customTrayIconTooltip": "Если задано, трей всегда использует этот значок. Состояние системного прокси, TUN и режима указано в подсказке трея.",
|
||||
"settings.customTrayIconTooltip": "Укажите запасной значок или отдельные значки для состояний выключено, системный прокси и TUN. Незаполненные состояния используют запасной или стандартный значок.",
|
||||
"settings.customTrayIconSelect": "Выбрать значок трея",
|
||||
"settings.cropTrayIcon": "Обрезать значок трея",
|
||||
"settings.customTrayIconBase64": "Обрезанный значок сохранен",
|
||||
"settings.customTrayIconFallback": "Запасной",
|
||||
"settings.customTrayIconStates": "Значки состояний",
|
||||
"settings.customTrayIcon.common": "Выключено",
|
||||
"settings.customTrayIcon.sysProxy": "Системный прокси",
|
||||
"settings.customTrayIcon.tun": "TUN",
|
||||
"settings.selectTrayIcon": "Выбрать значок",
|
||||
"settings.changeTrayIcon": "Изменить значок",
|
||||
"settings.disableAnimations": "Отключить анимационные эффекты",
|
||||
|
||||
@@ -100,10 +100,15 @@
|
||||
"settings.trayProxyGroupStyleSubmenu": "子菜单",
|
||||
"settings.disableTrayIconColor": "禁用托盘图标颜色变化",
|
||||
"settings.customTrayIcon": "自定义托盘图标",
|
||||
"settings.customTrayIconTooltip": "启用后托盘将始终使用此图标;系统代理、TUN 和模式状态改由托盘 Tooltip 标注。",
|
||||
"settings.customTrayIconTooltip": "可设置备用图标或按关闭、系统代理、TUN 状态分别设置图标;未设置的状态将回退到备用图标或默认图标。",
|
||||
"settings.customTrayIconSelect": "选择托盘图标",
|
||||
"settings.cropTrayIcon": "裁剪托盘图标",
|
||||
"settings.customTrayIconBase64": "已储存裁剪图标",
|
||||
"settings.customTrayIconFallback": "备用",
|
||||
"settings.customTrayIconStates": "状态图标",
|
||||
"settings.customTrayIcon.common": "关闭",
|
||||
"settings.customTrayIcon.sysProxy": "系统代理",
|
||||
"settings.customTrayIcon.tun": "TUN",
|
||||
"settings.selectTrayIcon": "选择图标",
|
||||
"settings.changeTrayIcon": "更换图标",
|
||||
"settings.disableAnimations": "禁用动画效果",
|
||||
|
||||
@@ -100,10 +100,15 @@
|
||||
"settings.trayProxyGroupStyleSubmenu": "子菜單",
|
||||
"settings.disableTrayIconColor": "禁用托盤圖標顏色變化",
|
||||
"settings.customTrayIcon": "自定義托盤圖標",
|
||||
"settings.customTrayIconTooltip": "啟用後托盤將始終使用此圖標;系統代理、TUN 和模式狀態改由托盤 Tooltip 標註。",
|
||||
"settings.customTrayIconTooltip": "可設定備用圖標或按關閉、系統代理、TUN 狀態分別設定圖標;未設定的狀態將回退到備用圖標或默認圖標。",
|
||||
"settings.customTrayIconSelect": "選擇托盤圖標",
|
||||
"settings.cropTrayIcon": "裁剪托盤圖標",
|
||||
"settings.customTrayIconBase64": "已儲存裁剪圖標",
|
||||
"settings.customTrayIconFallback": "備用",
|
||||
"settings.customTrayIconStates": "狀態圖標",
|
||||
"settings.customTrayIcon.common": "關閉",
|
||||
"settings.customTrayIcon.sysProxy": "系統代理",
|
||||
"settings.customTrayIcon.tun": "TUN",
|
||||
"settings.selectTrayIcon": "選擇圖標",
|
||||
"settings.changeTrayIcon": "更換圖標",
|
||||
"settings.disableAnimations": "禁用動畫效果",
|
||||
|
||||
7
src/shared/types.d.ts
vendored
7
src/shared/types.d.ts
vendored
@@ -258,6 +258,12 @@ interface INetworkLatencyTarget {
|
||||
url: string
|
||||
}
|
||||
|
||||
interface ICustomTrayIcons {
|
||||
common?: string
|
||||
sysProxy?: string
|
||||
tun?: string
|
||||
}
|
||||
|
||||
interface IAppConfig {
|
||||
core: 'mihomo' | 'mihomo-alpha' | 'mihomo-smart' | 'mihomo-specific'
|
||||
specificVersion?: string
|
||||
@@ -359,6 +365,7 @@ interface IAppConfig {
|
||||
showTraffic?: boolean
|
||||
disableTrayIconColor?: boolean
|
||||
customTrayIcon?: string
|
||||
customTrayIcons?: ICustomTrayIcons
|
||||
trayProxyGroupStyle?: 'default' | 'submenu'
|
||||
disableAnimations?: boolean
|
||||
webdavUrl?: string
|
||||
|
||||
Reference in New Issue
Block a user