mirror of
https://github.com/pompurin404/mihomo-party.git
synced 2024-11-16 03:32:17 +08:00
support floating window
This commit is contained in:
parent
0d9e28f8d1
commit
1b523c94f5
|
@ -1,7 +1,8 @@
|
|||
### Breaking Changes
|
||||
|
||||
- 此版本修改了应用的显示名称,macOS用户可能无法自动更新,需要手动删除 `/Applications/mihomo-party.app`
|
||||
|
||||
### New Features
|
||||
|
||||
- 允许切换订阅卡片显示过期时间还是更新时间
|
||||
- 添加悬浮窗功能,可以在设置中开启
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 修复某些 Windows 管理员权限无法正常启动的问题
|
||||
|
|
|
@ -22,6 +22,14 @@ export default defineConfig({
|
|||
plugins: [externalizeDepsPlugin()]
|
||||
},
|
||||
renderer: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve('src/renderer/index.html'),
|
||||
floating: resolve('src/renderer/floating.html')
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve('src/renderer/src')
|
||||
|
|
|
@ -5,6 +5,7 @@ import WebSocket from 'ws'
|
|||
import { tray } from '../resolve/tray'
|
||||
import { calcTraffic } from '../utils/calc'
|
||||
import { getRuntimeConfig } from './factory'
|
||||
import { floatingWindow } from '../resolve/floatingWindow'
|
||||
|
||||
let axiosIns: AxiosInstance = null!
|
||||
let mihomoTrafficWs: WebSocket | null = null
|
||||
|
@ -202,6 +203,7 @@ const mihomoTraffic = async (): Promise<void> => {
|
|||
`${calcTraffic(json.down)}/s`.padStart(9)
|
||||
)
|
||||
}
|
||||
floatingWindow?.webContents.send('mihomoTraffic', json)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { existsSync, writeFileSync } from 'fs'
|
|||
import { exePath, taskDir } from './utils/dirs'
|
||||
import path from 'path'
|
||||
import { startMonitor } from './resolve/trafficMonitor'
|
||||
import { showFloatingWindow } from './resolve/floatingWindow'
|
||||
|
||||
let quitTimeout: NodeJS.Timeout | null = null
|
||||
export let mainWindow: BrowserWindow | null = null
|
||||
|
@ -149,8 +150,12 @@ app.whenReady().then(async () => {
|
|||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
const { showFloatingWindow: showFloating = false } = await getAppConfig()
|
||||
registerIpcMainHandlers()
|
||||
await createWindow()
|
||||
if (showFloating) {
|
||||
showFloatingWindow()
|
||||
}
|
||||
await createTray()
|
||||
await initShortcut()
|
||||
app.on('activate', function () {
|
||||
|
@ -191,7 +196,8 @@ export async function createWindow(): Promise<void> {
|
|||
const { useWindowFrame = false } = await getAppConfig()
|
||||
const mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 800,
|
||||
defaultHeight: 600
|
||||
defaultHeight: 600,
|
||||
file: 'window-state.json'
|
||||
})
|
||||
// https://github.com/electron/electron/issues/16521#issuecomment-582955104
|
||||
Menu.setApplicationMenu(null)
|
||||
|
@ -269,7 +275,6 @@ export async function createWindow(): Promise<void> {
|
|||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
|
@ -288,3 +293,9 @@ export function showMainWindow(): void {
|
|||
mainWindow.focusOnWebView()
|
||||
}
|
||||
}
|
||||
|
||||
export function closeMainWindow(): void {
|
||||
if (mainWindow) {
|
||||
mainWindow.close()
|
||||
}
|
||||
}
|
||||
|
|
75
src/main/resolve/floatingWindow.ts
Normal file
75
src/main/resolve/floatingWindow.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import { is } from '@electron-toolkit/utils'
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
import windowStateKeeper from 'electron-window-state'
|
||||
import { join } from 'path'
|
||||
import { getAppConfig } from '../config'
|
||||
import { applyTheme } from './theme'
|
||||
import { buildContextMenu } from './tray'
|
||||
|
||||
export let floatingWindow: BrowserWindow | null = null
|
||||
|
||||
async function createFloatingWindow(): Promise<void> {
|
||||
const floatingWindowState = windowStateKeeper({
|
||||
file: 'floating-window-state.json'
|
||||
})
|
||||
const { customTheme = 'default.css' } = await getAppConfig()
|
||||
floatingWindow = new BrowserWindow({
|
||||
width: 126,
|
||||
height: 50,
|
||||
x: floatingWindowState.x,
|
||||
y: floatingWindowState.y,
|
||||
show: false,
|
||||
frame: false,
|
||||
alwaysOnTop: true,
|
||||
resizable: false,
|
||||
transparent: true,
|
||||
skipTaskbar: true,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
closable: false,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
spellcheck: false,
|
||||
sandbox: false
|
||||
}
|
||||
})
|
||||
floatingWindowState.manage(floatingWindow)
|
||||
floatingWindow.on('ready-to-show', () => {
|
||||
applyTheme(customTheme)
|
||||
floatingWindow?.show()
|
||||
})
|
||||
floatingWindow.on('moved', () => {
|
||||
if (floatingWindow) floatingWindowState.saveState(floatingWindow)
|
||||
})
|
||||
ipcMain.on('updateFloatingWindow', () => {
|
||||
if (floatingWindow) {
|
||||
floatingWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
floatingWindow?.webContents.send('appConfigUpdated')
|
||||
}
|
||||
})
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
floatingWindow.loadURL(`${process.env['ELECTRON_RENDERER_URL']}/floating.html`)
|
||||
} else {
|
||||
floatingWindow.loadFile(join(__dirname, '../renderer/floating.html'))
|
||||
}
|
||||
}
|
||||
export function showFloatingWindow(): void {
|
||||
if (floatingWindow) {
|
||||
floatingWindow.show()
|
||||
} else {
|
||||
createFloatingWindow()
|
||||
}
|
||||
}
|
||||
|
||||
export function closeFloatingWindow(): void {
|
||||
if (floatingWindow) {
|
||||
floatingWindow.close()
|
||||
floatingWindow.destroy()
|
||||
floatingWindow = null
|
||||
}
|
||||
}
|
||||
|
||||
export async function showContextMenu(): Promise<void> {
|
||||
const menu = await buildContextMenu()
|
||||
menu.popup()
|
||||
}
|
|
@ -9,6 +9,7 @@ import {
|
|||
import { triggerSysProxy } from '../sys/sysproxy'
|
||||
import { patchMihomoConfig } from '../core/mihomoApi'
|
||||
import { quitWithoutCore, restartCore } from '../core/manager'
|
||||
import { closeFloatingWindow, floatingWindow, showFloatingWindow } from './floatingWindow'
|
||||
|
||||
export async function registerShortcut(
|
||||
oldShortcut: string,
|
||||
|
@ -31,6 +32,17 @@ export async function registerShortcut(
|
|||
}
|
||||
})
|
||||
}
|
||||
case 'showFloatingWindowShortcut': {
|
||||
return globalShortcut.register(newShortcut, async () => {
|
||||
if (floatingWindow) {
|
||||
await patchAppConfig({ showFloatingWindow: false })
|
||||
closeFloatingWindow()
|
||||
} else {
|
||||
await patchAppConfig({ showFloatingWindow: true })
|
||||
showFloatingWindow()
|
||||
}
|
||||
})
|
||||
}
|
||||
case 'triggerSysProxyShortcut': {
|
||||
return globalShortcut.register(newShortcut, async () => {
|
||||
const {
|
||||
|
@ -42,10 +54,11 @@ export async function registerShortcut(
|
|||
new Notification({
|
||||
title: `系统代理已${!enable ? '开启' : '关闭'}`
|
||||
}).show()
|
||||
mainWindow?.webContents.send('appConfigUpdated')
|
||||
floatingWindow?.webContents.send('appConfigUpdated')
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
mainWindow?.webContents.send('appConfigUpdated')
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
}
|
||||
})
|
||||
|
@ -64,10 +77,11 @@ export async function registerShortcut(
|
|||
new Notification({
|
||||
title: `虚拟网卡已${!enable ? '开启' : '关闭'}`
|
||||
}).show()
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
floatingWindow?.webContents.send('appConfigUpdated')
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
}
|
||||
})
|
||||
|
@ -122,6 +136,7 @@ export async function registerShortcut(
|
|||
|
||||
export async function initShortcut(): Promise<void> {
|
||||
const {
|
||||
showFloatingWindowShortcut,
|
||||
showWindowShortcut,
|
||||
triggerSysProxyShortcut,
|
||||
triggerTunShortcut,
|
||||
|
@ -138,6 +153,13 @@ export async function initShortcut(): Promise<void> {
|
|||
// ignore
|
||||
}
|
||||
}
|
||||
if (showFloatingWindowShortcut) {
|
||||
try {
|
||||
await registerShortcut('', showFloatingWindowShortcut, 'showFloatingWindowShortcut')
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
if (triggerSysProxyShortcut) {
|
||||
try {
|
||||
await registerShortcut('', triggerSysProxyShortcut, 'triggerSysProxyShortcut')
|
||||
|
|
|
@ -6,8 +6,10 @@ import AdmZip from 'adm-zip'
|
|||
import { getControledMihomoConfig } from '../config'
|
||||
import { existsSync } from 'fs'
|
||||
import { mainWindow } from '..'
|
||||
import { floatingWindow } from './floatingWindow'
|
||||
|
||||
let insertedCSSKey: string | undefined = undefined
|
||||
let insertedCSSKeyMain: string | undefined = undefined
|
||||
let insertedCSSKeyFloating: string | undefined = undefined
|
||||
|
||||
export async function resolveThemes(): Promise<{ key: string; label: string }[]> {
|
||||
const files = await readdir(themesDir())
|
||||
|
@ -67,6 +69,12 @@ export async function writeTheme(theme: string, css: string): Promise<void> {
|
|||
|
||||
export async function applyTheme(theme: string): Promise<void> {
|
||||
const css = await readTheme(theme)
|
||||
await mainWindow?.webContents.removeInsertedCSS(insertedCSSKey || '')
|
||||
insertedCSSKey = await mainWindow?.webContents.insertCSS(css)
|
||||
await mainWindow?.webContents.removeInsertedCSS(insertedCSSKeyMain || '')
|
||||
insertedCSSKeyMain = await mainWindow?.webContents.insertCSS(css)
|
||||
try {
|
||||
await floatingWindow?.webContents.removeInsertedCSS(insertedCSSKeyFloating || '')
|
||||
insertedCSSKeyFloating = await floatingWindow?.webContents.insertCSS(css)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,15 +15,16 @@ import {
|
|||
mihomoGroups,
|
||||
patchMihomoConfig
|
||||
} from '../core/mihomoApi'
|
||||
import { mainWindow, showMainWindow } from '..'
|
||||
import { closeMainWindow, mainWindow, showMainWindow } from '..'
|
||||
import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
|
||||
import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
|
||||
import { triggerSysProxy } from '../sys/sysproxy'
|
||||
import { quitWithoutCore, restartCore } from '../core/manager'
|
||||
import { closeFloatingWindow, floatingWindow, showFloatingWindow } from './floatingWindow'
|
||||
|
||||
export let tray: Tray | null = null
|
||||
|
||||
const buildContextMenu = async (): Promise<Menu> => {
|
||||
export const buildContextMenu = async (): Promise<Menu> => {
|
||||
const { mode, tun } = await getControledMihomoConfig()
|
||||
const {
|
||||
sysProxy,
|
||||
|
@ -31,6 +32,7 @@ const buildContextMenu = async (): Promise<Menu> => {
|
|||
autoCloseConnection,
|
||||
proxyInTray = true,
|
||||
triggerSysProxyShortcut = '',
|
||||
showFloatingWindowShortcut = '',
|
||||
showWindowShortcut = '',
|
||||
triggerTunShortcut = '',
|
||||
ruleModeShortcut = '',
|
||||
|
@ -90,6 +92,21 @@ const buildContextMenu = async (): Promise<Menu> => {
|
|||
showMainWindow()
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'show-floating',
|
||||
accelerator: showFloatingWindowShortcut,
|
||||
label: floatingWindow?.isVisible() ? '关闭悬浮窗' : '显示悬浮窗',
|
||||
type: 'normal',
|
||||
click: async (): Promise<void> => {
|
||||
if (floatingWindow) {
|
||||
await patchAppConfig({ showFloatingWindow: false })
|
||||
closeFloatingWindow()
|
||||
} else {
|
||||
await patchAppConfig({ showFloatingWindow: true })
|
||||
showFloatingWindow()
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rule',
|
||||
label: '规则模式',
|
||||
|
@ -140,10 +157,11 @@ const buildContextMenu = async (): Promise<Menu> => {
|
|||
try {
|
||||
await triggerSysProxy(enable)
|
||||
await patchAppConfig({ sysProxy: { enable } })
|
||||
mainWindow?.webContents.send('appConfigUpdated')
|
||||
floatingWindow?.webContents.send('appConfigUpdated')
|
||||
} catch (e) {
|
||||
// ignore
|
||||
} finally {
|
||||
mainWindow?.webContents.send('appConfigUpdated')
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
}
|
||||
}
|
||||
|
@ -155,15 +173,21 @@ const buildContextMenu = async (): Promise<Menu> => {
|
|||
checked: tun?.enable ?? false,
|
||||
click: async (item): Promise<void> => {
|
||||
const enable = item.checked
|
||||
try {
|
||||
if (enable) {
|
||||
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||
} else {
|
||||
await patchControledMihomoConfig({ tun: { enable } })
|
||||
}
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
floatingWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
await restartCore()
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
}
|
||||
}
|
||||
},
|
||||
...groupsMenu,
|
||||
{ type: 'separator' },
|
||||
|
@ -291,7 +315,7 @@ export async function createTray(): Promise<void> {
|
|||
})
|
||||
tray?.addListener('right-click', async () => {
|
||||
if (mainWindow?.isVisible()) {
|
||||
mainWindow?.close()
|
||||
closeMainWindow()
|
||||
} else {
|
||||
showMainWindow()
|
||||
}
|
||||
|
@ -303,7 +327,7 @@ export async function createTray(): Promise<void> {
|
|||
if (process.platform === 'win32') {
|
||||
tray?.addListener('click', () => {
|
||||
if (mainWindow?.isVisible()) {
|
||||
mainWindow?.close()
|
||||
closeMainWindow()
|
||||
} else {
|
||||
showMainWindow()
|
||||
}
|
||||
|
@ -315,7 +339,7 @@ export async function createTray(): Promise<void> {
|
|||
if (process.platform === 'linux') {
|
||||
tray?.addListener('click', () => {
|
||||
if (mainWindow?.isVisible()) {
|
||||
mainWindow?.close()
|
||||
closeMainWindow()
|
||||
} else {
|
||||
showMainWindow()
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ import { listWebdavBackups, webdavBackup, webdavDelete, webdavRestore } from '..
|
|||
import { getInterfaces } from '../sys/interface'
|
||||
import { copyEnv } from '../resolve/tray'
|
||||
import { registerShortcut } from '../resolve/shortcut'
|
||||
import { mainWindow } from '..'
|
||||
import { closeMainWindow, mainWindow, showMainWindow } from '..'
|
||||
import {
|
||||
applyTheme,
|
||||
fetchThemes,
|
||||
|
@ -81,6 +81,7 @@ import v8 from 'v8'
|
|||
import { getGistUrl } from '../resolve/gistApi'
|
||||
import { getImageDataURL } from './image'
|
||||
import { startMonitor } from '../resolve/trafficMonitor'
|
||||
import { closeFloatingWindow, showContextMenu, showFloatingWindow } from '../resolve/floatingWindow'
|
||||
|
||||
function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
fn: (...args: any[]) => Promise<T> // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -209,6 +210,11 @@ export function registerIpcMainHandlers(): void {
|
|||
ipcMain.handle('isAlwaysOnTop', () => {
|
||||
return mainWindow?.isAlwaysOnTop()
|
||||
})
|
||||
ipcMain.handle('showMainWindow', showMainWindow)
|
||||
ipcMain.handle('closeMainWindow', closeMainWindow)
|
||||
ipcMain.handle('showFloatingWindow', showFloatingWindow)
|
||||
ipcMain.handle('closeFloatingWindow', closeFloatingWindow)
|
||||
ipcMain.handle('showContextMenu', () => ipcErrorWrapper(showContextMenu)())
|
||||
ipcMain.handle('openFile', (_e, type, id, ext) => openFile(type, id, ext))
|
||||
ipcMain.handle('openDevTools', () => {
|
||||
mainWindow?.webContents.openDevTools()
|
||||
|
|
17
src/renderer/floating.html
Normal file
17
src/renderer/floating.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" lang="zh" />
|
||||
<title>Mihomo Party</title>
|
||||
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data:; frame-src http://127.0.0.1:*;"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/floating.tsx"></script>
|
||||
</body>
|
||||
</html>
|
61
src/renderer/src/FloatingApp.tsx
Normal file
61
src/renderer/src/FloatingApp.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import MihomoIcon from './components/base/mihomo-icon'
|
||||
import { calcTraffic } from './utils/calc'
|
||||
import { showContextMenu, showMainWindow } from './utils/ipc'
|
||||
import { useAppConfig } from './hooks/use-app-config'
|
||||
import { useControledMihomoConfig } from './hooks/use-controled-mihomo-config'
|
||||
|
||||
const FloatingApp: React.FC = () => {
|
||||
const { appConfig } = useAppConfig()
|
||||
const { controledMihomoConfig } = useControledMihomoConfig()
|
||||
const { sysProxy } = appConfig || {}
|
||||
const { tun } = controledMihomoConfig || {}
|
||||
const sysProxyEnabled = sysProxy?.enable
|
||||
const tunEnabled = tun?.enable
|
||||
|
||||
const [upload, setUpload] = useState(0)
|
||||
const [download, setDownload] = useState(0)
|
||||
useEffect(() => {
|
||||
window.electron.ipcRenderer.on('mihomoTraffic', async (_e, info: IMihomoTrafficInfo) => {
|
||||
setUpload(info.up)
|
||||
setDownload(info.down)
|
||||
})
|
||||
return (): void => {
|
||||
window.electron.ipcRenderer.removeAllListeners('mihomoTraffic')
|
||||
}
|
||||
}, [])
|
||||
return (
|
||||
<div className="app-drag p-[4px] h-[100vh]">
|
||||
<div className="floating-bg drop-shadow-md flex rounded-[calc(calc(100vh-8px)/2)] bg-content1 h-[calc(100vh-8px)] w-[calc(100vw-8px)]">
|
||||
<div className="flex justify-center items-center h-full w-[calc(100vh-8px)]">
|
||||
<div
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault()
|
||||
showContextMenu()
|
||||
}}
|
||||
onClick={() => {
|
||||
showMainWindow()
|
||||
}}
|
||||
className={`app-nodrag cursor-pointer floating-thumb ${tunEnabled ? 'bg-secondary' : sysProxyEnabled ? 'bg-primary' : 'bg-default'} hover:opacity-hover rounded-full h-[calc(100vh-14px)] w-[calc(100vh-14px)]`}
|
||||
>
|
||||
<MihomoIcon className="floating-icon text-primary-foreground h-full leading-full text-[22px] mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center w-[calc(100%-42px)]">
|
||||
<div className="flex justify-end">
|
||||
<div className="floating-text whitespace-nowrap overflow-hidden text-[12px] mr-[10px] font-bold">
|
||||
{calcTraffic(upload)}/s
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex justify-end">
|
||||
<div className="floating-text whitespace-nowrap overflow-hidden text-[12px] mr-[10px] font-bold">
|
||||
{calcTraffic(download)}/s
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FloatingApp
|
20
src/renderer/src/assets/floating.css
Normal file
20
src/renderer/src/assets/floating.css
Normal file
|
@ -0,0 +1,20 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
background: none !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.app-nodrag {
|
||||
-webkit-app-region: none;
|
||||
}
|
||||
|
||||
.app-drag {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
* {
|
||||
user-select: none;
|
||||
}
|
|
@ -7,6 +7,7 @@ import useSWR from 'swr'
|
|||
import {
|
||||
applyTheme,
|
||||
checkAutoRun,
|
||||
closeFloatingWindow,
|
||||
copyEnv,
|
||||
disableAutoRun,
|
||||
enableAutoRun,
|
||||
|
@ -15,6 +16,7 @@ import {
|
|||
importThemes,
|
||||
relaunchApp,
|
||||
resolveThemes,
|
||||
showFloatingWindow,
|
||||
startMonitor,
|
||||
writeTheme
|
||||
} from '@renderer/utils/ipc'
|
||||
|
@ -37,6 +39,7 @@ const GeneralConfig: React.FC = () => {
|
|||
useDockIcon = true,
|
||||
showTraffic = true,
|
||||
proxyInTray = true,
|
||||
showFloatingWindow: showFloating = false,
|
||||
useWindowFrame = false,
|
||||
autoQuitWithoutCore = false,
|
||||
autoQuitWithoutCoreDelay = 60,
|
||||
|
@ -175,6 +178,20 @@ const GeneralConfig: React.FC = () => {
|
|||
<SelectItem key="powershell">PowerShell</SelectItem>
|
||||
</Select>
|
||||
</SettingItem>
|
||||
<SettingItem title="显示悬浮窗" divider>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={showFloating}
|
||||
onValueChange={async (v) => {
|
||||
await patchAppConfig({ showFloatingWindow: v })
|
||||
if (v) {
|
||||
showFloatingWindow()
|
||||
} else {
|
||||
closeFloatingWindow()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</SettingItem>
|
||||
{platform !== 'linux' && (
|
||||
<>
|
||||
<SettingItem title="托盘菜单显示节点信息" divider>
|
||||
|
|
|
@ -43,6 +43,7 @@ const ShortcutConfig: React.FC = () => {
|
|||
const { appConfig, patchAppConfig } = useAppConfig()
|
||||
const {
|
||||
showWindowShortcut = '',
|
||||
showFloatingWindowShortcut = '',
|
||||
triggerSysProxyShortcut = '',
|
||||
triggerTunShortcut = '',
|
||||
ruleModeShortcut = '',
|
||||
|
@ -63,6 +64,15 @@ const ShortcutConfig: React.FC = () => {
|
|||
/>
|
||||
</div>
|
||||
</SettingItem>
|
||||
<SettingItem title="打开/关闭悬浮窗" divider>
|
||||
<div className="flex justify-end w-[60%]">
|
||||
<ShortcutInput
|
||||
value={showFloatingWindowShortcut}
|
||||
patchAppConfig={patchAppConfig}
|
||||
action="showFloatingWindowShortcut"
|
||||
/>
|
||||
</div>
|
||||
</SettingItem>
|
||||
<SettingItem title="打开/关闭系统代理" divider>
|
||||
<div className="flex justify-end w-[60%]">
|
||||
<ShortcutInput
|
||||
|
|
|
@ -29,6 +29,7 @@ const SysproxySwitcher: React.FC = () => {
|
|||
try {
|
||||
await triggerSysProxy(enable)
|
||||
await patchAppConfig({ sysProxy: { enable } })
|
||||
window.electron.ipcRenderer.send('updateFloatingWindow')
|
||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||
} catch (e) {
|
||||
alert(e)
|
||||
|
|
|
@ -51,6 +51,7 @@ const TunSwitcher: React.FC = () => {
|
|||
await patchControledMihomoConfig({ tun: { enable } })
|
||||
}
|
||||
await restartCore()
|
||||
window.electron.ipcRenderer.send('updateFloatingWindow')
|
||||
window.electron.ipcRenderer.send('updateTrayMenu')
|
||||
}
|
||||
|
||||
|
|
25
src/renderer/src/floating.tsx
Normal file
25
src/renderer/src/floating.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import { NextUIProvider } from '@nextui-org/react'
|
||||
import '@renderer/assets/floating.css'
|
||||
import FloatingApp from '@renderer/FloatingApp'
|
||||
import BaseErrorBoundary from './components/base/base-error-boundary'
|
||||
import { AppConfigProvider } from './hooks/use-app-config'
|
||||
import { ControledMihomoConfigProvider } from './hooks/use-controled-mihomo-config'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<NextUIProvider>
|
||||
<NextThemesProvider attribute="class" enableSystem defaultTheme="dark">
|
||||
<BaseErrorBoundary>
|
||||
<AppConfigProvider>
|
||||
<ControledMihomoConfigProvider>
|
||||
<FloatingApp />
|
||||
</ControledMihomoConfigProvider>
|
||||
</AppConfigProvider>
|
||||
</BaseErrorBoundary>
|
||||
</NextThemesProvider>
|
||||
</NextUIProvider>
|
||||
</React.StrictMode>
|
||||
)
|
|
@ -327,6 +327,26 @@ export async function subStoreCollections(): Promise<ISubStoreSub[]> {
|
|||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('subStoreCollections'))
|
||||
}
|
||||
|
||||
export async function showMainWindow(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showMainWindow'))
|
||||
}
|
||||
|
||||
export async function closeMainWindow(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('closeMainWindow'))
|
||||
}
|
||||
|
||||
export async function showFloatingWindow(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showFloatingWindow'))
|
||||
}
|
||||
|
||||
export async function closeFloatingWindow(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('closeFloatingWindow'))
|
||||
}
|
||||
|
||||
export async function showContextMenu(): Promise<void> {
|
||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showContextMenu'))
|
||||
}
|
||||
|
||||
export async function openFile(
|
||||
type: 'profile' | 'override',
|
||||
id: string,
|
||||
|
|
2
src/shared/types.d.ts
vendored
2
src/shared/types.d.ts
vendored
|
@ -212,6 +212,7 @@ interface IAppConfig {
|
|||
proxyCols: 'auto' | '1' | '2' | '3' | '4'
|
||||
connectionDirection: 'asc' | 'desc'
|
||||
connectionOrderBy: 'time' | 'upload' | 'download' | 'uploadSpeed' | 'downloadSpeed'
|
||||
showFloatingWindow?: boolean
|
||||
connectionCardStatus?: CardStatus
|
||||
dnsCardStatus?: CardStatus
|
||||
logCardStatus?: CardStatus
|
||||
|
@ -262,6 +263,7 @@ interface IAppConfig {
|
|||
useNameserverPolicy: boolean
|
||||
nameserverPolicy: { [key: string]: string | string[] }
|
||||
showWindowShortcut?: string
|
||||
showFloatingWindowShortcut?: string
|
||||
triggerSysProxyShortcut?: string
|
||||
triggerTunShortcut?: string
|
||||
ruleModeShortcut?: string
|
||||
|
|
Loading…
Reference in New Issue
Block a user