mirror of
https://github.com/viarotel-org/escrcpy.git
synced 2025-02-26 14:21:46 +01:00
perf: 🚀 The main panel supports single instance startup
This commit is contained in:
parent
6c4242c40a
commit
c49d22cabf
10
electron/helpers/emitter.js
Normal file
10
electron/helpers/emitter.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
|
||||
const eventEmitter = new EventEmitter()
|
||||
|
||||
export {
|
||||
EventEmitter,
|
||||
eventEmitter,
|
||||
}
|
||||
|
||||
export default eventEmitter
|
157
electron/helpers/single.js
Normal file
157
electron/helpers/single.js
Normal file
@ -0,0 +1,157 @@
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
|
||||
/**
|
||||
* 确保 Electron 应用只运行一个实例的工具函数
|
||||
* @typedef {Object} SingleInstanceOptions
|
||||
* @property {Function} [onSecondInstance] - 当第二个实例启动时的回调函数
|
||||
* @property {boolean} [enableSandbox=false] - 是否启用沙箱模式
|
||||
* @property {Function} [onSuccess] - 成功获取单例锁后的回调函数
|
||||
* @property {Function} [onShowWindow] - 主窗口已展示回调
|
||||
* @property {boolean} [forceFocus=true] - 是否强制聚焦已存在的窗口
|
||||
* @property {boolean} [silentMode=false] - 静默模式,不显示任何提示
|
||||
* @property {Function} [onError] - 错误处理回调函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 第二个实例启动时的回调函数类型
|
||||
* @callback OnSecondInstanceCallback
|
||||
* @param {Event} event - Electron 事件对象
|
||||
* @param {string[]} commandLine - 命令行参数数组
|
||||
* @param {string} workingDirectory - 工作目录
|
||||
* @param {BrowserWindow|null} mainWindow - 主窗口实例,如果存在的话
|
||||
*/
|
||||
|
||||
/**
|
||||
* 确保应用程序只运行单个实例
|
||||
* @param {SingleInstanceOptions} options - 配置选项
|
||||
* @returns {boolean} 是否成功获取单例锁
|
||||
*
|
||||
* @example
|
||||
* // 基础使用
|
||||
* ensureSingleInstance({
|
||||
* onSuccess: () => {
|
||||
* app.whenReady().then(createWindow)
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // 高级使用
|
||||
* ensureSingleInstance({
|
||||
* onSecondInstance: (event, commandLine, workingDirectory, mainWindow) => {
|
||||
* if (mainWindow) {
|
||||
* mainWindow.webContents.send('new-instance-launched', commandLine);
|
||||
* }
|
||||
* },
|
||||
* onSuccess: () => {
|
||||
* console.log('Successfully acquired lock');
|
||||
* createWindow();
|
||||
* },
|
||||
* onError: (error) => {
|
||||
* console.error('Error in single instance check:', error);
|
||||
* },
|
||||
* forceFocus: true,
|
||||
* silentMode: false
|
||||
* });
|
||||
*
|
||||
* @throws {Error} 如果在非 Electron 环境中调用
|
||||
*/
|
||||
function ensureSingleInstance(options = {}) {
|
||||
// 参数解构与默认值设置
|
||||
const {
|
||||
onSecondInstance,
|
||||
enableSandbox = false,
|
||||
onSuccess,
|
||||
onShowWindow,
|
||||
onError,
|
||||
forceFocus = true,
|
||||
silentMode = false,
|
||||
} = options
|
||||
|
||||
// 验证运行环境
|
||||
if (!app || !BrowserWindow) {
|
||||
const error = new Error('ensureSingleInstance must be called in Electron environment')
|
||||
if (onError) {
|
||||
onError(error)
|
||||
return false
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
try {
|
||||
// 沙箱模式检查
|
||||
if (enableSandbox) {
|
||||
!silentMode && console.log('Sandbox mode enabled, skipping single instance check')
|
||||
onSuccess?.()
|
||||
return true
|
||||
}
|
||||
|
||||
// 请求单例锁
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
|
||||
// 如果无法获取锁,说明已有实例在运行
|
||||
if (!gotTheLock) {
|
||||
!silentMode && console.log('Application instance already running, quitting...')
|
||||
app.quit()
|
||||
return false
|
||||
}
|
||||
|
||||
// 监听第二个实例的启动
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
try {
|
||||
// 获取所有窗口
|
||||
const windows = BrowserWindow.getAllWindows()
|
||||
const mainWindow = windows.length ? windows[0] : null
|
||||
|
||||
// 处理窗口焦点
|
||||
onShowWindow?.(mainWindow, commandLine)
|
||||
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized() || !mainWindow.isVisible()) {
|
||||
mainWindow.show()
|
||||
}
|
||||
if (forceFocus) {
|
||||
mainWindow.focus()
|
||||
}
|
||||
}
|
||||
|
||||
// 调用用户自定义的回调
|
||||
onSecondInstance?.(event, commandLine, workingDirectory, mainWindow)
|
||||
}
|
||||
catch (error) {
|
||||
!silentMode && console.error('Error handling second instance:', error)
|
||||
onError?.(error)
|
||||
}
|
||||
})
|
||||
|
||||
// 调用成功回调
|
||||
onSuccess?.()
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
!silentMode && console.error('Error in ensureSingleInstance:', error)
|
||||
onError?.(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前是否为应用程序的主实例
|
||||
* @returns {boolean} 如果是主实例返回 true,否则返回 false
|
||||
*/
|
||||
function isMainInstance() {
|
||||
return app.requestSingleInstanceLock()
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放单例锁,允许其他实例启动
|
||||
* @returns {void}
|
||||
*/
|
||||
function releaseSingleInstanceLock() {
|
||||
app.releaseSingleInstanceLock()
|
||||
}
|
||||
|
||||
export {
|
||||
ensureSingleInstance,
|
||||
isMainInstance,
|
||||
releaseSingleInstanceLock,
|
||||
}
|
@ -1,13 +1,18 @@
|
||||
import { app, dialog, Menu, Tray } from 'electron'
|
||||
import { trayPath } from '$electron/configs/index.js'
|
||||
import { executeI18n } from '$electron/helpers/index.js'
|
||||
import appStore from '$electron/helpers/store.js'
|
||||
import { app, dialog, Menu, Tray } from 'electron'
|
||||
import { eventEmitter } from '$electron/helpers/emitter.js'
|
||||
|
||||
export default (mainWindow) => {
|
||||
const t = value => executeI18n(mainWindow, value)
|
||||
|
||||
let tray = null
|
||||
|
||||
eventEmitter.on('tray:destroy', () => {
|
||||
tray?.destroy?.()
|
||||
})
|
||||
|
||||
const showApp = () => {
|
||||
if (process.platform === 'darwin') {
|
||||
app.dock.show()
|
||||
|
@ -22,6 +22,9 @@ import { loadPage } from './helpers/index.js'
|
||||
|
||||
import { Edger } from './helpers/edger/index.js'
|
||||
|
||||
import { ensureSingleInstance } from './helpers/single.js'
|
||||
import { eventEmitter } from './helpers/emitter.js'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
@ -122,27 +125,39 @@ function createWindow() {
|
||||
control(mainWindow)
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
electronApp.setAppUserModelId('com.viarotel.escrcpy')
|
||||
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
function onWhenReady() {
|
||||
app.whenReady().then(() => {
|
||||
electronApp.setAppUserModelId('com.viarotel.escrcpy')
|
||||
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
createWindow()
|
||||
|
||||
// macOS 中应用被激活
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
return
|
||||
}
|
||||
|
||||
app.dock.show()
|
||||
mainWindow.show()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
createWindow()
|
||||
|
||||
// macOS 中应用被激活
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
return
|
||||
}
|
||||
|
||||
app.dock.show()
|
||||
mainWindow.show()
|
||||
})
|
||||
ensureSingleInstance({
|
||||
onSuccess() {
|
||||
onWhenReady()
|
||||
},
|
||||
onShowWindow() {
|
||||
eventEmitter.emit('tray:destroy')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.isQuiting = true
|
||||
app.quit()
|
||||
|
Loading…
x
Reference in New Issue
Block a user