Compare commits

..

6 Commits

Author SHA1 Message Date
ezequielnick
45484ffff2 fix: dns/sniffer override logic 2025-08-06 17:21:51 +08:00
ezequielnick
73161d0cc2 1.8.2 Released 2025-08-06 14:45:04 +08:00
ezequielnick
578a8a559f fix: missing Smart core permission setup on Linux 2025-08-06 14:44:43 +08:00
ezequielnick
470adeb519 fix: double-click to edit override & add right-click menu support 2025-08-06 13:54:56 +08:00
ezequielnick
58e0925c5b refactor: Sniffing module with override logic 2025-08-06 13:25:57 +08:00
ezequielnick
0a064bdbb8 fix: windows first startup issue 2025-08-06 10:46:21 +08:00
21 changed files with 251 additions and 134 deletions

View File

@ -23,6 +23,7 @@ package() {
chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}" install -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop" sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"

View File

@ -29,6 +29,7 @@ package() {
cp -r $srcdir/opt/mihomo-party/resources/files ${pkgdir}/opt/mihomo-party/resources/ cp -r $srcdir/opt/mihomo-party/resources/files ${pkgdir}/opt/mihomo-party/resources/
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}" install -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop" install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
install -Dm644 "${pkgdir}/opt/mihomo-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png" install -Dm644 "${pkgdir}/opt/mihomo-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png"

View File

@ -39,6 +39,7 @@ package() {
cp -r $srcdir/${_pkgname}-${pkgver}/extra/files ${pkgdir}/opt/mihomo-party/resources/ cp -r $srcdir/${_pkgname}-${pkgver}/extra/files ${pkgdir}/opt/mihomo-party/resources/
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}" install -Dm755 "${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop" install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
install -Dm644 "${pkgdir}/opt/mihomo-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png" install -Dm644 "${pkgdir}/opt/mihomo-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png"

View File

@ -41,6 +41,7 @@ package() {
chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${srcdir}/../${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}" install -Dm755 "${srcdir}/../${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop" sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"

View File

@ -36,6 +36,7 @@ package() {
chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party chmod +x ${pkgdir}/opt/mihomo-party/mihomo-party
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx ${pkgdir}/opt/mihomo-party/resources/sidecar/mihomo-smart
install -Dm755 "${srcdir}/../${pkgname}.sh" "${pkgdir}/usr/bin/${pkgname}" install -Dm755 "${srcdir}/../${pkgname}.sh" "${pkgdir}/usr/bin/${pkgname}"
sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${pkgname}.desktop" sed -i '3s!/opt/mihomo-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${pkgname}.desktop"

View File

@ -13,6 +13,7 @@ fi
chmod 4755 '/opt/mihomo-party/chrome-sandbox' || true chmod 4755 '/opt/mihomo-party/chrome-sandbox' || true
chmod +sx /opt/mihomo-party/resources/sidecar/mihomo chmod +sx /opt/mihomo-party/resources/sidecar/mihomo
chmod +sx /opt/mihomo-party/resources/sidecar/mihomo-alpha chmod +sx /opt/mihomo-party/resources/sidecar/mihomo-alpha
chmod +sx /opt/mihomo-party/resources/sidecar/mihomo-smart
if hash update-mime-database 2>/dev/null; then if hash update-mime-database 2>/dev/null; then
update-mime-database /usr/share/mime || true update-mime-database /usr/share/mime || true

View File

@ -1,7 +1,24 @@
## 1.8.2
**本次更新主要集中在重大内核更新和依赖升级后所产生的 bug 修复解决了自1.7版以后首次安装无法启动的问题,推荐更新**
### 新功能 (Feat)
- 重构 域名嗅探 卡片模块,改为“覆写”逻辑,当开关打开后,使用 嗅探覆写 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
- 订阅/覆写卡片可右键呼出菜单
- MacOS 下“轻触(tap)”触控板可进行开关操作(之前必须“按下(click)”)
### 修复 (Fix)
- **因多国语言带来的在 Windows 下首次安装无法启动的问题**
- 1.8.1升级依赖导致的节点圆角显示失效
- DNS 覆写模块的逻辑冲突
- 点击订阅卡片功能区导致的选中订阅问题
- 覆写卡片可以双击编辑
- 因代码不规范导致的控制台警告
- Linux 下没有设置 Smart 内核权限导致的“外部控制监听错误”
## 1.8.1 ## 1.8.1
### 新功能 (Feat) ### 新功能 (Feat)
- 重构 DNS 控制模块改为“覆写”逻辑当开关打开后使用DNS 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置 - 重构 DNS 卡片模块改为“覆写”逻辑当开关打开后使用DNS 设置中的配置覆盖订阅原始配置,关闭开关恢复订阅原始配置
### 性能提升(Perf) ### 性能提升(Perf)
- 更新依赖,提升页面响应性 - 更新依赖,提升页面响应性

View File

@ -1,6 +1,6 @@
{ {
"name": "mihomo-party", "name": "mihomo-party",
"version": "1.8.1", "version": "1.8.2",
"description": "Mihomo Party", "description": "Mihomo Party",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "mihomo-party-org", "author": "mihomo-party-org",

View File

@ -20,23 +20,7 @@ export async function getControledMihomoConfig(force = false): Promise<Partial<I
export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> { export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
const { useNameserverPolicy, controlDns = true, controlSniff = true } = await getAppConfig() const { useNameserverPolicy, controlDns = true, controlSniff = true } = await getAppConfig()
if (!controlDns) {
delete controledMihomoConfig.dns
delete controledMihomoConfig.hosts
} else {
// 从不接管状态恢复
if (controledMihomoConfig.dns?.ipv6 === undefined) {
controledMihomoConfig.dns = defaultControledMihomoConfig.dns
}
}
if (!controlSniff) {
delete controledMihomoConfig.sniffer
} else {
// 从不接管状态恢复
if (!controledMihomoConfig.sniffer) {
controledMihomoConfig.sniffer = defaultControledMihomoConfig.sniffer
}
}
if (patch.hosts) { if (patch.hosts) {
controledMihomoConfig.hosts = patch.hosts controledMihomoConfig.hosts = patch.hosts
} }
@ -45,12 +29,37 @@ export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>):
controledMihomoConfig.dns['nameserver-policy'] = patch.dns['nameserver-policy'] controledMihomoConfig.dns['nameserver-policy'] = patch.dns['nameserver-policy']
} }
controledMihomoConfig = deepMerge(controledMihomoConfig, patch) controledMihomoConfig = deepMerge(controledMihomoConfig, patch)
// 覆写开关控制
let configForProfile = { ...controledMihomoConfig }
if (!controlDns) {
delete configForProfile.dns
delete configForProfile.hosts
} else {
if (configForProfile.dns?.ipv6 === undefined) {
configForProfile.dns = defaultControledMihomoConfig.dns
}
}
if (!controlSniff) {
delete configForProfile.sniffer
} else {
if (!configForProfile.sniffer) {
configForProfile.sniffer = defaultControledMihomoConfig.sniffer
}
}
if (!useNameserverPolicy) { if (!useNameserverPolicy) {
delete controledMihomoConfig?.dns?.['nameserver-policy'] delete configForProfile?.dns?.['nameserver-policy']
} }
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
delete controledMihomoConfig?.tun?.device delete configForProfile?.tun?.device
} }
const originalConfig = controledMihomoConfig
controledMihomoConfig = configForProfile
await generateProfile() await generateProfile()
controledMihomoConfig = originalConfig
await writeFile(controledMihomoConfigPath(), yaml.stringify(controledMihomoConfig), 'utf-8') await writeFile(controledMihomoConfigPath(), yaml.stringify(controledMihomoConfig), 'utf-8')
} }

View File

@ -39,6 +39,7 @@ import os from 'os'
import { createWriteStream, existsSync } from 'fs' import { createWriteStream, existsSync } from 'fs'
import { uploadRuntimeConfig } from '../resolve/gistApi' import { uploadRuntimeConfig } from '../resolve/gistApi'
import { startMonitor } from '../resolve/trafficMonitor' import { startMonitor } from '../resolve/trafficMonitor'
import { safeShowErrorBox } from '../utils/init'
import i18next from '../../shared/i18n' import i18next from '../../shared/i18n'
chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}).on('unlinkDir', async () => { chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}).on('unlinkDir', async () => {
@ -46,7 +47,7 @@ chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {}).on('unlinkDir', as
await stopCore(true) await stopCore(true)
await startCore() await startCore()
} catch (e) { } catch (e) {
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`) safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
} }
}) })
@ -228,7 +229,7 @@ export async function keepCoreAlive(): Promise<void> {
await writeFile(path.join(dataDir(), 'core.pid'), child.pid.toString()) await writeFile(path.join(dataDir(), 'core.pid'), child.pid.toString())
} }
} catch (e) { } catch (e) {
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`) safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
} }
} }

View File

@ -18,12 +18,31 @@ import { initProfileUpdater } from './core/profileUpdater'
import { existsSync, writeFileSync } from 'fs' import { existsSync, writeFileSync } from 'fs'
import { exePath, taskDir } from './utils/dirs' import { exePath, taskDir } from './utils/dirs'
import path from 'path' import path from 'path'
import iconv from 'iconv-lite'
import { startMonitor } from './resolve/trafficMonitor' import { startMonitor } from './resolve/trafficMonitor'
import { showFloatingWindow } from './resolve/floatingWindow' import { showFloatingWindow } from './resolve/floatingWindow'
import iconv from 'iconv-lite'
import { initI18n } from '../shared/i18n' import { initI18n } from '../shared/i18n'
import i18next from 'i18next' import i18next from 'i18next'
// 错误处理
function showSafeErrorBox(titleKey: string, message: string): void {
let title: string
try {
title = i18next.t(titleKey)
if (!title || title === titleKey) throw new Error('Translation not ready')
} catch {
const isZh = app.getLocale().startsWith('zh')
const fallbacks: Record<string, { zh: string; en: string }> = {
'common.error.initFailed': { zh: '应用初始化失败', en: 'Application initialization failed' },
'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' },
'profiles.error.importFailed': { zh: '配置导入失败', en: 'Profile import failed' },
'common.error.adminRequired': { zh: '需要管理员权限', en: 'Administrator privileges required' }
}
title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error')
}
dialog.showErrorBox(title, message)
}
async function fixUserDataPermissions(): Promise<void> { async function fixUserDataPermissions(): Promise<void> {
if (process.platform !== 'darwin') return if (process.platform !== 'darwin') return
@ -50,6 +69,7 @@ async function fixUserDataPermissions(): Promise<void> {
let quitTimeout: NodeJS.Timeout | null = null let quitTimeout: NodeJS.Timeout | null = null
export let mainWindow: BrowserWindow | null = null export let mainWindow: BrowserWindow | null = null
// Windows 管理员权限检查(仅在生产模式下)
if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')) { if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')) {
try { try {
createElevateTask() createElevateTask()
@ -74,10 +94,7 @@ if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')
} catch { } catch {
// ignore // ignore
} }
dialog.showErrorBox( showSafeErrorBox('common.error.adminRequired', `${createErrorStr}\n${eStr}`)
i18next.t('common.error.adminRequired'),
`${i18next.t('common.error.adminRequired')}\n${createErrorStr}\n${eStr}`
)
} finally { } finally {
app.exit() app.exit()
} }
@ -171,6 +188,9 @@ app.whenReady().then(async () => {
electronApp.setAppUserModelId('party.mihomo.app') electronApp.setAppUserModelId('party.mihomo.app')
try { try {
// 首先等待初始化完成,确保配置文件和目录都已创建
await initPromise
const appConfig = await getAppConfig() const appConfig = await getAppConfig()
// 如果配置中没有语言设置,则使用系统语言 // 如果配置中没有语言设置,则使用系统语言
if (!appConfig.language) { if (!appConfig.language) {
@ -179,9 +199,8 @@ app.whenReady().then(async () => {
appConfig.language = systemLanguage appConfig.language = systemLanguage
} }
await initI18n({ lng: appConfig.language }) await initI18n({ lng: appConfig.language })
await initPromise
} catch (e) { } catch (e) {
dialog.showErrorBox(i18next.t('common.error.initFailed'), `${e}`) showSafeErrorBox('common.error.initFailed', `${e}`)
app.quit() app.quit()
} }
try { try {
@ -190,7 +209,7 @@ app.whenReady().then(async () => {
await initProfileUpdater() await initProfileUpdater()
}) })
} catch (e) { } catch (e) {
dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`) showSafeErrorBox('mihomo.error.coreStartFailed', `${e}`)
} }
try { try {
await startMonitor() await startMonitor()
@ -242,7 +261,7 @@ async function handleDeepLink(url: string): Promise<void> {
new Notification({ title: i18next.t('profiles.notification.importSuccess') }).show() new Notification({ title: i18next.t('profiles.notification.importSuccess') }).show()
break break
} catch (e) { } catch (e) {
dialog.showErrorBox(i18next.t('profiles.error.importFailed'), `${url}\n${e}`) showSafeErrorBox('profiles.error.importFailed', `${url}\n${e}`)
} }
} }
} }

View File

@ -39,8 +39,25 @@ import {
patchAppConfig, patchAppConfig,
patchControledMihomoConfig patchControledMihomoConfig
} from '../config' } from '../config'
import { app } from 'electron' import { app, dialog } from 'electron'
import { startSSIDCheck } from '../sys/ssid' import { startSSIDCheck } from '../sys/ssid'
import i18next from '../../shared/i18n'
// 安全错误处理
export function safeShowErrorBox(titleKey: string, message: string): void {
let title: string
try {
title = i18next.t(titleKey)
if (!title || title === titleKey) throw new Error('Translation not ready')
} catch {
const isZh = process.env.LANG?.startsWith('zh') || process.env.LC_ALL?.startsWith('zh')
const fallbacks: Record<string, { zh: string; en: string }> = {
'mihomo.error.coreStartFailed': { zh: '内核启动出错', en: 'Core start failed' }
}
title = fallbacks[titleKey] ? (isZh ? fallbacks[titleKey].zh : fallbacks[titleKey].en) : (isZh ? '错误' : 'Error')
}
dialog.showErrorBox(title, message)
}
async function fixDataDirPermissions(): Promise<void> { async function fixDataDirPermissions(): Promise<void> {
if (process.platform !== 'darwin') return if (process.platform !== 'darwin') return
@ -68,47 +85,48 @@ async function fixDataDirPermissions(): Promise<void> {
async function initDirs(): Promise<void> { async function initDirs(): Promise<void> {
await fixDataDirPermissions() await fixDataDirPermissions()
if (!existsSync(dataDir())) { // 按依赖顺序创建目录
await mkdir(dataDir()) const dirsToCreate = [
} dataDir(),
if (!existsSync(themesDir())) { themesDir(),
await mkdir(themesDir()) profilesDir(),
} overrideDir(),
if (!existsSync(profilesDir())) { mihomoWorkDir(),
await mkdir(profilesDir()) logDir(),
} mihomoTestDir(),
if (!existsSync(overrideDir())) { subStoreDir()
await mkdir(overrideDir()) ]
}
if (!existsSync(mihomoWorkDir())) { for (const dir of dirsToCreate) {
await mkdir(mihomoWorkDir()) try {
} if (!existsSync(dir)) {
if (!existsSync(logDir())) { await mkdir(dir, { recursive: true })
await mkdir(logDir()) }
} } catch (error) {
if (!existsSync(mihomoTestDir())) { console.error(`Failed to create directory ${dir}:`, error)
await mkdir(mihomoTestDir()) throw new Error(`Failed to create directory ${dir}: ${error}`)
} }
if (!existsSync(subStoreDir())) {
await mkdir(subStoreDir())
} }
} }
async function initConfig(): Promise<void> { async function initConfig(): Promise<void> {
if (!existsSync(appConfigPath())) { const configs = [
await writeFile(appConfigPath(), yaml.stringify(defaultConfig)) { path: appConfigPath(), content: defaultConfig, name: 'app config' },
} { path: profileConfigPath(), content: defaultProfileConfig, name: 'profile config' },
if (!existsSync(profileConfigPath())) { { path: overrideConfigPath(), content: defaultOverrideConfig, name: 'override config' },
await writeFile(profileConfigPath(), yaml.stringify(defaultProfileConfig)) { path: profilePath('default'), content: defaultProfile, name: 'default profile' },
} { path: controledMihomoConfigPath(), content: defaultControledMihomoConfig, name: 'mihomo config' }
if (!existsSync(overrideConfigPath())) { ]
await writeFile(overrideConfigPath(), yaml.stringify(defaultOverrideConfig))
} for (const config of configs) {
if (!existsSync(profilePath('default'))) { try {
await writeFile(profilePath('default'), yaml.stringify(defaultProfile)) if (!existsSync(config.path)) {
} await writeFile(config.path, yaml.stringify(config.content))
if (!existsSync(controledMihomoConfigPath())) { }
await writeFile(controledMihomoConfigPath(), yaml.stringify(defaultControledMihomoConfig)) } catch (error) {
console.error(`Failed to create ${config.name} at ${config.path}:`, error)
throw new Error(`Failed to create ${config.name}: ${error}`)
}
} }
} }
@ -117,13 +135,30 @@ async function initFiles(): Promise<void> {
const targetPath = path.join(mihomoWorkDir(), file) const targetPath = path.join(mihomoWorkDir(), file)
const testTargetPath = path.join(mihomoTestDir(), file) const testTargetPath = path.join(mihomoTestDir(), file)
const sourcePath = path.join(resourcesFilesDir(), file) const sourcePath = path.join(resourcesFilesDir(), file)
if (!existsSync(targetPath) && existsSync(sourcePath)) {
await cp(sourcePath, targetPath, { recursive: true }) try {
} if (!existsSync(targetPath) && existsSync(sourcePath)) {
if (!existsSync(testTargetPath) && existsSync(sourcePath)) { await cp(sourcePath, targetPath, { recursive: true })
await cp(sourcePath, testTargetPath, { recursive: true }) }
if (!existsSync(testTargetPath) && existsSync(sourcePath)) {
await cp(sourcePath, testTargetPath, { recursive: true })
}
} catch (error) {
console.error(`Failed to copy ${file}:`, error)
if (['country.mmdb', 'geoip.dat', 'geosite.dat'].includes(file)) {
throw new Error(`Failed to copy critical file ${file}: ${error}`)
}
} }
} }
// 确保工作目录存在
if (!existsSync(mihomoWorkDir())) {
await mkdir(mihomoWorkDir(), { recursive: true })
}
if (!existsSync(mihomoTestDir())) {
await mkdir(mihomoTestDir(), { recursive: true })
}
await Promise.all([ await Promise.all([
copy('country.mmdb'), copy('country.mmdb'),
copy('geoip.metadb'), copy('geoip.metadb'),

View File

@ -10,7 +10,7 @@ import {
} from '@heroui/react' } from '@heroui/react'
import { IoMdMore, IoMdRefresh } from 'react-icons/io' import { IoMdMore, IoMdRefresh } from 'react-icons/io'
import dayjs from '@renderer/utils/dayjs' import dayjs from '@renderer/utils/dayjs'
import React, { Key, useEffect, useMemo, useState } from 'react' import React, { Key, useMemo, useState } from 'react'
import EditFileModal from './edit-file-modal' import EditFileModal from './edit-file-modal'
import EditInfoModal from './edit-info-modal' import EditInfoModal from './edit-info-modal'
import { useSortable } from '@dnd-kit/sortable' import { useSortable } from '@dnd-kit/sortable'
@ -54,7 +54,7 @@ const OverrideItem: React.FC<Props> = (props) => {
id: info.id id: info.id
}) })
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
const [disableOpen, setDisableOpen] = useState(false) const [dropdownOpen, setDropdownOpen] = useState(false)
const menuItems: MenuItem[] = useMemo(() => { const menuItems: MenuItem[] = useMemo(() => {
const list = [ const list = [
{ {
@ -124,17 +124,13 @@ const OverrideItem: React.FC<Props> = (props) => {
} }
} }
useEffect(() => {
if (isDragging) {
setTimeout(() => { const handleContextMenu = (e: React.MouseEvent) => {
setDisableOpen(true) e.preventDefault()
}, 200) e.stopPropagation()
} else { setDropdownOpen(true)
setTimeout(() => { }
setDisableOpen(false)
}, 200)
}
}, [isDragging])
return ( return (
<div <div
@ -164,13 +160,21 @@ const OverrideItem: React.FC<Props> = (props) => {
<Card <Card
as="div" as="div"
fullWidth fullWidth
isPressable className="cursor-pointer"
onPress={() => { onContextMenu={handleContextMenu}
if (disableOpen) return onDoubleClick={(e) => {
if ((e.target as Element)?.closest('button, [role="menu"], [role="menuitem"]')) {
return
}
setOpenFileEditor(true) setOpenFileEditor(true)
}} }}
> >
<div ref={setNodeRef} {...attributes} {...listeners} className="h-full w-full"> <div
ref={setNodeRef}
{...attributes}
{...listeners}
className="h-full w-full"
>
<CardBody> <CardBody>
<div className="flex justify-between h-[32px]"> <div className="flex justify-between h-[32px]">
<h3 <h3
@ -206,7 +210,10 @@ const OverrideItem: React.FC<Props> = (props) => {
</Button> </Button>
)} )}
<Dropdown> <Dropdown
isOpen={dropdownOpen}
onOpenChange={setDropdownOpen}
>
<DropdownTrigger> <DropdownTrigger>
<Button isIconOnly size="sm" variant="light" color="default"> <Button isIconOnly size="sm" variant="light" color="default">
<IoMdMore color="default" className={`text-[24px]`} /> <IoMdMore color="default" className={`text-[24px]`} />

View File

@ -4,7 +4,7 @@ import SettingItem from '../base/base-setting-item'
import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react' import { Button, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
import { useAppConfig } from '@renderer/hooks/use-app-config' import { useAppConfig } from '@renderer/hooks/use-app-config'
import debounce from '@renderer/utils/debounce' import debounce from '@renderer/utils/debounce'
import { getGistUrl, patchControledMihomoConfig, restartCore } from '@renderer/utils/ipc' import { getGistUrl, restartCore } from '@renderer/utils/ipc'
import { MdDeleteForever } from 'react-icons/md' import { MdDeleteForever } from 'react-icons/md'
import { BiCopy } from 'react-icons/bi' import { BiCopy } from 'react-icons/bi'
import { IoIosHelpCircle } from 'react-icons/io' import { IoIosHelpCircle } from 'react-icons/io'
@ -16,7 +16,6 @@ const MihomoConfig: React.FC = () => {
const { appConfig, patchAppConfig } = useAppConfig() const { appConfig, patchAppConfig } = useAppConfig()
const { const {
diffWorkDir = false, diffWorkDir = false,
controlSniff = true,
delayTestConcurrency, delayTestConcurrency,
delayTestTimeout, delayTestTimeout,
githubToken = '', githubToken = '',
@ -193,21 +192,7 @@ const MihomoConfig: React.FC = () => {
/> />
</SettingItem> </SettingItem>
<SettingItem title={t('mihomo.controlSniff')} divider>
<Switch
size="sm"
isSelected={controlSniff}
onValueChange={async (v) => {
try {
await patchAppConfig({ controlSniff: v })
await patchControledMihomoConfig({})
await restartCore()
} catch (e) {
alert(e)
}
}}
/>
</SettingItem>
<SettingItem title={t('mihomo.autoCloseConnection')} divider> <SettingItem title={t('mihomo.autoCloseConnection')} divider>
<Switch <Switch
size="sm" size="sm"

View File

@ -2,7 +2,7 @@ import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'
import BorderSwitch from '@renderer/components/base/border-swtich' import BorderSwitch from '@renderer/components/base/border-swtich'
import { RiScan2Fill } from 'react-icons/ri' import { RiScan2Fill } from 'react-icons/ri'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { patchMihomoConfig } from '@renderer/utils/ipc' import { restartCore } from '@renderer/utils/ipc'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { useSortable } from '@dnd-kit/sortable' import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities' import { CSS } from '@dnd-kit/utilities'
@ -15,15 +15,13 @@ interface Props {
} }
const SniffCard: React.FC<Props> = (props) => { const SniffCard: React.FC<Props> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { appConfig } = useAppConfig() const { appConfig, patchAppConfig } = useAppConfig()
const { iconOnly } = props const { iconOnly } = props
const { sniffCardStatus = 'col-span-1', controlSniff = true } = appConfig || {} const { sniffCardStatus = 'col-span-1', controlSniff = true } = appConfig || {}
const location = useLocation() const location = useLocation()
const navigate = useNavigate() const navigate = useNavigate()
const match = location.pathname.includes('/sniffer') const match = location.pathname.includes('/sniffer')
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig() const { patchControledMihomoConfig } = useControledMihomoConfig()
const { sniffer } = controledMihomoConfig || {}
const { enable } = sniffer || {}
const { const {
attributes, attributes,
listeners, listeners,
@ -35,14 +33,19 @@ const SniffCard: React.FC<Props> = (props) => {
id: 'sniff' id: 'sniff'
}) })
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
const onChange = async (enable: boolean): Promise<void> => { const onChange = async (controlSniff: boolean): Promise<void> => {
await patchControledMihomoConfig({ sniffer: { enable } }) try {
await patchMihomoConfig({ sniffer: { enable } }) await patchAppConfig({ controlSniff })
await patchControledMihomoConfig({})
await restartCore()
} catch (e) {
alert(e)
}
} }
if (iconOnly) { if (iconOnly) {
return ( return (
<div className={`${sniffCardStatus} ${!controlSniff ? 'hidden' : ''} flex justify-center`}> <div className={`${sniffCardStatus} flex justify-center`}>
<Tooltip content={t('sider.cards.sniff')} placement="right"> <Tooltip content={t('sider.cards.sniff')} placement="right">
<Button <Button
size="sm" size="sm"
@ -68,7 +71,7 @@ const SniffCard: React.FC<Props> = (props) => {
transition, transition,
zIndex: isDragging ? 'calc(infinity)' : undefined zIndex: isDragging ? 'calc(infinity)' : undefined
}} }}
className={`${sniffCardStatus} ${!controlSniff ? 'hidden' : ''} sniff-card`} className={`${sniffCardStatus} sniff-card`}
> >
<Card <Card
fullWidth fullWidth
@ -91,8 +94,8 @@ const SniffCard: React.FC<Props> = (props) => {
/> />
</Button> </Button>
<BorderSwitch <BorderSwitch
isShowBorder={match && enable} isShowBorder={match && controlSniff}
isSelected={enable} isSelected={controlSniff}
onValueChange={onChange} onValueChange={onChange}
/> />
</div> </div>

View File

@ -216,7 +216,7 @@
"sider.cards.connections": "Connections", "sider.cards.connections": "Connections",
"sider.cards.core": "Core Settings", "sider.cards.core": "Core Settings",
"sider.cards.dns": "DNS Override", "sider.cards.dns": "DNS Override",
"sider.cards.sniff": "Sniffing", "sider.cards.sniff": "Sniff OVRD",
"sider.cards.logs": "Logs", "sider.cards.logs": "Logs",
"sider.cards.substore": "Sub-Store", "sider.cards.substore": "Sub-Store",
"sider.cards.config": "Runtime Config", "sider.cards.config": "Runtime Config",
@ -269,6 +269,7 @@
"proxies.search.placeholder": "Search Proxies", "proxies.search.placeholder": "Search Proxies",
"proxies.locate": "Locate Current Proxy", "proxies.locate": "Locate Current Proxy",
"sniffer.title": "Domain Sniffing Settings", "sniffer.title": "Domain Sniffing Settings",
"sniffer.enable": "Enable Domain Sniffing",
"sniffer.parsePureIP": "Sniff Unmapped IP Addresses", "sniffer.parsePureIP": "Sniff Unmapped IP Addresses",
"sniffer.forceDNSMapping": "Sniff Real IP Mappings", "sniffer.forceDNSMapping": "Sniff Real IP Mappings",
"sniffer.overrideDestination": "Override Connection Address", "sniffer.overrideDestination": "Override Connection Address",
@ -284,6 +285,7 @@
"sniffer.skipDstAddress.placeholder": "Example: 1.1.1.1/32", "sniffer.skipDstAddress.placeholder": "Example: 1.1.1.1/32",
"sniffer.skipSrcAddress.title": "Skip Source Address Sniffing", "sniffer.skipSrcAddress.title": "Skip Source Address Sniffing",
"sniffer.skipSrcAddress.placeholder": "Example: 192.168.1.1/24", "sniffer.skipSrcAddress.placeholder": "Example: 192.168.1.1/24",
"sniffer.saveOnly": "Save Only",
"sysproxy.title": "System Proxy", "sysproxy.title": "System Proxy",
"sysproxy.host.title": "Proxy Host", "sysproxy.host.title": "Proxy Host",
"sysproxy.host.placeholder": "Default 127.0.0.1, do not modify unless necessary", "sysproxy.host.placeholder": "Default 127.0.0.1, do not modify unless necessary",
@ -342,6 +344,7 @@
"dns.customHosts.list": "Hosts List", "dns.customHosts.list": "Hosts List",
"dns.customHosts.domainPlaceholder": "Domain", "dns.customHosts.domainPlaceholder": "Domain",
"dns.customHosts.valuePlaceholder": "Domain or IP", "dns.customHosts.valuePlaceholder": "Domain or IP",
"dns.saveOnly": "Save Only",
"profiles.title": "Profile Management", "profiles.title": "Profile Management",
"profiles.updateAll": "Update All Profiles", "profiles.updateAll": "Update All Profiles",
"profiles.useProxy": "Proxy", "profiles.useProxy": "Proxy",

View File

@ -207,7 +207,7 @@
"sider.cards.connections": "اتصالات", "sider.cards.connections": "اتصالات",
"sider.cards.core": "تنظیمات هسته", "sider.cards.core": "تنظیمات هسته",
"sider.cards.dns": "بازنویسی دی‌ان‌اس", "sider.cards.dns": "بازنویسی دی‌ان‌اس",
"sider.cards.sniff": "تشخیص دامنه", "sider.cards.sniff": "لغو بو کشیدن",
"sider.cards.logs": "گزارش‌ها", "sider.cards.logs": "گزارش‌ها",
"sider.cards.substore": "ساب استور", "sider.cards.substore": "ساب استور",
"sider.cards.config": "پیکربندی اجرا", "sider.cards.config": "پیکربندی اجرا",
@ -260,6 +260,7 @@
"proxies.search.placeholder": "جستجوی پراکسی‌ها", "proxies.search.placeholder": "جستجوی پراکسی‌ها",
"proxies.locate": "یافتن پراکسی فعلی", "proxies.locate": "یافتن پراکسی فعلی",
"sniffer.title": "تنظیمات تشخیص دامنه", "sniffer.title": "تنظیمات تشخیص دامنه",
"sniffer.enable": "فعال کردن قابلیت شنود دامنه",
"sniffer.parsePureIP": "تشخیص آدرس‌های IP بدون نگاشت", "sniffer.parsePureIP": "تشخیص آدرس‌های IP بدون نگاشت",
"sniffer.forceDNSMapping": "تشخیص نگاشت‌های IP واقعی", "sniffer.forceDNSMapping": "تشخیص نگاشت‌های IP واقعی",
"sniffer.overrideDestination": "جایگزینی آدرس اتصال", "sniffer.overrideDestination": "جایگزینی آدرس اتصال",
@ -275,6 +276,7 @@
"sniffer.skipDstAddress.placeholder": "مثال: 1.1.1.1/32", "sniffer.skipDstAddress.placeholder": "مثال: 1.1.1.1/32",
"sniffer.skipSrcAddress.title": "رد کردن تشخیص آدرس مبدا", "sniffer.skipSrcAddress.title": "رد کردن تشخیص آدرس مبدا",
"sniffer.skipSrcAddress.placeholder": "مثال: 192.168.1.1/24", "sniffer.skipSrcAddress.placeholder": "مثال: 192.168.1.1/24",
"sniffer.saveOnly": "فقط ذخیره",
"sysproxy.title": "پراکسی سیستم", "sysproxy.title": "پراکسی سیستم",
"sysproxy.host.title": "میزبان پراکسی", "sysproxy.host.title": "میزبان پراکسی",
"sysproxy.host.placeholder": "پیش‌فرض 127.0.0.1، در صورت عدم نیاز تغییر ندهید", "sysproxy.host.placeholder": "پیش‌فرض 127.0.0.1، در صورت عدم نیاز تغییر ندهید",
@ -333,6 +335,7 @@
"dns.customHosts.list": "لیست Hosts", "dns.customHosts.list": "لیست Hosts",
"dns.customHosts.domainPlaceholder": "دامنه", "dns.customHosts.domainPlaceholder": "دامنه",
"dns.customHosts.valuePlaceholder": "دامنه یا IP", "dns.customHosts.valuePlaceholder": "دامنه یا IP",
"dns.saveOnly": "فقط ذخیره",
"profiles.title": "مدیریت پروفایل", "profiles.title": "مدیریت پروفایل",
"profiles.updateAll": "به‌روزرسانی همه پروفایل‌ها", "profiles.updateAll": "به‌روزرسانی همه پروفایل‌ها",
"profiles.useProxy": "پراکسی", "profiles.useProxy": "پراکسی",

View File

@ -207,7 +207,7 @@
"sider.cards.connections": "Подключения", "sider.cards.connections": "Подключения",
"sider.cards.core": "Настройки ядра", "sider.cards.core": "Настройки ядра",
"sider.cards.dns": "Переопределение DNS", "sider.cards.dns": "Переопределение DNS",
"sider.cards.sniff": "Анализ трафика", "sider.cards.sniff": "переопределение сниффинга",
"sider.cards.logs": "Журналы", "sider.cards.logs": "Журналы",
"sider.cards.substore": "Sub-Store", "sider.cards.substore": "Sub-Store",
"sider.cards.config": "Конфигурация", "sider.cards.config": "Конфигурация",
@ -260,6 +260,7 @@
"proxies.search.placeholder": "Поиск прокси", "proxies.search.placeholder": "Поиск прокси",
"proxies.locate": "Найти текущий прокси", "proxies.locate": "Найти текущий прокси",
"sniffer.title": "Настройки анализа доменов", "sniffer.title": "Настройки анализа доменов",
"sniffer.enable": "Включить анализ домена",
"sniffer.parsePureIP": "Анализировать немаппированные IP-адреса", "sniffer.parsePureIP": "Анализировать немаппированные IP-адреса",
"sniffer.forceDNSMapping": "Анализировать реальные IP-маппинги", "sniffer.forceDNSMapping": "Анализировать реальные IP-маппинги",
"sniffer.overrideDestination": "Переопределить адрес подключения", "sniffer.overrideDestination": "Переопределить адрес подключения",
@ -275,6 +276,7 @@
"sniffer.skipDstAddress.placeholder": "Пример: 1.1.1.1/32", "sniffer.skipDstAddress.placeholder": "Пример: 1.1.1.1/32",
"sniffer.skipSrcAddress.title": "Пропустить анализ исходных адресов", "sniffer.skipSrcAddress.title": "Пропустить анализ исходных адресов",
"sniffer.skipSrcAddress.placeholder": "Пример: 192.168.1.1/24", "sniffer.skipSrcAddress.placeholder": "Пример: 192.168.1.1/24",
"sniffer.saveOnly": "Только сохранить",
"sysproxy.title": "Системный прокси", "sysproxy.title": "Системный прокси",
"sysproxy.host.title": "Хост прокси", "sysproxy.host.title": "Хост прокси",
"sysproxy.host.placeholder": "По умолчанию 127.0.0.1, не изменяйте без необходимости", "sysproxy.host.placeholder": "По умолчанию 127.0.0.1, не изменяйте без необходимости",
@ -333,6 +335,7 @@
"dns.customHosts.list": "Список Hosts", "dns.customHosts.list": "Список Hosts",
"dns.customHosts.domainPlaceholder": "Домен", "dns.customHosts.domainPlaceholder": "Домен",
"dns.customHosts.valuePlaceholder": "Домен или IP", "dns.customHosts.valuePlaceholder": "Домен или IP",
"dns.saveOnly": "Только сохранить",
"profiles.title": "Управление профилями", "profiles.title": "Управление профилями",
"profiles.updateAll": "Обновить все профили", "profiles.updateAll": "Обновить все профили",
"profiles.useProxy": "Прокси", "profiles.useProxy": "Прокси",

View File

@ -216,7 +216,7 @@
"sider.cards.connections": "连接", "sider.cards.connections": "连接",
"sider.cards.core": "内核设置", "sider.cards.core": "内核设置",
"sider.cards.dns": "DNS覆写", "sider.cards.dns": "DNS覆写",
"sider.cards.sniff": "域名嗅探", "sider.cards.sniff": "嗅探覆写",
"sider.cards.logs": "日志", "sider.cards.logs": "日志",
"sider.cards.substore": "Sub-Store", "sider.cards.substore": "Sub-Store",
"sider.cards.config": "运行时配置", "sider.cards.config": "运行时配置",
@ -269,6 +269,7 @@
"proxies.search.placeholder": "搜索节点", "proxies.search.placeholder": "搜索节点",
"proxies.locate": "定位到当前节点", "proxies.locate": "定位到当前节点",
"sniffer.title": "域名嗅探设置", "sniffer.title": "域名嗅探设置",
"sniffer.enable": "启用域名嗅探",
"sniffer.parsePureIP": "对未映射 IP 地址嗅探", "sniffer.parsePureIP": "对未映射 IP 地址嗅探",
"sniffer.forceDNSMapping": "对真实 IP 映射嗅探", "sniffer.forceDNSMapping": "对真实 IP 映射嗅探",
"sniffer.overrideDestination": "覆盖连接地址", "sniffer.overrideDestination": "覆盖连接地址",
@ -284,6 +285,7 @@
"sniffer.skipDstAddress.placeholder": "例1.1.1.1/32", "sniffer.skipDstAddress.placeholder": "例1.1.1.1/32",
"sniffer.skipSrcAddress.title": "跳过来源地址嗅探", "sniffer.skipSrcAddress.title": "跳过来源地址嗅探",
"sniffer.skipSrcAddress.placeholder": "例192.168.1.1/24", "sniffer.skipSrcAddress.placeholder": "例192.168.1.1/24",
"sniffer.saveOnly": "仅保存",
"sysproxy.title": "系统代理", "sysproxy.title": "系统代理",
"sysproxy.host.title": "代理主机", "sysproxy.host.title": "代理主机",
"sysproxy.host.placeholder": "默认 127.0.0.1 若无特殊需求请勿修改", "sysproxy.host.placeholder": "默认 127.0.0.1 若无特殊需求请勿修改",
@ -342,6 +344,7 @@
"dns.customHosts.list": "Hosts 列表", "dns.customHosts.list": "Hosts 列表",
"dns.customHosts.domainPlaceholder": "域名", "dns.customHosts.domainPlaceholder": "域名",
"dns.customHosts.valuePlaceholder": "域名或 IP", "dns.customHosts.valuePlaceholder": "域名或 IP",
"dns.saveOnly": "仅保存",
"profiles.title": "订阅管理", "profiles.title": "订阅管理",
"profiles.updateAll": "更新全部订阅", "profiles.updateAll": "更新全部订阅",
"profiles.useProxy": "代理", "profiles.useProxy": "代理",

View File

@ -13,7 +13,7 @@ const DNS: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig() const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { appConfig, patchAppConfig } = useAppConfig() const { appConfig, patchAppConfig } = useAppConfig()
const { nameserverPolicy, useNameserverPolicy } = appConfig || {} const { nameserverPolicy, useNameserverPolicy, controlDns = true } = appConfig || {}
const { dns, hosts } = controledMihomoConfig || {} const { dns, hosts } = controledMihomoConfig || {}
const { const {
enable = true, enable = true,
@ -128,8 +128,10 @@ const DNS: React.FC = () => {
try { try {
setChanged(false) setChanged(false)
await patchControledMihomoConfig(patch) await patchControledMihomoConfig(patch)
await patchMihomoConfig(patch) if (controlDns) {
await restartCore() await patchMihomoConfig(patch)
await restartCore()
}
} catch (e) { } catch (e) {
alert(e) alert(e)
} }
@ -175,7 +177,7 @@ const DNS: React.FC = () => {
onSave(result) onSave(result)
}} }}
> >
{t('common.save')} {controlDns ? t('common.save') : t('dns.saveOnly')}
</Button> </Button>
) )
} }

View File

@ -3,7 +3,8 @@ import BasePage from '@renderer/components/base/base-page'
import SettingCard from '@renderer/components/base/base-setting-card' import SettingCard from '@renderer/components/base/base-setting-card'
import SettingItem from '@renderer/components/base/base-setting-item' import SettingItem from '@renderer/components/base/base-setting-item'
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
import { restartCore } from '@renderer/utils/ipc' import { useAppConfig } from '@renderer/hooks/use-app-config'
import { restartCore, patchMihomoConfig } from '@renderer/utils/ipc'
import React, { ReactNode, useState } from 'react' import React, { ReactNode, useState } from 'react'
import { MdDeleteForever } from 'react-icons/md' import { MdDeleteForever } from 'react-icons/md'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -11,8 +12,11 @@ import { useTranslation } from 'react-i18next'
const Sniffer: React.FC = () => { const Sniffer: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig() const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { appConfig } = useAppConfig()
const { controlSniff = true } = appConfig || {}
const { sniffer } = controledMihomoConfig || {} const { sniffer } = controledMihomoConfig || {}
const { const {
enable = true,
'parse-pure-ip': parsePureIP = true, 'parse-pure-ip': parsePureIP = true,
'force-dns-mapping': forceDNSMapping = true, 'force-dns-mapping': forceDNSMapping = true,
'override-destination': overrideDestination = false, 'override-destination': overrideDestination = false,
@ -41,6 +45,7 @@ const Sniffer: React.FC = () => {
} = sniffer || {} } = sniffer || {}
const [changed, setChanged] = useState(false) const [changed, setChanged] = useState(false)
const [values, originSetValues] = useState({ const [values, originSetValues] = useState({
enable,
parsePureIP, parsePureIP,
forceDNSMapping, forceDNSMapping,
overrideDestination, overrideDestination,
@ -59,7 +64,11 @@ const Sniffer: React.FC = () => {
try { try {
setChanged(false) setChanged(false)
await patchControledMihomoConfig(patch) await patchControledMihomoConfig(patch)
await restartCore()
if (controlSniff) {
await patchMihomoConfig(patch)
await restartCore()
}
} catch (e) { } catch (e) {
alert(e) alert(e)
} }
@ -130,22 +139,34 @@ const Sniffer: React.FC = () => {
onPress={() => onPress={() =>
onSave({ onSave({
sniffer: { sniffer: {
enable: values.enable,
'parse-pure-ip': values.parsePureIP, 'parse-pure-ip': values.parsePureIP,
'force-dns-mapping': values.forceDNSMapping, 'force-dns-mapping': values.forceDNSMapping,
'override-destination': values.overrideDestination, 'override-destination': values.overrideDestination,
sniff: values.sniff, sniff: values.sniff,
'skip-domain': values.skipDomain, 'skip-domain': values.skipDomain,
'force-domain': values.forceDomain 'force-domain': values.forceDomain,
'skip-dst-address': values.skipDstAddress,
'skip-src-address': values.skipSrcAddress
} }
}) })
} }
> >
{t('common.save')} {controlSniff ? t('common.save') : t('sniffer.saveOnly')}
</Button> </Button>
) )
} }
> >
<SettingCard> <SettingCard>
<SettingItem title={t('sniffer.enable')} divider>
<Switch
size="sm"
isSelected={values.enable}
onValueChange={(v) => {
setValues({ ...values, enable: v })
}}
/>
</SettingItem>
<SettingItem title={t('sniffer.overrideDestination')} divider> <SettingItem title={t('sniffer.overrideDestination')} divider>
<Switch <Switch
size="sm" size="sm"