diff --git a/electron-builder.yml b/electron-builder.yml index 1ecf921..df057e4 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -14,6 +14,11 @@ asarUnpack: extraResources: - from: './resources/' to: '' +protocols: + name: 'Mihomo Party URI Scheme' + schemes: + - 'clash' + - 'mihomo' win: target: - nsis @@ -41,6 +46,8 @@ mac: dmg: artifactName: ${name}-macos-${version}-${arch}-installer.${ext} linux: + desktop: + MimeType: 'x-scheme-handler/clash;x-scheme-handler/mihomo' target: - deb - rpm diff --git a/src/main/config/profile.ts b/src/main/config/profile.ts index 131daa7..48c6a8b 100644 --- a/src/main/config/profile.ts +++ b/src/main/config/profile.ts @@ -6,6 +6,7 @@ import { window } from '..' import axios from 'axios' import yaml from 'yaml' import fs from 'fs' +import { dialog } from 'electron' let profileConfig: IProfileConfig // profile.yaml let currentProfile: Partial // profiles/xxx.yaml @@ -95,6 +96,10 @@ export async function createProfile(item: Partial): Promise): Promise { + app.on('second-instance', (_event, commandline) => { window?.show() window?.focusOnWebView() + const url = commandline.pop() + if (url) { + handleDeepLink(url) + } + }) + app.on('open-url', (_event, url) => { + window?.show() + window?.focusOnWebView() + handleDeepLink(url) }) - // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. @@ -67,6 +75,27 @@ if (!gotTheLock) { }) } +function handleDeepLink(url: string): void { + if (url.startsWith('clash://install-config')) { + url = url.replace('clash://install-config/?url=', '').replace('clash://install-config?url=', '') + addProfileItem({ + type: 'remote', + name: 'Remote File', + url + }) + } + if (url.startsWith('mihomo://install-config')) { + url = url + .replace('mihomo://install-config/?url=', '') + .replace('mihomo://install-config?url=', '') + addProfileItem({ + type: 'remote', + name: 'Remote File', + url + }) + } +} + function createWindow(): void { // Create the browser window. window = new BrowserWindow({ diff --git a/src/main/resolve/init.ts b/src/main/resolve/init.ts index 536af21..ee15ddb 100644 --- a/src/main/resolve/init.ts +++ b/src/main/resolve/init.ts @@ -22,6 +22,7 @@ import path from 'path' import { startPacServer } from './server' import { triggerSysProxy } from './sysproxy' import { getAppConfig } from '../config' +import { app } from 'electron' function initDirs(): void { if (!fs.existsSync(dataDir)) { @@ -71,10 +72,21 @@ function initFiles(): void { } } +function initDeeplink(): void { + if (process.defaultApp) { + if (process.argv.length >= 2) { + app.setAsDefaultProtocolClient('clash', process.execPath, [path.resolve(process.argv[1])]) + } + } else { + app.setAsDefaultProtocolClient('clash') + } +} + export function init(): void { initDirs() initConfig() initFiles() + initDeeplink() startPacServer().then(() => { triggerSysProxy(getAppConfig().sysProxy.enable) }) diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index b158de9..ad6479a 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -59,5 +59,6 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('triggerSysProxy', (_e, enable) => triggerSysProxy(enable)) ipcMain.handle('isEncryptionAvailable', isEncryptionAvailable) ipcMain.handle('encryptString', (_e, str) => safeStorage.encryptString(str)) + ipcMain.handle('platform', () => process.platform) ipcMain.handle('quitApp', () => app.quit()) } diff --git a/src/renderer/src/components/sider/tun-switcher.tsx b/src/renderer/src/components/sider/tun-switcher.tsx index e25e62e..c73c9c2 100644 --- a/src/renderer/src/components/sider/tun-switcher.tsx +++ b/src/renderer/src/components/sider/tun-switcher.tsx @@ -3,7 +3,12 @@ import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-c import BorderSwitch from '@renderer/components/base/border-swtich' import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb' import { useLocation, useNavigate } from 'react-router-dom' -import { encryptString, patchMihomoConfig, isEncryptionAvailable } from '@renderer/utils/ipc' +import { + platform, + encryptString, + patchMihomoConfig, + isEncryptionAvailable +} from '@renderer/utils/ipc' import React, { useState } from 'react' import { useAppConfig } from '@renderer/hooks/use-app-config' import BasePasswordModal from '../base/base-password-modal' @@ -19,14 +24,17 @@ const TunSwitcher: React.FC = () => { const { enable } = tun || {} const onChange = async (enable: boolean): Promise => { - const encryptionAvailable = await isEncryptionAvailable() - if (!appConfig?.encryptedPassword && encryptionAvailable) { - setOpenPasswordModal(true) - return - } - if (!encryptionAvailable) { - alert('加密不可用,请手动给内核授权') + if (enable && (await platform()) !== 'win32') { + const encryptionAvailable = await isEncryptionAvailable() + if (!appConfig?.encryptedPassword && encryptionAvailable) { + setOpenPasswordModal(true) + return + } + if (!encryptionAvailable) { + alert('加密不可用,请手动给内核授权') + } } + await patchControledMihomoConfig({ tun: { enable } }) await patchMihomoConfig({ tun: { enable } }) } diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index a376565..bcbfbf3 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -113,6 +113,11 @@ export async function isEncryptionAvailable(): Promise { export async function encryptString(str: string): Promise { return await window.electron.ipcRenderer.invoke('encryptString', str) } + +export async function platform(): Promise { + return await window.electron.ipcRenderer.invoke('platform') +} + export async function quitApp(): Promise { return await window.electron.ipcRenderer.invoke('quitApp') }