diff --git a/src/main/cmds.ts b/src/main/cmds.ts index 8877cdb..ec47daf 100644 --- a/src/main/cmds.ts +++ b/src/main/cmds.ts @@ -19,6 +19,7 @@ import { removeProfileItem } from './config' import { restartCore } from './manager' +import { triggerSysProxy } from './sysproxy' export function registerIpcMainHandlers(): void { ipcMain.handle('mihomoVersion', mihomoVersion) @@ -39,4 +40,5 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('addProfileItem', (_e, item) => addProfileItem(item)) ipcMain.handle('removeProfileItem', (_e, id) => removeProfileItem(id)) ipcMain.handle('restartCore', () => restartCore()) + ipcMain.handle('triggerSysProxy', (_e, enable) => triggerSysProxy(enable)) } diff --git a/src/main/sysproxy.ts b/src/main/sysproxy.ts new file mode 100644 index 0000000..f37cc2c --- /dev/null +++ b/src/main/sysproxy.ts @@ -0,0 +1,17 @@ +import { controledMihomoConfig } from './config' + +export function triggerSysProxy(enable: boolean): void { + if (enable) { + enableSysProxy() + } else { + disableSysProxy() + } +} + +export function enableSysProxy(): void { + console.log('enableSysProxy', controledMihomoConfig['mixed-port']) +} + +export function disableSysProxy(): void { + console.log('disableSysProxy') +} diff --git a/src/main/template.ts b/src/main/template.ts index 76937f2..73b5383 100644 --- a/src/main/template.ts +++ b/src/main/template.ts @@ -1,6 +1,7 @@ export const defaultConfig: IAppConfig = { core: 'mihomo', - silentStart: false + silentStart: false, + sysProxy: { enable: false, mode: 'manual' } } export const defaultControledMihomoConfig: Partial = { @@ -9,7 +10,8 @@ export const defaultControledMihomoConfig: Partial = { mode: 'rule', 'mixed-port': 7890, 'allow-lan': false, - 'log-level': 'info' + 'log-level': 'info', + tun: { enable: false } } export const defaultProfileConfig: IProfileConfig = { diff --git a/src/main/tray.ts b/src/main/tray.ts index 78d26fd..d0dfeec 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -1,9 +1,11 @@ -import { controledMihomoConfig, setControledMihomoConfig } from './config' +import { appConfig, controledMihomoConfig, setAppConfig, setControledMihomoConfig } from './config' import icoIcon from '../../resources/icon.ico?asset' import pngIcon from '../../resources/icon.png?asset' import { patchMihomoConfig } from './mihomoApi' import { window } from '.' -import { app, ipcMain, Menu, Tray } from 'electron' +import { app, ipcMain, Menu, shell, Tray } from 'electron' +import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from './dirs' +import { triggerSysProxy } from './sysproxy' let tray: Tray | null = null @@ -55,6 +57,58 @@ const buildContextMenu = (): Menu => { } }, { type: 'separator' }, + { + type: 'checkbox', + label: '系统代理', + checked: appConfig.sysProxy.enable, + click: (item): void => { + const enable = item.checked + setAppConfig({ sysProxy: { enable } }) + triggerSysProxy(enable) + window?.webContents.send('appConfigUpdated') + updateTrayMenu() + } + }, + { + type: 'checkbox', + label: '虚拟网卡', + checked: controledMihomoConfig.tun?.enable ?? false, + click: (item): void => { + const enable = item.checked + setControledMihomoConfig({ tun: { enable } }) + patchMihomoConfig({ tun: { enable } }) + window?.webContents.send('controledMihomoConfigUpdated') + updateTrayMenu() + } + }, + { type: 'separator' }, + { + type: 'submenu', + label: '打开目录', + submenu: [ + { + type: 'normal', + label: '应用目录', + click: (): Promise => shell.openPath(dataDir) + }, + { + type: 'normal', + label: '工作目录', + click: (): Promise => shell.openPath(mihomoWorkDir()) + }, + { + type: 'normal', + label: '内核目录', + click: (): Promise => shell.openPath(mihomoCoreDir()) + }, + { + type: 'normal', + label: '日志目录', + click: (): Promise => shell.openPath(logDir()) + } + ] + }, + { type: 'separator' }, { id: 'restart', label: '重启应用', @@ -80,6 +134,9 @@ export function createTray(): void { ipcMain.on('controledMihomoConfigUpdated', () => { updateTrayMenu() }) + ipcMain.on('appConfigUpdated', () => { + updateTrayMenu() + }) tray.setContextMenu(menu) tray.setIgnoreDoubleClickEvents(true) diff --git a/src/renderer/src/components/sider/sysproxy-switcher.tsx b/src/renderer/src/components/sider/sysproxy-switcher.tsx index a7120b0..7fab62b 100644 --- a/src/renderer/src/components/sider/sysproxy-switcher.tsx +++ b/src/renderer/src/components/sider/sysproxy-switcher.tsx @@ -1,13 +1,22 @@ import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react' -import React, { useState } from 'react' -import { AiOutlineGlobal } from 'react-icons/ai' import { useLocation, useNavigate } from 'react-router-dom' +import { useAppConfig } from '@renderer/hooks/use-config' +import { AiOutlineGlobal } from 'react-icons/ai' +import React from 'react' +import { triggerSysProxy } from '@renderer/utils/ipc' const SysproxySwitcher: React.FC = () => { const navigate = useNavigate() const location = useLocation() const match = location.pathname.includes('/sysproxy') - const [enable, setEnable] = useState(false) + const { appConfig, patchAppConfig } = useAppConfig() + const { sysProxy } = appConfig || {} + const { enable } = sysProxy || {} + + const onChange = async (enable: boolean): Promise => { + await patchAppConfig({ sysProxy: { enable } }) + await triggerSysProxy(enable) + } return ( { }} size="sm" isSelected={enable} - onValueChange={setEnable} + onValueChange={onChange} /> diff --git a/src/renderer/src/components/sider/tun-switcher.tsx b/src/renderer/src/components/sider/tun-switcher.tsx index 6d7728a..94a7c03 100644 --- a/src/renderer/src/components/sider/tun-switcher.tsx +++ b/src/renderer/src/components/sider/tun-switcher.tsx @@ -1,13 +1,23 @@ import { Button, Card, CardBody, CardFooter, Switch } from '@nextui-org/react' -import React, { useState } from 'react' +import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb' import { useLocation, useNavigate } from 'react-router-dom' +import { patchMihomoConfig } from '@renderer/utils/ipc' +import React from 'react' const TunSwitcher: React.FC = () => { const navigate = useNavigate() const location = useLocation() const match = location.pathname.includes('/tun') - const [enable, setEnable] = useState(false) + + const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig() + const { tun } = controledMihomoConfig || {} + const { enable } = tun || {} + + const onChange = async (enable: boolean): Promise => { + await patchControledMihomoConfig({ tun: { enable } }) + await patchMihomoConfig({ tun: { enable } }) + } return ( { }} size="sm" isSelected={enable} - onValueChange={setEnable} + onValueChange={onChange} /> diff --git a/src/renderer/src/hooks/use-config.tsx b/src/renderer/src/hooks/use-config.tsx index 1bafa04..d50d1ca 100644 --- a/src/renderer/src/hooks/use-config.tsx +++ b/src/renderer/src/hooks/use-config.tsx @@ -1,5 +1,6 @@ import useSWR from 'swr' import { getAppConfig, setAppConfig } from '@renderer/utils/ipc' +import { useEffect } from 'react' interface RetuenType { appConfig: IAppConfig | undefined @@ -13,8 +14,18 @@ export const useAppConfig = (): RetuenType => { const patchAppConfig = async (value: Partial): Promise => { await setAppConfig(value) mutateAppConfig() + window.electron.ipcRenderer.send('appConfigUpdated') } + useEffect(() => { + window.electron.ipcRenderer.on('appConfigUpdated', () => { + mutateAppConfig() + }) + return (): void => { + window.electron.ipcRenderer.removeAllListeners('appConfigUpdated') + } + }, []) + return { appConfig, mutateAppConfig, diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index be7548f..a3e573f 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -69,3 +69,7 @@ export async function removeProfileItem(id: string): Promise { export async function restartCore(): Promise { await window.electron.ipcRenderer.invoke('restartCore') } + +export async function triggerSysProxy(enable: boolean): Promise { + await window.electron.ipcRenderer.invoke('triggerSysProxy', enable) +} diff --git a/src/shared/types.d.ts b/src/shared/types.d.ts index fb56908..a8ef726 100644 --- a/src/shared/types.d.ts +++ b/src/shared/types.d.ts @@ -63,11 +63,49 @@ interface IMihomoConnectionDetail { rulePayload: string } +interface ISysProxyConfig { + enable: boolean + mode?: 'auto' | 'manual' + bypass?: string[] + pacScript?: string +} + interface IAppConfig { core: 'mihomo' | 'mihomo-alpha' silentStart: boolean + sysProxy: ISysProxyConfig } +interface IMihomoTunConfig { + enable: boolean + stack?: 'system' | 'gvisor' | 'mixed' + 'auto-route'?: boolean + 'auto-redirect'?: boolean + 'auto-detect-interface'?: boolean + 'dns-hijack'?: string[] + device?: string + mtu?: number + 'strict-route'?: boolean + gso?: boolean + 'gso-max-size'?: number + 'udp-timeout'?: number + 'iproute2-table-index'?: number + 'iproute2-rule-index'?: number + 'endpoint-independent-nat'?: boolean + 'route-address-set'?: string[] + 'route-exclude-address-set'?: string[] + 'route-address'?: string[] + 'route-exclude-address'?: string[] + 'include-interface'?: string[] + 'exclude-interface'?: string[] + 'include-uid'?: number[] + 'include-uid-range'?: string[] + 'exclude-uid'?: number[] + 'exclude-uid-range'?: string[] + 'include-android-user'?: string[] + 'include-package'?: string[] + 'exclude-package'?: string[] +} interface IMihomoConfig { 'external-controller': string secret?: string @@ -81,6 +119,7 @@ interface IMihomoConfig { proxies?: [] 'proxy-groups'?: [] rules?: [] + tun: IMihomoTunConfig } interface IProfileConfig {