diff --git a/.eslintrc.js b/.eslintrc.js index f5d68712..4d467813 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,56 +1,56 @@ -const path = require("path"); - -module.exports = { - root: true, - env: { - browser: true, - es2021: true, - node: true, - "vue/setup-compiler-macros": true, - }, - parserOptions: { - ecmaVersion: 12, - sourceType: "module", - project: [ - path.join(__dirname, "packages/renderer/tsconfig.json"), - path.join(__dirname, "packages/main/tsconfig.json"), - ], - }, - plugins: ["vue", "@typescript-eslint"], - extends: [ - "eslint:recommended", - "plugin:vue/vue3-recommended", - "plugin:vue/vue3-essential", - "standard-with-typescript", - "@vue/eslint-config-typescript", - ], - rules: { - "vue/max-len": [ - "error", - { - code: 120, - template: 120, - }, - ], - "no-undef": "off", // 让ts检查undefined - "@typescript-eslint/no-floating-promises": "off", - "@typescript-eslint/strict-boolean-expressions": "off", - "vue/multi-word-component-names": "off", - "vue/max-attributes-per-line": [ - "error", - { - singleline: { - max: 3, - }, - multiline: { - max: 1, - }, - }, - ], - "@typescript-eslint/promise-function-async": "off", - "@typescript-eslint/naming-convention": "off", - "camelcase": "off", - "vue/prop-name-casing": "off", - "space-before-function-paren": "off", - }, -}; +const path = require('path') + +module.exports = { + root: true, + env: { + browser: true, + es2021: true, + node: true, + 'vue/setup-compiler-macros': true, + }, + parserOptions: { + ecmaVersion: 12, + sourceType: 'module', + project: [ + path.join(__dirname, 'packages/renderer/tsconfig.json'), + path.join(__dirname, 'packages/main/tsconfig.json'), + ], + }, + plugins: ['vue', '@typescript-eslint'], + extends: [ + 'eslint:recommended', + 'plugin:vue/vue3-recommended', + 'plugin:vue/vue3-essential', + 'standard-with-typescript', + '@vue/eslint-config-typescript', + ], + rules: { + 'vue/max-len': [ + 'error', + { + code: 120, + template: 120, + }, + ], + 'no-undef': 'off', // 让ts检查undefined + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/strict-boolean-expressions': 'off', + 'vue/multi-word-component-names': 'off', + 'vue/max-attributes-per-line': [ + 'error', + { + singleline: { + max: 3, + }, + multiline: { + max: 1, + }, + }, + ], + '@typescript-eslint/promise-function-async': 'off', + '@typescript-eslint/naming-convention': 'off', + camelcase: 'off', + 'vue/prop-name-casing': 'off', + 'space-before-function-paren': 'off', + }, +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..065a8f3e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +packages/common/ArknightsGameData/** +*.md +*.yml +*.yaml +*.json +*.html \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..109326be --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "trailingComma": "es5", + "semi": false, + "singleQuote": true, + "arrowParens": "avoid", + "vueIndentScriptAndStyle": false, + "printWidth": 80 +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 1037269d..65a17988 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,4 +9,8 @@ "packages/renderer/src/i18n" ], "i18n-ally.keystyle": "nested", + "[javascript][typescript][vue][css][less]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } diff --git a/forge.config.js b/forge.config.js index 8296c696..7561d4f8 100644 --- a/forge.config.js +++ b/forge.config.js @@ -1,24 +1,24 @@ -const info = require('./package.json'); +const info = require('./package.json') module.exports = { packagerConfig: { icon: 'packages/common/resources/icon', appBundleId: 'com.maa.maa-x', productName: 'MaaX', - ignore: (filepath) => { + ignore: filepath => { if (filepath.length === 0) { - return false; + return false } if (/^\/dist/.test(filepath)) { - return false; + return false } if (/^\/package.json/.test(filepath)) { - return false; + return false } if (/^\/node_modules/.test(filepath)) { - return false; + return false } - return true; + return true }, // asar: true, }, @@ -64,9 +64,9 @@ module.exports = { config: { repository: { owner: 'MaaAssistantArknights', - name: 'MaaX' + name: 'MaaX', }, - } - } - ] -}; + }, + }, + ], +} diff --git a/package.json b/package.json index f8839e04..744406c8 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "make": "electron-forge make", "start": "electron-forge start", "package": "electron-forge package", - "publish": "electron-forge publish" + "publish": "electron-forge publish", + "format": "npx prettier -w ." }, "engines": { "node": ">=18.12.0" diff --git a/packages/@types/api/service.d.ts b/packages/@types/api/service.d.ts index e9dc94e5..d06b0033 100644 --- a/packages/@types/api/service.d.ts +++ b/packages/@types/api/service.d.ts @@ -1,3 +1,6 @@ -interface ApiServiceProvider { - get: (url: string, config?: import('axios').AxiosRequestConfig) => Promise -} +interface ApiServiceProvider { + get: ( + url: string, + config?: import('axios').AxiosRequestConfig + ) => Promise +} diff --git a/packages/@types/componentManager/index.d.ts b/packages/@types/componentManager/index.d.ts index 24d72c7e..e9fe191c 100644 --- a/packages/@types/componentManager/index.d.ts +++ b/packages/@types/componentManager/index.d.ts @@ -1,7 +1,4 @@ -type ComponentType = - | 'Maa Core' - | 'Maa App' - | 'Android Platform Tools' +type ComponentType = 'Maa Core' | 'Maa App' | 'Android Platform Tools' type ComponentStatus = | 'not-installed' diff --git a/packages/@types/coreLoader/ipc.d.ts b/packages/@types/coreLoader/ipc.d.ts index 08c7fa17..c5e38a26 100644 --- a/packages/@types/coreLoader/ipc.d.ts +++ b/packages/@types/coreLoader/ipc.d.ts @@ -1,7 +1,7 @@ enum TouchMode { minitouch = 'minitouch', maatouch = 'maatouch', - adb = 'adb' + adb = 'adb', } interface InitCoreParam { diff --git a/packages/@types/deviceDetector/adapterBase.d.ts b/packages/@types/deviceDetector/adapterBase.d.ts index 52cbd491..9871aceb 100644 --- a/packages/@types/deviceDetector/adapterBase.d.ts +++ b/packages/@types/deviceDetector/adapterBase.d.ts @@ -7,9 +7,9 @@ interface EmulatorAdapter { getAdbDevices: () => Promise /** - * @description Get all emulators - * @returns {Promise} - */ + * @description Get all emulators + * @returns {Promise} + */ getEmulators: () => Promise } diff --git a/packages/@types/global.d.ts b/packages/@types/global.d.ts index 49468070..8f515f07 100644 --- a/packages/@types/global.d.ts +++ b/packages/@types/global.d.ts @@ -1,6 +1,6 @@ import type { MessageApiInjection } from 'naive-ui/lib/message/src/MessageProvider' import type { DialogApiInjection } from 'naive-ui/lib/dialog/src/DialogProvider' -export { } +export {} declare global { interface Window { diff --git a/packages/@types/i18n/boolean.d.ts b/packages/@types/i18n/boolean.d.ts index 6a6634d6..c17cb743 100644 --- a/packages/@types/i18n/boolean.d.ts +++ b/packages/@types/i18n/boolean.d.ts @@ -1,3 +1 @@ -type BooleanDisplayOption = - | 'Right/Wrong' - | 'Yes/No' +type BooleanDisplayOption = 'Right/Wrong' | 'Yes/No' diff --git a/packages/@types/ipc/ipc-events.d.ts b/packages/@types/ipc/ipc-events.d.ts index 4b3c886b..86b5f050 100644 --- a/packages/@types/ipc/ipc-events.d.ts +++ b/packages/@types/ipc/ipc-events.d.ts @@ -78,10 +78,16 @@ type IpcRendererHandleEvent = interface IpcMainEvent { name: IpcMainHandleEvent - listener: (event: import('electron').IpcMainInvokeEvent, ...args: any[]) => Promise | T | undefined + listener: ( + event: import('electron').IpcMainInvokeEvent, + ...args: any[] + ) => Promise | T | undefined } interface IpcRendererEvent { name: IpcRendererHandleEvent - listener: (event: import('electron').IpcRendererEvent, ...args: any[]) => Promise | T | undefined + listener: ( + event: import('electron').IpcRendererEvent, + ...args: any[] + ) => Promise | T | undefined } diff --git a/packages/@types/module.d.ts b/packages/@types/module.d.ts index e311ccd0..8799f6b5 100644 --- a/packages/@types/module.d.ts +++ b/packages/@types/module.d.ts @@ -1,4 +1,4 @@ -interface Module { - readonly version: string - readonly name: string -} +interface Module { + readonly version: string + readonly name: string +} diff --git a/packages/@types/store/store.device.d.ts b/packages/@types/store/store.device.d.ts index 1aaa0147..577b762a 100644 --- a/packages/@types/store/store.device.d.ts +++ b/packages/@types/store/store.device.d.ts @@ -65,7 +65,7 @@ interface Device { /** * @props 模拟器命令行参数 * @description 启动参数, 比如蓝叠启动指定实例的 "--instance" "114514" - */ + */ commandLine?: string } diff --git a/packages/@types/store/store.task.d.ts b/packages/@types/store/store.task.d.ts index f2a81fa9..477917c8 100644 --- a/packages/@types/store/store.task.d.ts +++ b/packages/@types/store/store.task.d.ts @@ -22,12 +22,12 @@ type TaskName = | 'copilot' | 'idle' - type CoreTaskName = +type CoreTaskName = | 'Emulator' | 'StartUp' | 'Fight' | 'Recruit' - |'Infrast' + | 'Infrast' | 'Visit' | 'Mall' | 'Award' @@ -110,7 +110,7 @@ interface TaskGroup { id: number /** * @props 任务组内的任务列表 - */ + */ tasks: Task[] } diff --git a/packages/common/enum/callback.ts b/packages/common/enum/callback.ts index 02531333..571f9707 100644 --- a/packages/common/enum/callback.ts +++ b/packages/common/enum/callback.ts @@ -1,49 +1,49 @@ -export enum AsstMsg { - /* Global Info */ - InternalError = 0, // 内部错误 - InitFailed, // 初始化失败 - ConnectionInfo, // 连接相关信息 - AllTasksCompleted, // 全部任务完成 - /* TaskChain Info */ - TaskChainError = 10000, // 任务链执行/识别错误 - TaskChainStart, // 任务链开始 - TaskChainCompleted, // 任务链完成 - TaskChainExtraInfo, // 任务链额外信息 - /* SubTask Info */ - SubTaskError = 20000, // 原子任务执行/识别错误 - SubTaskStart, // 原子任务开始 - SubTaskCompleted, // 原子任务完成 - SubTaskExtraInfo, // 原子任务额外信息 -} - -export enum TaskChainMap { - Emulator = 'emulator', - StartUp = 'startup', - Fight = 'fight', - Mall = 'mall', - Recruit = 'recruit', - RecruitCalc = 'recruit_calc', - Infrast = 'infrast', - Visit = 'visit', - Roguelike = 'rogue', - Copilot = 'copilot', - Shutdown = 'shutdown', - Award = 'award', - Idle = 'idle', - Debug = 'debug', - - emulator = 'Emulator', - startup = 'StartUp', - fight = 'Fight', - mall = 'Mall', - recruit = 'Recruit', - recruit_calc = 'RecruitCalc', - infrast = 'Infrast', - visit = 'Visit', - rogue = 'Roguelike', - copilot = 'Copilot', - shutdown = 'Shutdown', - award = 'Award', - idle = 'Idle', - debug = 'Debug', -} +export enum AsstMsg { + /* Global Info */ + InternalError = 0, // 内部错误 + InitFailed, // 初始化失败 + ConnectionInfo, // 连接相关信息 + AllTasksCompleted, // 全部任务完成 + /* TaskChain Info */ + TaskChainError = 10000, // 任务链执行/识别错误 + TaskChainStart, // 任务链开始 + TaskChainCompleted, // 任务链完成 + TaskChainExtraInfo, // 任务链额外信息 + /* SubTask Info */ + SubTaskError = 20000, // 原子任务执行/识别错误 + SubTaskStart, // 原子任务开始 + SubTaskCompleted, // 原子任务完成 + SubTaskExtraInfo, // 原子任务额外信息 +} + +export enum TaskChainMap { + Emulator = 'emulator', + StartUp = 'startup', + Fight = 'fight', + Mall = 'mall', + Recruit = 'recruit', + RecruitCalc = 'recruit_calc', + Infrast = 'infrast', + Visit = 'visit', + Roguelike = 'rogue', + Copilot = 'copilot', + Shutdown = 'shutdown', + Award = 'award', + Idle = 'idle', + Debug = 'debug', + + emulator = 'Emulator', + startup = 'StartUp', + fight = 'Fight', + mall = 'Mall', + recruit = 'Recruit', + recruit_calc = 'RecruitCalc', + infrast = 'Infrast', + visit = 'Visit', + rogue = 'Roguelike', + copilot = 'Copilot', + shutdown = 'Shutdown', + award = 'Award', + idle = 'Idle', + debug = 'Debug', +} diff --git a/packages/common/enum/settings.ts b/packages/common/enum/settings.ts index 2a9f0e9d..f9c90db6 100644 --- a/packages/common/enum/settings.ts +++ b/packages/common/enum/settings.ts @@ -1,5 +1,5 @@ export enum TouchMode { minitouch = 'minitouch', maatouch = 'maatouch', - adb = 'adb' + adb = 'adb', } diff --git a/packages/common/function/createMap.ts b/packages/common/function/createMap.ts index 4ec171d7..8b25c56c 100644 --- a/packages/common/function/createMap.ts +++ b/packages/common/function/createMap.ts @@ -1,9 +1,11 @@ type SupportedMapType = string | number -export function createMap ( +export function createMap( obj: Record ): Record { - (function (obj) { - for (const [key, value] of Object.entries(obj)) { obj[obj[key] = value] = key } + ;(function (obj) { + for (const [key, value] of Object.entries(obj)) { + obj[(obj[key] = value)] = key + } })(obj || (obj = {})) return obj } diff --git a/packages/common/function/singletonDecorator.ts b/packages/common/function/singletonDecorator.ts index 1e121d15..c7836f78 100644 --- a/packages/common/function/singletonDecorator.ts +++ b/packages/common/function/singletonDecorator.ts @@ -7,7 +7,7 @@ type SingletonType any> = T & { export const Singleton = any>(type: T): T => new Proxy(type, { // this will hijack the constructor - construct (target: SingletonType, argsList, newTarget) { + construct(target: SingletonType, argsList, newTarget) { // we should skip the proxy for children of our target class if (target.prototype !== newTarget.prototype) { return Reflect.construct(target, argsList, newTarget) @@ -18,5 +18,5 @@ export const Singleton = any>(type: T): T => } // return the instance we created! return target[SINGLETON_KEY] - } + }, }) diff --git a/packages/common/function/uuid.ts b/packages/common/function/uuid.ts index 8f618e84..89316235 100644 --- a/packages/common/function/uuid.ts +++ b/packages/common/function/uuid.ts @@ -1,6 +1,7 @@ export const uuidV4 = function (): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8) + const r = (Math.random() * 16) | 0 + const v = c === 'x' ? r : (r & 0x3) | 0x8 return v.toString(16) }) } diff --git a/packages/main/componentManager/componentInstaller.ts b/packages/main/componentManager/componentInstaller.ts index 8ac0d8a4..ab930ebe 100644 --- a/packages/main/componentManager/componentInstaller.ts +++ b/packages/main/componentManager/componentInstaller.ts @@ -20,13 +20,13 @@ interface UnzipHandle { } abstract class ComponentInstaller { - public abstract install (): Promise + public abstract install(): Promise - public abstract checkUpdate (): Promise - protected abstract onStart (): void - protected abstract onProgress (progress: number): void - protected abstract onCompleted (): void - protected abstract onException (): void + public abstract checkUpdate(): Promise + protected abstract onStart(): void + protected abstract onProgress(progress: number): void + protected abstract onCompleted(): void + protected abstract onException(): void protected readonly maxRetryTimes = 3 @@ -37,7 +37,8 @@ abstract class ComponentInstaller { compressedSize: number extractedSize: number }> = [] - fs.createReadStream(src).pipe(unzipper.Extract({ path: dist })) + fs.createReadStream(src) + .pipe(unzipper.Extract({ path: dist })) .on('entry', (entry: Entry) => { const filename = path.join(dist, entry.path) const { type } = entry @@ -46,21 +47,30 @@ abstract class ComponentInstaller { filename, uncompressedSize: entry.extra.uncompressedSize, compressedSize: entry.extra.compressedSize, - extractedSize: 0 + extractedSize: 0, }) const writeStream = fs.createWriteStream(filename) writeStream.on('ready', () => { - entry.on('data', (data: Uint8Array) => { - writeStream.write(data, () => { - const file = files.find(f => f.filename === filename) - if (file) { - file.extractedSize += data.length - const extractedSize = files.reduce((acc, cur) => acc + cur.extractedSize, 0) - const totalSize = files.reduce((acc, cur) => acc + cur.uncompressedSize, 0) - this.unzipHandle.handleUnzipUpdate(extractedSize / totalSize) - } + entry + .on('data', (data: Uint8Array) => { + writeStream.write(data, () => { + const file = files.find(f => f.filename === filename) + if (file) { + file.extractedSize += data.length + const extractedSize = files.reduce( + (acc, cur) => acc + cur.extractedSize, + 0 + ) + const totalSize = files.reduce( + (acc, cur) => acc + cur.uncompressedSize, + 0 + ) + this.unzipHandle.handleUnzipUpdate( + extractedSize / totalSize + ) + } + }) }) - }) .on('end', () => { writeStream.close() }) @@ -70,7 +80,9 @@ abstract class ComponentInstaller { }) }) } else if (type === 'Directory') { - if (!fs.existsSync(filename)) { fs.mkdirSync(filename) } + if (!fs.existsSync(filename)) { + fs.mkdirSync(filename) + } } entry.autodrain() }) @@ -84,15 +96,19 @@ abstract class ComponentInstaller { } protected getRelease = async () => { - if (this.releaseTemp && Date.now() - this.releaseTemp.updated < 5 * 60 * 1000) { + if ( + this.releaseTemp && + Date.now() - this.releaseTemp.updated < 5 * 60 * 1000 + ) { return this.releaseTemp.data } const proxy = await this.getProxy() - const url = 'https://api.github.com/repos/MaaAssistantArknights/MaaRelease/releases' + const url = + 'https://api.github.com/repos/MaaAssistantArknights/MaaRelease/releases' const releaseResponse = await axios.get(url, { adapter: require('axios/lib/adapters/http.js'), - proxy + proxy, }) this.releaseTemp = { data: releaseResponse.data[0], updated: Date.now() } return this.releaseTemp.data @@ -109,27 +125,38 @@ abstract class ComponentInstaller { protocol, auth: { username, - password - } + password, + }, } } return proxy } - protected tryRequest = async (url: string, retryCount = this.maxRetryTimes) => { + protected tryRequest = async ( + url: string, + retryCount = this.maxRetryTimes + ) => { const proxy = await this.getProxy() for (let i = 0; i < retryCount; i++) { try { const response = await axios.get(url, { adapter: require('axios/lib/adapters/http.js'), - proxy + proxy, }) return response } catch (error) { // eslint-disable-next-line vue/max-len - const errorText = `${error.code as string} | ${(error.response?.status) as string|undefined ?? ''} ${(error.response?.statusText) as string|undefined ?? ''}` + const errorText = `${error.code as string} | ${ + (error.response?.status as string | undefined) ?? '' + } ${(error.response?.statusText as string | undefined) ?? ''}` // eslint-disable-next-line vue/max-len - logger.error(`[Component Installer | ${this.componentType}] Error request on URL: ${url}, attempts: ${i + 1}/${retryCount}, Error: ${errorText}`) + logger.error( + `[Component Installer | ${ + this.componentType + }] Error request on URL: ${url}, attempts: ${ + i + 1 + }/${retryCount}, Error: ${errorText}` + ) if (i === retryCount - 1) { return null } @@ -137,7 +164,7 @@ abstract class ComponentInstaller { } } - protected releaseTemp: {data: any, updated: number} | null = null + protected releaseTemp: { data: any; updated: number } | null = null public abstract readonly downloadHandle: DownloadHandle public abstract readonly unzipHandle: UnzipHandle diff --git a/packages/main/componentManager/components/adb.ts b/packages/main/componentManager/components/adb.ts index 2d39aa2b..d2726480 100644 --- a/packages/main/componentManager/components/adb.ts +++ b/packages/main/componentManager/components/adb.ts @@ -8,7 +8,7 @@ export const getComponentAdb = async (): Promise => { const componentAdb: Component = { type: 'Android Platform Tools', status: 'not-installed', - installer: new AdbInstaller() + installer: new AdbInstaller(), } const installed = fs.existsSync(path.join(getAppBaseDir(), 'platform-tools')) diff --git a/packages/main/componentManager/components/core.ts b/packages/main/componentManager/components/core.ts index f6301fd3..84645992 100644 --- a/packages/main/componentManager/components/core.ts +++ b/packages/main/componentManager/components/core.ts @@ -10,7 +10,7 @@ export const getComponentCore = async (): Promise => { const componentCore: Component = { type: 'Maa Core', status: 'not-installed', - installer: new CoreInstaller() + installer: new CoreInstaller(), } const installed = fs.existsSync(path.join(coreLoader.libPath, 'core_version')) diff --git a/packages/main/componentManager/index.ts b/packages/main/componentManager/index.ts index b3ffc8ee..94d3d28e 100644 --- a/packages/main/componentManager/index.ts +++ b/packages/main/componentManager/index.ts @@ -6,14 +6,17 @@ import CoreLoader from '@main/coreLoader' @Singleton class ComponentManager implements Module { - constructor () { - ipcMainHandle('main.ComponentManager:getStatus', + constructor() { + ipcMainHandle( + 'main.ComponentManager:getStatus', async (event, componentName: ComponentType) => { this.components_[componentName] = await this.update[componentName]() return this.components_[componentName]?.status - }) + } + ) - ipcMainHandle('main.ComponentManager:install', + ipcMainHandle( + 'main.ComponentManager:install', async (event, componentName: ComponentType) => { // 安装文件时,需要dispose core,否则无法写入 // TODO core 卸载炸了 @@ -23,21 +26,22 @@ class ComponentManager implements Module { } this.components_[componentName] = await this.update[componentName]() this.components_[componentName]?.installer?.install() - }) + } + ) } - public get name (): string { + public get name(): string { return 'ComponentManager' } - public get version (): string { + public get version(): string { return '1.0.0' } private readonly update: Record Promise> = { 'Maa App': async () => ({ type: 'Maa App', status: 'installed' }), 'Maa Core': getComponentCore, - 'Android Platform Tools': getComponentAdb + 'Android Platform Tools': getComponentAdb, } private readonly components_: Partial> = {} diff --git a/packages/main/componentManager/installers/adb.ts b/packages/main/componentManager/installers/adb.ts index d464a4c0..aa263c74 100644 --- a/packages/main/componentManager/installers/adb.ts +++ b/packages/main/componentManager/installers/adb.ts @@ -10,11 +10,11 @@ import { getPlatform } from '@main/utils/os' @Singleton class AdbInstaller extends ComponentInstaller { - public constructor () { + public constructor() { super() } - public async install (): Promise { + public async install(): Promise { try { if (this.downloader_) { const update = await this.checkUpdate() @@ -28,37 +28,35 @@ class AdbInstaller extends ComponentInstaller { } } - public get status (): InstallerStatus { + public get status(): InstallerStatus { return this.status_ } - protected onStart (): void { + protected onStart(): void {} - } - - protected onProgress (progress: number): void { + protected onProgress(progress: number): void { ipcMainSend('renderer.ComponentManager:updateStatus', { type: this.componentType, status: this.status_, - progress + progress, }) } - protected onCompleted (): void { + protected onCompleted(): void { this.status_ = 'done' ipcMainSend('renderer.ComponentManager:installDone', { type: this.componentType, status: this.status_, - progress: 0 // 不显示进度条 + progress: 0, // 不显示进度条 }) } - protected onException (): void { + protected onException(): void { this.status_ = 'exception' ipcMainSend('renderer.ComponentManager:installInterrupted', { type: this.componentType, status: this.status_, - progress: 0 + progress: 0, }) } @@ -76,7 +74,7 @@ class AdbInstaller extends ComponentInstaller { handleDownloadInterrupted: () => { this.status_ = 'exception' this.onException() - } + }, } public readonly unzipHandle = { @@ -90,28 +88,28 @@ class AdbInstaller extends ComponentInstaller { handleUnzipInterrupted: () => { this.status_ = 'exception' this.onException() - } + }, } - public async checkUpdate (): Promise { + public async checkUpdate(): Promise { const platform = getPlatform() if (platform === 'windows') { return { url: 'https://dl.google.com/android/repository/platform-tools-latest-windows.zip', version: 'latest', - releaseDate: '' + releaseDate: '', } } else if (platform === 'macos') { return { url: 'https://dl.google.com/android/repository/platform-tools-latest-darwin.zip', version: 'latest', - releaseDate: '' + releaseDate: '', } } else { return { url: 'https://dl.google.com/android/repository/platform-tools-latest-linux.zip', version: 'latest', - releaseDate: '' + releaseDate: '', } } } diff --git a/packages/main/componentManager/installers/core.ts b/packages/main/componentManager/installers/core.ts index ec0caaf1..1e712886 100644 --- a/packages/main/componentManager/installers/core.ts +++ b/packages/main/componentManager/installers/core.ts @@ -10,11 +10,11 @@ import { getDownloadUrlSuffix } from '@main/utils/os' @Singleton class CoreInstaller extends ComponentInstaller { - public constructor () { + public constructor() { super() } - public async install (): Promise { + public async install(): Promise { try { if (this.downloader_) { const update = await this.checkUpdate() @@ -35,21 +35,21 @@ class CoreInstaller extends ComponentInstaller { } } - public get status (): InstallerStatus { + public get status(): InstallerStatus { return this.status_ } - protected onStart (): void {} + protected onStart(): void {} - protected onProgress (progress: number): void { + protected onProgress(progress: number): void { ipcMainSend('renderer.ComponentManager:updateStatus', { type: this.componentType, status: this.status_, - progress + progress, }) } - protected onCompleted (): void { + protected onCompleted(): void { this.status_ = 'done' if (fs.existsSync(this.versionFile)) { fs.rmSync(this.versionFile) @@ -58,16 +58,16 @@ class CoreInstaller extends ComponentInstaller { ipcMainSend('renderer.ComponentManager:installDone', { type: this.componentType, status: this.status_, - progress: 0 // 不显示进度条 + progress: 0, // 不显示进度条 }) } - protected onException (): void { + protected onException(): void { this.status_ = 'exception' ipcMainSend('renderer.ComponentManager:installInterrupted', { type: this.componentType, status: this.status_, - progress: 0 + progress: 0, }) } @@ -86,8 +86,8 @@ class CoreInstaller extends ComponentInstaller { handleDownloadInterrupted: () => { this.status_ = 'exception' this.onException() - } - }; + }, + } public readonly unzipHandle = { handleUnzipUpdate: (percent: number) => { @@ -100,28 +100,28 @@ class CoreInstaller extends ComponentInstaller { handleUnzipInterrupted: () => { this.status_ = 'exception' this.onException() - } - }; + }, + } public getRelease = async () => { const apiUrls = [ 'https://gh.cirno.xyz/api.github.com/repos/MaaAssistantArknights/MaaRelease/releases', - 'https://api.github.com/repos/MaaAssistantArknights/MaaRelease/releases' + 'https://api.github.com/repos/MaaAssistantArknights/MaaRelease/releases', ] for (const url of apiUrls) { const releaseResponse = await this.tryRequest(url) if (releaseResponse) { this.releaseTemp = { data: releaseResponse.data[0], - updated: Date.now() + updated: Date.now(), } break } } return this.releaseTemp?.data - }; + } - public async checkUpdate (): Promise { + public async checkUpdate(): Promise { let release = null try { release = await this.getRelease() @@ -175,24 +175,21 @@ class CoreInstaller extends ComponentInstaller { return { url: download.browser_download_url, version: tag_name, - releaseDate: published_at + releaseDate: published_at, } } - protected status_: InstallerStatus = 'pending'; - public downloader_: DownloadManager | null = null; + protected status_: InstallerStatus = 'pending' + public downloader_: DownloadManager | null = null - private readonly versionFile = path.join( - getAppBaseDir(), - 'core/core_version' - ); + private readonly versionFile = path.join(getAppBaseDir(), 'core/core_version') private readonly upgradableFile = path.join( getAppBaseDir(), 'core/core_upgradable' - ); + ) - protected readonly componentType: ComponentType = 'Maa Core'; + protected readonly componentType: ComponentType = 'Maa Core' } export default CoreInstaller diff --git a/packages/main/coreLoader/callback.ts b/packages/main/coreLoader/callback.ts index 03c4881a..d50f4f63 100644 --- a/packages/main/coreLoader/callback.ts +++ b/packages/main/coreLoader/callback.ts @@ -12,7 +12,7 @@ const callbackHandle = ffi.Callback( logger.silly(data) ipcMainSend('renderer.CoreLoader:callback', { code, - data: JSON.parse(data) + data: JSON.parse(data), // customArgs }) } diff --git a/packages/main/coreLoader/index.ts b/packages/main/coreLoader/index.ts index b532acc6..2eda9419 100644 --- a/packages/main/coreLoader/index.ts +++ b/packages/main/coreLoader/index.ts @@ -43,7 +43,7 @@ type AsstInstancePtr = ref.Pointer // type CallBackFunc = (msg: number, detail: string, custom?: any) => any -function createVoidPointer (): ref.Value { +function createVoidPointer(): ref.Value { return ref.alloc(ref.types.void) } @Singleton @@ -52,37 +52,35 @@ class CoreLoader { win32: [ 'opencv_world4_maa.dll', 'onnxruntime_maa.dll', - 'MaaDerpLearning.dll' + 'MaaDerpLearning.dll', ], - linux: [ - 'libiomp5.so', - 'libmklml_intel.so', - 'libmkldnn.so' - ], - darwin: ['libpaddle_inference.dylib'] + linux: ['libiomp5.so', 'libmklml_intel.so', 'libmkldnn.so'], + darwin: ['libpaddle_inference.dylib'], } private readonly libName: Record = { win32: 'MaaCore.dll', darwin: 'MeoAssistant.dylib', - linux: 'libMeoAssistant.so' + linux: 'libMeoAssistant.so', } - private DLib!: ffi.DynamicLibrary; + private DLib!: ffi.DynamicLibrary private static libPath: string - private static readonly libPathKey = 'libPath'; - private static readonly defaultLibPath = path.join(getAppBaseDir(), 'core'); + private static readonly libPathKey = 'libPath' + private static readonly defaultLibPath = path.join(getAppBaseDir(), 'core') private static loadStatus: boolean // core加载状态 - public MeoAsstLib!: any; - private readonly DepLibs: DynamicLibrary[] = []; - MeoAsstPtr: Record = {}; + public MeoAsstLib!: any + private readonly DepLibs: DynamicLibrary[] = [] + MeoAsstPtr: Record = {} - constructor () { + constructor() { // 在构造函数中创建core存储文件夹 CoreLoader.loadStatus = false CoreLoader.libPath = storage.get(CoreLoader.libPathKey) as string if (!_.isString(CoreLoader.libPath) || !existsSync(CoreLoader.libPath)) { - logger.error(`Update resource folder: ${CoreLoader.libPath} --> ${CoreLoader.defaultLibPath}`) + logger.error( + `Update resource folder: ${CoreLoader.libPath} --> ${CoreLoader.defaultLibPath}` + ) CoreLoader.libPath = CoreLoader.defaultLibPath if (!existsSync(CoreLoader.libPath)) mkdirSync(CoreLoader.libPath) } @@ -95,32 +93,32 @@ class CoreLoader { /** * @description 返回组件名 */ - public get name (): string { + public get name(): string { return 'CoreLoader' } /** * @description 返回组件版本 */ - public get version (): string { + public get version(): string { return '1.0.0' } - public get loadStatus (): boolean { + public get loadStatus(): boolean { return CoreLoader.loadStatus } /** * @description 返回core所在目录 */ - public get libPath (): string { + public get libPath(): string { return CoreLoader.libPath } /** * @description 释放core */ - public dispose (): void { + public dispose(): void { if (!CoreLoader.loadStatus) { logger.silly('core already disposed, ignore...') return @@ -143,129 +141,175 @@ class CoreLoader { /** * 加载core */ - public load (): void { + public load(): void { if (CoreLoader.loadStatus) { logger.silly('core already loaded, ignore..') return } try { CoreLoader.loadStatus = true - this.dependences[process.platform].forEach((lib) => { + this.dependences[process.platform].forEach(lib => { this.DepLibs.push(ffi.DynamicLibrary(path.join(this.libPath, lib))) }) - this.DLib = ffi.DynamicLibrary(path.join(this.libPath, this.libName[process.platform]), ffi.RTLD_NOW) - this.MeoAsstLib = - { - AsstSetUserDir: ffi.ForeignFunction(this.DLib.get('AsstSetUserDir'), - BoolType, - [StringType], - ffi.FFI_STDCALL), - - AsstLoadResource: ffi.ForeignFunction(this.DLib.get('AsstLoadResource'), - BoolType, - [StringType], - ffi.FFI_STDCALL), - - AsstSetStaticOption: ffi.ForeignFunction(this.DLib.get('AsstSetStaticOption'), - BoolType, - [IntType, StringType], - ffi.FFI_STDCALL), - - AsstCreate: ffi.ForeignFunction(this.DLib.get('AsstCreate'), - AsstPtrType, - [], - ffi.FFI_STDCALL), - - AsstCreateEx: ffi.ForeignFunction(this.DLib.get('AsstCreateEx'), - AsstPtrType, - ['pointer', CustomArgsType], - ffi.FFI_STDCALL), - - AsstDestroy: ffi.ForeignFunction(this.DLib.get('AsstDestroy'), - VoidType, - [AsstPtrType], - ffi.FFI_STDCALL), - - AsstSetInstanceOption: ffi.ForeignFunction(this.DLib.get('AsstSetInstanceOption'), - BoolType, - [AsstPtrType, IntType, StringType], - ffi.FFI_STDCALL), - - AsstConnect: ffi.ForeignFunction(this.DLib.get('AsstConnect'), - BoolType, - [AsstPtrType, StringType, StringType, StringType], - ffi.FFI_STDCALL), - - AsstAppendTask: ffi.ForeignFunction(this.DLib.get('AsstAppendTask'), - IntType, - [AsstPtrType, StringType, StringType], - ffi.FFI_STDCALL), - - AsstSetTaskParams: ffi.ForeignFunction(this.DLib.get('AsstSetTaskParams'), - BoolType, - [AsstPtrType, IntType, StringType], - ffi.FFI_STDCALL), - - AsstStart: ffi.ForeignFunction(this.DLib.get('AsstStart'), - BoolType, - [AsstPtrType], - ffi.FFI_STDCALL), - - AsstStop: ffi.ForeignFunction(this.DLib.get('AsstStop'), - BoolType, - [AsstPtrType], - ffi.FFI_STDCALL), - - AsstRunning: ffi.ForeignFunction(this.DLib.get('AsstRunning'), - BoolType, - [AsstPtrType], - ffi.FFI_STDCALL), - - AsstAsyncConnect: ffi.ForeignFunction(this.DLib.get('AsstAsyncConnect'), - AsstAsyncCallIdType, - [AsstPtrType, StringType, StringType, StringType, BoolType], - ffi.FFI_STDCALL), - - AsstAsyncClick: ffi.ForeignFunction(this.DLib.get('AsstAsyncClick'), - AsstAsyncCallIdType, - [AsstPtrType, IntType, IntType, BoolType], - ffi.FFI_STDCALL), - - AsstAsyncScreencap: ffi.ForeignFunction(this.DLib.get('AsstAsyncScreencap'), - AsstAsyncCallIdType, - [AsstPtrType, BoolType], - ffi.FFI_STDCALL), - - AsstGetImage: ffi.ForeignFunction(this.DLib.get('AsstGetImage'), - ULLType, - [AsstPtrType, Buff, ULLType], - ffi.FFI_STDCALL), - - AsstGetUUID: ffi.ForeignFunction(this.DLib.get('AsstGetUUID'), - ULLType, - [AsstPtrType, StringType, ULLType], - ffi.FFI_STDCALL), - - AsstGetTasksList: ffi.ForeignFunction(this.DLib.get('AsstGetTasksList'), - ULLType, - [AsstPtrType, IntPointerType, ULLType], - ffi.FFI_STDCALL), - - AsstGetNullSize: ffi.ForeignFunction(this.DLib.get('AsstGetNullSize'), - ULLType, - [], - ffi.FFI_STDCALL), - - AsstGetVersion: ffi.ForeignFunction(this.DLib.get('AsstGetVersion'), - StringType, - [], - ffi.FFI_STDCALL), - - AsstLog: ffi.ForeignFunction(this.DLib.get('AsstLog'), - VoidType, - [StringType, StringType], - ffi.FFI_STDCALL) - } + this.DLib = ffi.DynamicLibrary( + path.join(this.libPath, this.libName[process.platform]), + ffi.RTLD_NOW + ) + this.MeoAsstLib = { + AsstSetUserDir: ffi.ForeignFunction( + this.DLib.get('AsstSetUserDir'), + BoolType, + [StringType], + ffi.FFI_STDCALL + ), + + AsstLoadResource: ffi.ForeignFunction( + this.DLib.get('AsstLoadResource'), + BoolType, + [StringType], + ffi.FFI_STDCALL + ), + + AsstSetStaticOption: ffi.ForeignFunction( + this.DLib.get('AsstSetStaticOption'), + BoolType, + [IntType, StringType], + ffi.FFI_STDCALL + ), + + AsstCreate: ffi.ForeignFunction( + this.DLib.get('AsstCreate'), + AsstPtrType, + [], + ffi.FFI_STDCALL + ), + + AsstCreateEx: ffi.ForeignFunction( + this.DLib.get('AsstCreateEx'), + AsstPtrType, + ['pointer', CustomArgsType], + ffi.FFI_STDCALL + ), + + AsstDestroy: ffi.ForeignFunction( + this.DLib.get('AsstDestroy'), + VoidType, + [AsstPtrType], + ffi.FFI_STDCALL + ), + + AsstSetInstanceOption: ffi.ForeignFunction( + this.DLib.get('AsstSetInstanceOption'), + BoolType, + [AsstPtrType, IntType, StringType], + ffi.FFI_STDCALL + ), + + AsstConnect: ffi.ForeignFunction( + this.DLib.get('AsstConnect'), + BoolType, + [AsstPtrType, StringType, StringType, StringType], + ffi.FFI_STDCALL + ), + + AsstAppendTask: ffi.ForeignFunction( + this.DLib.get('AsstAppendTask'), + IntType, + [AsstPtrType, StringType, StringType], + ffi.FFI_STDCALL + ), + + AsstSetTaskParams: ffi.ForeignFunction( + this.DLib.get('AsstSetTaskParams'), + BoolType, + [AsstPtrType, IntType, StringType], + ffi.FFI_STDCALL + ), + + AsstStart: ffi.ForeignFunction( + this.DLib.get('AsstStart'), + BoolType, + [AsstPtrType], + ffi.FFI_STDCALL + ), + + AsstStop: ffi.ForeignFunction( + this.DLib.get('AsstStop'), + BoolType, + [AsstPtrType], + ffi.FFI_STDCALL + ), + + AsstRunning: ffi.ForeignFunction( + this.DLib.get('AsstRunning'), + BoolType, + [AsstPtrType], + ffi.FFI_STDCALL + ), + + AsstAsyncConnect: ffi.ForeignFunction( + this.DLib.get('AsstAsyncConnect'), + AsstAsyncCallIdType, + [AsstPtrType, StringType, StringType, StringType, BoolType], + ffi.FFI_STDCALL + ), + + AsstAsyncClick: ffi.ForeignFunction( + this.DLib.get('AsstAsyncClick'), + AsstAsyncCallIdType, + [AsstPtrType, IntType, IntType, BoolType], + ffi.FFI_STDCALL + ), + + AsstAsyncScreencap: ffi.ForeignFunction( + this.DLib.get('AsstAsyncScreencap'), + AsstAsyncCallIdType, + [AsstPtrType, BoolType], + ffi.FFI_STDCALL + ), + + AsstGetImage: ffi.ForeignFunction( + this.DLib.get('AsstGetImage'), + ULLType, + [AsstPtrType, Buff, ULLType], + ffi.FFI_STDCALL + ), + + AsstGetUUID: ffi.ForeignFunction( + this.DLib.get('AsstGetUUID'), + ULLType, + [AsstPtrType, StringType, ULLType], + ffi.FFI_STDCALL + ), + + AsstGetTasksList: ffi.ForeignFunction( + this.DLib.get('AsstGetTasksList'), + ULLType, + [AsstPtrType, IntPointerType, ULLType], + ffi.FFI_STDCALL + ), + + AsstGetNullSize: ffi.ForeignFunction( + this.DLib.get('AsstGetNullSize'), + ULLType, + [], + ffi.FFI_STDCALL + ), + + AsstGetVersion: ffi.ForeignFunction( + this.DLib.get('AsstGetVersion'), + StringType, + [], + ffi.FFI_STDCALL + ), + + AsstLog: ffi.ForeignFunction( + this.DLib.get('AsstLog'), + VoidType, + [StringType, StringType], + ffi.FFI_STDCALL + ), + } const version = this.GetCoreVersion() if (version) { logger.info(`core loaded: version ${version}`) @@ -282,7 +326,7 @@ class CoreLoader { * @param path? 未指定就用libPath * @returns */ - public LoadResource (path?: string): Boolean { + public LoadResource(path?: string): Boolean { return this.MeoAsstLib.AsstLoadResource(path ?? this.libPath) } @@ -290,7 +334,7 @@ class CoreLoader { * 创建普通实例, 即无回调版 * @returns 实例指针{ref.Pointer} */ - public Create (): boolean { + public Create(): boolean { this.MeoAsstPtr.placeholder = this.MeoAsstLib.AsstCreate() return !!this.MeoAsstPtr.placeholder } @@ -302,7 +346,7 @@ class CoreLoader { * @param customArg 自定义参数{???} * @returns 是否创建成功 */ - public CreateEx ( + public CreateEx( uuid: string, callback: any = callbackHandle, customArg: any = createVoidPointer() @@ -318,7 +362,7 @@ class CoreLoader { * @description 摧毁实例 * @param uuid 设备唯一标识符 */ - public Destroy (uuid: string): void { + public Destroy(uuid: string): void { if (this.MeoAsstPtr[uuid]) { this.MeoAsstLib.AsstDestroy(this.MeoAsstPtr[uuid]) // eslint-disable-next-line @typescript-eslint/no-dynamic-delete @@ -327,8 +371,18 @@ class CoreLoader { } /** @deprecated 已废弃,将在接下来的版本中移除 */ - public Connect (address: string, uuid: string, adbPath: string, config: string): boolean { - return this.MeoAsstLib.AsstConnect(this.MeoAsstPtr[uuid], adbPath, address, config) + public Connect( + address: string, + uuid: string, + adbPath: string, + config: string + ): boolean { + return this.MeoAsstLib.AsstConnect( + this.MeoAsstPtr[uuid], + adbPath, + address, + config + ) } /** @@ -340,8 +394,20 @@ class CoreLoader { * @param block 是否阻塞 * @returns 是否连接成功 */ - public AsyncConnect (address: string, uuid: string, adbPath: string, config: string, block: boolean = false): number { - return this.MeoAsstLib.AsstAsyncConnect(this.MeoAsstPtr[uuid], adbPath, address, config, block) + public AsyncConnect( + address: string, + uuid: string, + adbPath: string, + config: string, + block: boolean = false + ): number { + return this.MeoAsstLib.AsstAsyncConnect( + this.MeoAsstPtr[uuid], + adbPath, + address, + config, + block + ) } /** @@ -351,8 +417,12 @@ class CoreLoader { * @param params 任务json字符串, 详见文档 * @returns */ - public AppendTask (uuid: string, type: string, params: string): number { - return this.MeoAsstLib.AsstAppendTask(this.GetCoreInstanceByUUID(uuid), type, params) + public AppendTask(uuid: string, type: string, params: string): number { + return this.MeoAsstLib.AsstAppendTask( + this.GetCoreInstanceByUUID(uuid), + type, + params + ) } /** @@ -362,7 +432,7 @@ class CoreLoader { * @param params 任务参数 */ - public SetTaskParams (uuid: string, taskId: number, params: string): boolean { + public SetTaskParams(uuid: string, taskId: number, params: string): boolean { return this.MeoAsstLib.AsstSetTaskParams( this.GetCoreInstanceByUUID(uuid), taskId, @@ -375,7 +445,7 @@ class CoreLoader { * @param uuid 设备唯一标识符 * @returns 是否成功 */ - public Start (uuid: string): boolean { + public Start(uuid: string): boolean { return this.MeoAsstLib.AsstStart(this.GetCoreInstanceByUUID(uuid)) } @@ -384,7 +454,7 @@ class CoreLoader { * @param uuid 设备唯一标识符 * @returns */ - public Stop (uuid: string): boolean { + public Stop(uuid: string): boolean { return this.MeoAsstLib.AsstStop(this.GetCoreInstanceByUUID(uuid)) } @@ -395,7 +465,7 @@ class CoreLoader { * @param y y坐标 * @returns */ - public Click (uuid: string, x: number, y: number): boolean { + public Click(uuid: string, x: number, y: number): boolean { return this.MeoAsstLib.AsstClick(this.GetCoreInstanceByUUID(uuid), x, y) } @@ -405,14 +475,21 @@ class CoreLoader { * @param block 阻塞 * @returns */ - public AsyncScreencap (uuid: string, block: boolean = true): number | boolean { + public AsyncScreencap(uuid: string, block: boolean = true): number | boolean { if (!this.MeoAsstPtr[uuid]) return false - return this.MeoAsstLib.AsstAsyncScreencap(this.GetCoreInstanceByUUID(uuid), block) + return this.MeoAsstLib.AsstAsyncScreencap( + this.GetCoreInstanceByUUID(uuid), + block + ) } - public GetImage (uuid: string): string { + public GetImage(uuid: string): string { const buf = Buffer.alloc(5114514) - const len = this.MeoAsstLib.AsstGetImage(this.GetCoreInstanceByUUID(uuid), buf as any, buf.length) + const len = this.MeoAsstLib.AsstGetImage( + this.GetCoreInstanceByUUID(uuid), + buf as any, + buf.length + ) const buf2 = buf.slice(0, len as number) const v2 = buf2.toString('base64') return v2 @@ -422,24 +499,32 @@ class CoreLoader { * @description core版本 * @returns 版本 */ - public GetCoreVersion (): string | null { + public GetCoreVersion(): string | null { if (!this.loadStatus) return null return this.MeoAsstLib.AsstGetVersion() } - public GetCoreInstanceByUUID (uuid: string): AsstInstancePtr { + public GetCoreInstanceByUUID(uuid: string): AsstInstancePtr { return this.MeoAsstPtr[uuid] } - public Log (level: string, message: string): void { + public Log(level: string, message: string): void { return this.MeoAsstLib.AsstLog(level, message) } - public SetInstanceOption (uuid: string, key: InstanceOptionKey, value: string): boolean { - return this.MeoAsstLib.AsstSetInstanceOption(this.GetCoreInstanceByUUID(uuid), key, value) + public SetInstanceOption( + uuid: string, + key: InstanceOptionKey, + value: string + ): boolean { + return this.MeoAsstLib.AsstSetInstanceOption( + this.GetCoreInstanceByUUID(uuid), + key, + value + ) } - public SetTouchMode (uuid: string, mode: TouchMode): boolean { + public SetTouchMode(uuid: string, mode: TouchMode): boolean { if (!this.MeoAsstPtr[uuid]) { return false } @@ -451,7 +536,7 @@ class CoreLoader { * @param mode TouchMode * @returns is all changes success */ - public ChangeTouchMode (mode: TouchMode): boolean { + public ChangeTouchMode(mode: TouchMode): boolean { for (const uuid in this.MeoAsstPtr) { if (this.MeoAsstPtr[uuid]) { const status = this.SetTouchMode(uuid, mode) diff --git a/packages/main/deviceDetector/adapters/index.ts b/packages/main/deviceDetector/adapters/index.ts index 6199df16..98162997 100644 --- a/packages/main/deviceDetector/adapters/index.ts +++ b/packages/main/deviceDetector/adapters/index.ts @@ -11,4 +11,6 @@ const getAdapter = async (): Promise => { } } -export default (async () => { return await getAdapter() })() +export default (async () => { + return await getAdapter() +})() diff --git a/packages/main/deviceDetector/adapters/linuxAdapter.ts b/packages/main/deviceDetector/adapters/linuxAdapter.ts index a2dfc8aa..190745e1 100644 --- a/packages/main/deviceDetector/adapters/linuxAdapter.ts +++ b/packages/main/deviceDetector/adapters/linuxAdapter.ts @@ -3,27 +3,19 @@ import { Singleton } from '@main/../common/function/singletonDecorator' @Singleton class LinuxEmulator implements EmulatorAdapter { - protected async getBluestack (): Promise { + protected async getBluestack(): Promise {} - } - - protected async getNox (): Promise { - - } + protected async getNox(): Promise {} - protected async getMumu (): Promise { + protected async getMumu(): Promise {} - } - - protected async getLd (): Promise { - - } + protected async getLd(): Promise {} - async getAdbDevices (): Promise { + async getAdbDevices(): Promise { return [] } - async getEmulators (): Promise { + async getEmulators(): Promise { const emulator: Emulator[] = [] const processes = await psList() processes.forEach(process => console.log(process)) diff --git a/packages/main/deviceDetector/adapters/macAdapter.ts b/packages/main/deviceDetector/adapters/macAdapter.ts index 8a75057f..d233d6de 100644 --- a/packages/main/deviceDetector/adapters/macAdapter.ts +++ b/packages/main/deviceDetector/adapters/macAdapter.ts @@ -3,27 +3,19 @@ import { Singleton } from '@main/../common/function/singletonDecorator' @Singleton class MacEmulator implements EmulatorAdapter { - protected async getBluestack (): Promise { + protected async getBluestack(): Promise {} - } - - protected async getNox (): Promise { - - } + protected async getNox(): Promise {} - protected async getMumu (): Promise { + protected async getMumu(): Promise {} - } - - protected async getLd (): Promise { - - } + protected async getLd(): Promise {} - async getAdbDevices (): Promise { + async getAdbDevices(): Promise { return [] } - async getEmulators (): Promise { + async getEmulators(): Promise { const emulator: Emulator[] = [] const processes = await psList() processes.forEach(process => console.log(process)) diff --git a/packages/main/deviceDetector/adapters/winAdapter.ts b/packages/main/deviceDetector/adapters/winAdapter.ts index fe132697..cc619430 100644 --- a/packages/main/deviceDetector/adapters/winAdapter.ts +++ b/packages/main/deviceDetector/adapters/winAdapter.ts @@ -17,13 +17,14 @@ const emulatorList = [ 'NemuHeadless.exe', // mumu模拟器 'MEmuHeadless.exe', // 逍遥模拟器 'Ld9BoxHeadless.exe', // 雷电9 - 'MuMuVMMHeadless.exe' // mumu12 + 'MuMuVMMHeadless.exe', // mumu12 ] const regPNamePid = /(.{3,25}[^\s*])\s*([0-9]*)\s.*\s*/g // get "HD-Player.exe 3396 Console 1 79,692 K" const getPnamePath = async (pname: string): Promise => { - const result = await $`Get-WmiObject -Query "select ExecutablePath FROM Win32_Process where Name='${pname}'" | Select-Object -Property ExecutablePath | ConvertTo-Json` + const result = + await $`Get-WmiObject -Query "select ExecutablePath FROM Win32_Process where Name='${pname}'" | Select-Object -Property ExecutablePath | ConvertTo-Json` const path = JSON.parse(result.stdout) return path.length > 1 ? path[0].ExecutablePath : path.ExecutablePath } @@ -34,15 +35,17 @@ const getPnamePath = async (pname: string): Promise => { // return path.ExecutablePath // } -async function getCommandLine (pid: string | number): Promise { +async function getCommandLine(pid: string | number): Promise { // 获取进程启动参数 const commandLineExp = `Get-WmiObject -Query "select CommandLine FROM Win32_Process where ProcessID='${pid}'" | Select-Object -Property CommandLine | ConvertTo-Json` - const ret: string = JSON.parse((await $`${commandLineExp}`).stdout).CommandLine + const ret: string = JSON.parse( + (await $`${commandLineExp}`).stdout + ).CommandLine logger.silly(`getCommandLine: ${ret}`) return ret } -async function testPort ( +async function testPort( hostname: string, port: number | string, timeout: number = 100 @@ -59,9 +62,9 @@ async function testPort ( return _.trim((await $`${exp}`).stdout).includes('True') } -function getBluestackInstanceName (cmd: string): string { +function getBluestackInstanceName(cmd: string): string { const instanceExp = /".*"\s"?--instance"?\s"?([^"\s]*)"?/g - const res = [...cmd.matchAll(instanceExp)].map((v) => v[1]) + const res = [...cmd.matchAll(instanceExp)].map(v => v[1]) const name = res ? res[0] : 'unknown' logger.info('[winAdapter] Get bluestack instance name: ', name) return name @@ -69,21 +72,27 @@ function getBluestackInstanceName (cmd: string): string { @Singleton class WindowsAdapter implements EmulatorAdapter { - protected async getBluestack (e: Emulator): Promise { + protected async getBluestack(e: Emulator): Promise { // const confPortExp = /bst.instance.Nougat64_?\d?.status.adb_port="(\d{4,6})"/g // const e: Emulator = { pname, pid } e.config = 'BlueStacks' const exePath = JSON.parse( - (await $`Get-WmiObject -Query "select ExecutablePath FROM Win32_Process where ProcessID=${e.pid}" | Select-Object -Property ExecutablePath | ConvertTo-Json`).stdout + ( + await $`Get-WmiObject -Query "select ExecutablePath FROM Win32_Process where ProcessID=${e.pid}" | Select-Object -Property ExecutablePath | ConvertTo-Json` + ).stdout ).ExecutablePath e.adbPath = path.join(path.dirname(exePath), 'HD-Adb.exe') const cmd = await getCommandLine(e.pid) e.commandLine = cmd // 从命令行启动的指令 const arg = getBluestackInstanceName(cmd) const confPath = path.join( - path.normalize(JSON.parse( - (await $`Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\\SOFTWARE\\BlueStacks_nxt | Select-Object -Property UserDefinedDir | ConvertTo-Json`).stdout - ).UserDefinedDir), + path.normalize( + JSON.parse( + ( + await $`Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\\SOFTWARE\\BlueStacks_nxt | Select-Object -Property UserDefinedDir | ConvertTo-Json` + ).stdout + ).UserDefinedDir + ), 'bluestacks.conf' ) if (e.adbPath.includes('BluestacksCN')) { @@ -91,20 +100,26 @@ class WindowsAdapter implements EmulatorAdapter { // 搞两套方案,先读注册表拿adb端口, 如果读失败了可能是打包复制导致,再使用 netstat 尝试 let success: boolean = false try { - const emulatorName: string[] = [...JSON.parse(( - (await $`Get-ChildItem -Path Registry::HKEY_LOCAL_MACHINE\\SOFTWARE\\BlueStacks_china_gmgr\\Guests | ConvertTo-Json`).stdout - ))].map( - (v) => v.PSChildName - ) // 蓝叠CN注册表中的模拟器id + const emulatorName: string[] = [ + ...JSON.parse( + ( + await $`Get-ChildItem -Path Registry::HKEY_LOCAL_MACHINE\\SOFTWARE\\BlueStacks_china_gmgr\\Guests | ConvertTo-Json` + ).stdout + ), + ].map(v => v.PSChildName) // 蓝叠CN注册表中的模拟器id if (emulatorName.length === 0) success = false else { for await (const v of emulatorName) { const port: string = JSON.parse( - (await $`Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\\SOFTWARE\\BlueStacks_china_gmgr\\Guests\\${v}\\Network\\0 | Select-Object -Property InboundRules | ConvertTo-Json`).stdout - ).InboundRules[0].split(':').pop() + ( + await $`Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\\SOFTWARE\\BlueStacks_china_gmgr\\Guests\\${v}\\Network\\0 | Select-Object -Property InboundRules | ConvertTo-Json` + ).stdout + ) + .InboundRules[0].split(':') + .pop() if ( !inUsePorts.includes(port) && - await testPort('127.0.0.1', port) && + (await testPort('127.0.0.1', port)) && !success ) { // 端口没有被占用, 测试端口成功, 本次循环未使用这个端口 @@ -122,7 +137,9 @@ class WindowsAdapter implements EmulatorAdapter { if (!success) { // 通过读注册表失败, 使用 netstat 抓一个5开头的端口充数 const regExp = '\\s*TCP\\s*127.0.0.1:(5\\d{3,4})\\s*' // 提取端口 - const port = (await $`netstat -ano | findstr ${e.pid}`).stdout.match(regExp) + const port = (await $`netstat -ano | findstr ${e.pid}`).stdout.match( + regExp + ) e.address = port != null ? `127.0.0.1:${port[1]}` : '127.0.0.1:5555' e.displayName = 'BlueStack CN [no regedit]' } @@ -132,11 +149,15 @@ class WindowsAdapter implements EmulatorAdapter { `bluestacks.conf not exist! path: ${confPath}` ) const conf = readFileSync(confPath, 'utf-8') // 读bluestacks.conf文件 - const confPortInstanceExp = arg ? RegExp(`bst.instance.${arg}.status.adb_port="(\\d{4,6})"`) : /bst.instance.(?:.*).status.adb_port="(\d{4,6})"/ + const confPortInstanceExp = arg + ? RegExp(`bst.instance.${arg}.status.adb_port="(\\d{4,6})"`) + : /bst.instance.(?:.*).status.adb_port="(\d{4,6})"/ const confPort = conf.match(confPortInstanceExp) console.log('confport:') e.displayName = 'BlueStack Global' - if (confPort) { e.address = `127.0.0.1:${confPort[1]}` } + if (confPort) { + e.address = `127.0.0.1:${confPort[1]}` + } /** e.tag = 'BlueStack Global'; [...conf.matchAll(confPortExp)] @@ -159,7 +180,7 @@ class WindowsAdapter implements EmulatorAdapter { // return e } - protected async getXY (e: Emulator): Promise { + protected async getXY(e: Emulator): Promise { /** * 逍遥模拟器获取流程: * 1. 根据"MEmuHeadless.exe"获取pid @@ -178,14 +199,18 @@ class WindowsAdapter implements EmulatorAdapter { e.commandLine = await getCommandLine(e.pid) const confName = e.commandLine.match(/--comment ([^\s]+)/) if (confName) { - const confPath = path.resolve(path.dirname(await getPnamePath('Memu.exe')), 'MemuHyperv VMs', confName[1], `${confName[1]}.memu`) - logger.silly(`XY conf_path: ${confPath}`) - assert( - existsSync(confPath), - `Memu.memu not exist! path: ${confPath}` + const confPath = path.resolve( + path.dirname(await getPnamePath('Memu.exe')), + 'MemuHyperv VMs', + confName[1], + `${confName[1]}.memu` ) + logger.silly(`XY conf_path: ${confPath}`) + assert(existsSync(confPath), `Memu.memu not exist! path: ${confPath}`) const confDetail = readFileSync(confPath, 'utf-8') // 读Memu.memu文件 - const confPortExp = confDetail.match(/ { + protected async getNox(e: Emulator): Promise { e.config = 'Nox' e.displayName = '夜神模拟器' const noxPath = path.dirname(await getPnamePath('Nox.exe')) @@ -204,16 +229,25 @@ class WindowsAdapter implements EmulatorAdapter { const noxConsoleListArr = noxConsoleList.split('\r\n') for (const line of noxConsoleListArr) { const arr = line.split(',') - if (arr.length > 1 && (arr.pop() as string).toString() === e.pid.toString()) { + if ( + arr.length > 1 && + (arr.pop() as string).toString() === e.pid.toString() + ) { e.commandLine = `"${noxConsole}"` + ` launch -name:${arr[2]}` const vmName = arr[1] - const configPath = path.resolve(noxPath, 'BignoxVMS', vmName, `${vmName}.vbox`) + const configPath = path.resolve( + noxPath, + 'BignoxVMS', + vmName, + `${vmName}.vbox` + ) if (!configPath) { logger.error('Nox config file not exist!', configPath) return } const conf = readFileSync(configPath, 'utf-8') - const confPortInstanceExp = // + const confPortInstanceExp = + // const confPort = conf.match(confPortInstanceExp) if (confPort) { e.address = `127.0.0.1:${confPort[1]}` @@ -227,7 +261,7 @@ class WindowsAdapter implements EmulatorAdapter { } // TODO: 适配新版 mumu, 似乎支持 hyperv 了? - protected async getMumu (e: Emulator): Promise { + protected async getMumu(e: Emulator): Promise { // MuMu的adb端口仅限7555, 所以, 请不要使用MuMu多开! // 流程: 有"NemuHeadless.exe"进程后,就去抓'NemuPlayer.exe'的路径. const emuPathExp = await getPnamePath('NemuPlayer.exe') // 模拟器启动器路径 @@ -242,7 +276,7 @@ class WindowsAdapter implements EmulatorAdapter { e.config = 'MuMuEmulator' } - protected async getMumu12 (e: Emulator): Promise { + protected async getMumu12(e: Emulator): Promise { e.config = 'MuMuEmulator' e.displayName = 'MuMu模拟器12' const emuPath = await getPnamePath('MuMuPlayer.exe') // 模拟器启动器路径 @@ -254,14 +288,17 @@ class WindowsAdapter implements EmulatorAdapter { return } logger.info('Found mumu12, vmName:', vmName[1]) // 寻找模拟器名, 配置在mumu根目录的vms里 - const configPath = path.resolve(emuPath, `../../vms/${vmName[1]}/configs`) + '\\vm_config.json' + const configPath = + path.resolve(emuPath, `../../vms/${vmName[1]}/configs`) + + '\\vm_config.json' if (!existsSync(configPath)) { logger.error('MuMu config file not exist!', configPath) return } const conf = readFileSync(configPath, 'utf-8') try { - const confPort = JSON.parse(conf).vm.nat.port_forward.adb.host_port as string + const confPort = JSON.parse(conf).vm.nat.port_forward.adb + .host_port as string e.address = `127.0.0.1:${confPort}` } catch (e) { logger.error(e) @@ -276,87 +313,126 @@ class WindowsAdapter implements EmulatorAdapter { } } - protected async getLd (e: Emulator): Promise { + protected async getLd(e: Emulator): Promise { // 雷电模拟器识别 e.config = 'LDPlayer' e.displayName = '雷电模拟器' const emulatorPath = await getPnamePath('dnplayer.exe') // dnplayer.exe路径, 和模拟器配置信息等在一起 - const consolePath = path.resolve(path.dirname(emulatorPath), 'dnconsole.exe') // dnconsole.exe路径, 用于启动模拟器 + const consolePath = path.resolve( + path.dirname(emulatorPath), + 'dnconsole.exe' + ) // dnconsole.exe路径, 用于启动模拟器 e.adbPath = path.resolve(path.dirname(emulatorPath), 'adb.exe') // adb路径 const cmd = await getCommandLine(e.pid) // headless.exe的启动参数, 实际上是不可用的, 提取其中的comment为模拟器真实名称, statvm为模拟器uuid const statvm = cmd.match(/--startvm (\w*-\w*-\w*-\w*-\w*)/) // 获取模拟器uuid, statvm const realName = cmd.match(/--comment ([\d+\w]*) /) // 获取真实名称, realName if (!realName || !statvm) return - const confPath = path.resolve(path.dirname(emulatorPath), 'vms', 'config', `${realName[1]}.config`) // 模拟器配置文件路径 + const confPath = path.resolve( + path.dirname(emulatorPath), + 'vms', + 'config', + `${realName[1]}.config` + ) // 模拟器配置文件路径 assert( existsSync(confPath), `${realName[1]}.config not exist! path: ${confPath}` ) const confDetail = readFileSync(confPath, 'utf-8') // 读config logger.silly(confDetail) - const displayName = confDetail.match(/"statusSettings.playerName":\s*"([^"]+)"/) // 读配置文件, 获取模拟器显示名称 displayName - if (displayName) { // 当新建模拟器时, 不一定会有此选项, 如果没有, 则取realName最后一个数字, 手动拼接 + const displayName = confDetail.match( + /"statusSettings.playerName":\s*"([^"]+)"/ + ) // 读配置文件, 获取模拟器显示名称 displayName + if (displayName) { + // 当新建模拟器时, 不一定会有此选项, 如果没有, 则取realName最后一个数字, 手动拼接 e.commandLine = '"' + consolePath + '" launch --name ' + displayName[1] // 真实命令行启动指令 } else { - e.commandLine = '"' + consolePath + '" launch --name 雷电模拟器-' + realName[1].slice(-1) // 真实命令行启动指令 + e.commandLine = + '"' + + consolePath + + '" launch --name 雷电模拟器-' + + realName[1].slice(-1) // 真实命令行启动指令 } const LdVBoxHeadlessPath = await getPnamePath('LdVBoxHeadless.exe') // LdVBoxHeadless.exe路径 - const VBoxManagePath = path.resolve(path.dirname(LdVBoxHeadlessPath), 'VBoxManage.exe') // VBoxManage.exe路径 - const port = (await $`"${VBoxManagePath}" showvminfo ${statvm[1]} --machinereadable`).stdout.match(/Forwarding\(1\)="tcp_5\d\d\d_5\d\d\d,tcp,,(\d*),,/) + const VBoxManagePath = path.resolve( + path.dirname(LdVBoxHeadlessPath), + 'VBoxManage.exe' + ) // VBoxManage.exe路径 + const port = ( + await $`"${VBoxManagePath}" showvminfo ${statvm[1]} --machinereadable` + ).stdout.match(/Forwarding\(1\)="tcp_5\d\d\d_5\d\d\d,tcp,,(\d*),,/) if (port) { e.address = `127.0.0.1:${port[1]}` } } - protected async getLd9 (e: Emulator): Promise { + protected async getLd9(e: Emulator): Promise { // 雷电9模拟器识别 e.config = 'LDPlayer' e.displayName = '雷电模拟器9' const emulatorPath = await getPnamePath('dnplayer.exe') // dnplayer.exe路径, 和模拟器配置信息等在一起 - const consolePath = path.resolve(path.dirname(emulatorPath), 'ldconsole.exe') // dnconsole.exe路径, 用于启动模拟器 + const consolePath = path.resolve( + path.dirname(emulatorPath), + 'ldconsole.exe' + ) // dnconsole.exe路径, 用于启动模拟器 e.adbPath = path.resolve(path.dirname(emulatorPath), 'adb.exe') // adb路径 const cmd = await getCommandLine(e.pid) // headless.exe的启动参数, 实际上是不可用的, 提取其中的comment为模拟器真实名称, statvm为模拟器uuid const statvm = cmd.match(/--startvm (\w*-\w*-\w*-\w*-\w*)/) // 获取模拟器uuid, statvm const realName = cmd.match(/--comment ([\d+\w]*) /) // 获取真实名称, realName if (!realName || !statvm) return - const confPath = path.resolve(path.dirname(emulatorPath), 'vms', 'config', `${realName[1]}.config`) // 模拟器配置文件路径 + const confPath = path.resolve( + path.dirname(emulatorPath), + 'vms', + 'config', + `${realName[1]}.config` + ) // 模拟器配置文件路径 assert( existsSync(confPath), - `${realName[1]}.config not exist! path: ${confPath}` + `${realName[1]}.config not exist! path: ${confPath}` ) const confDetail = readFileSync(confPath, 'utf-8') // 读config - const displayName = confDetail.match(/"statusSettings.playerName":\s*"([^"]+)"/) // 读配置文件, 获取模拟器显示名称 displayName - if (displayName) { // 当新建模拟器时, 不一定会有此选项, 如果没有, 则取realName最后一个数字, 手动拼接 + const displayName = confDetail.match( + /"statusSettings.playerName":\s*"([^"]+)"/ + ) // 读配置文件, 获取模拟器显示名称 displayName + if (displayName) { + // 当新建模拟器时, 不一定会有此选项, 如果没有, 则取realName最后一个数字, 手动拼接 e.commandLine = '"' + consolePath + '" launch --name ' + displayName[1] // 真实命令行启动指令 } else { const launchIndexReg = RegExp(`(\\d+),.*,\\d+,\\d+,\\d+,\\d+,${e.pid},.*`) - const emulatorIndex = (await $`${consolePath} list2`).stdout.match(launchIndexReg) // 匹配当前正在运行的模拟器列表, 寻找索引 + const emulatorIndex = (await $`${consolePath} list2`).stdout.match( + launchIndexReg + ) // 匹配当前正在运行的模拟器列表, 寻找索引 if (emulatorIndex) { logger.info('Get LD9 Emulator Index: ', emulatorIndex[1]) - e.commandLine = '"' + consolePath + '" launch --index ' + emulatorIndex[1] // 真实命令行启动指令 + e.commandLine = + '"' + consolePath + '" launch --index ' + emulatorIndex[1] // 真实命令行启动指令 } } const Ld9VBoxHeadlessPath = await getPnamePath('Ld9BoxHeadless.exe') // LdVBoxHeadless.exe路径 - const VBoxManagePath = path.resolve(path.dirname(Ld9VBoxHeadlessPath), 'VBoxManage.exe') // VBoxManage.exe路径 - const port = (await $`"${VBoxManagePath}" showvminfo ${statvm[1]} --machinereadable`).stdout.match(/Forwarding\(1\)="tcp_5\d\d\d_5\d\d\d,tcp,,(\d*),,/) + const VBoxManagePath = path.resolve( + path.dirname(Ld9VBoxHeadlessPath), + 'VBoxManage.exe' + ) // VBoxManage.exe路径 + const port = ( + await $`"${VBoxManagePath}" showvminfo ${statvm[1]} --machinereadable` + ).stdout.match(/Forwarding\(1\)="tcp_5\d\d\d_5\d\d\d,tcp,,(\d*),,/) if (port) { e.address = `127.0.0.1:${port[1]}` } } - async getAdbDevices (): Promise { + async getAdbDevices(): Promise { const emulators: Device[] = [] return emulators } - async getEmulators (): Promise { + async getEmulators(): Promise { inUsePorts.splice(0, inUsePorts.length) const emulators: Emulator[] = [] const { stdout: tasklist } = await $`tasklist` tasklist .toString() .split('\n') - .forEach((line) => { + .forEach(line => { const res = line.matchAll(regPNamePid) for (const match of res) { if (emulatorList.includes(match[1])) { @@ -392,8 +468,16 @@ class WindowsAdapter implements EmulatorAdapter { } logger.silly(`emulator: ${JSON.stringify(e)}`) } - emulators.forEach((e) => { - if (e.address && e.uuid && e.adbPath && e.config && e.commandLine && e.displayName) availableEmulators.push(e) + emulators.forEach(e => { + if ( + e.address && + e.uuid && + e.adbPath && + e.config && + e.commandLine && + e.displayName + ) + availableEmulators.push(e) }) return availableEmulators } diff --git a/packages/main/deviceDetector/hooks.ts b/packages/main/deviceDetector/hooks.ts index 77a77621..7a0f77c4 100644 --- a/packages/main/deviceDetector/hooks.ts +++ b/packages/main/deviceDetector/hooks.ts @@ -3,46 +3,55 @@ import { defaultAdbPath, getDeviceUuid, isDefaultAdbExists } from './utils' import { $$, $ } from '@main/utils/shell' import { parseArguments } from '@main/utils/arguments' -export function useEmulatorHooks (adapter: Promise): void { - ipcMainHandle('main.DeviceDetector:getEmulators', +export function useEmulatorHooks(adapter: Promise): void { + ipcMainHandle( + 'main.DeviceDetector:getEmulators', async (event): Promise => { return await (await adapter).getEmulators() } ) - ipcMainHandle('main.DeviceDetector:getAdbDevices', + ipcMainHandle( + 'main.DeviceDetector:getAdbDevices', async (event): Promise => { return await (await adapter).getAdbDevices() } ) - ipcMainHandle('main.DeviceDetector:getDeviceUuid', - async (event, address: string, adbPath?: string) => await getDeviceUuid(address, adbPath)) + ipcMainHandle( + 'main.DeviceDetector:getDeviceUuid', + async (event, address: string, adbPath?: string) => + await getDeviceUuid(address, adbPath) + ) - ipcMainHandle('main.DeviceDetector:getAdbPath', + ipcMainHandle( + 'main.DeviceDetector:getAdbPath', async (event): Promise => { return defaultAdbPath } ) - ipcMainHandle('main.DeviceDetector:startEmulator', + ipcMainHandle( + 'main.DeviceDetector:startEmulator', async (event, path: string): Promise => { const args = parseArguments(path) // eslint-disable-next-line await $$(args.splice(0, 1)[0], args) - }) + } + ) - ipcMainHandle('main.DeviceDetector:startEmulator2', + ipcMainHandle( + 'main.DeviceDetector:startEmulator2', async (event, path: string): Promise => { - // eslint-disable-next-line - await $`${path}` - }) + // eslint-disable-next-line + await $`${path}` + } + ) - ipcMainHandle('main.DeviceDetector:isDefaultAdbExists', - (event): boolean => { - return isDefaultAdbExists() - }) + ipcMainHandle('main.DeviceDetector:isDefaultAdbExists', (event): boolean => { + return isDefaultAdbExists() + }) } -export function removeEmulatorHooks (): void { +export function removeEmulatorHooks(): void { ipcMainRemove('main.DeviceDetector:getEmulators') ipcMainRemove('main.DeviceDetector:getAdbDevices') } diff --git a/packages/main/deviceDetector/index.ts b/packages/main/deviceDetector/index.ts index 5db3b697..1c55a200 100644 --- a/packages/main/deviceDetector/index.ts +++ b/packages/main/deviceDetector/index.ts @@ -5,16 +5,16 @@ import adapters from './adapters' @Singleton class DeviceDetector implements Module { private readonly adapter: Promise - constructor () { + constructor() { this.adapter = adapters useEmulatorHooks(this.adapter) } - public get name (): string { + public get name(): string { return 'DeviceDetector' } - public get version (): string { + public get version(): string { return '1.0.0' } @@ -22,7 +22,7 @@ class DeviceDetector implements Module { * @description 获取所有能识别到的模拟器信息 * @returns {Promise} */ - public async getEmulators (): Promise { + public async getEmulators(): Promise { return await (await this.adapter).getEmulators() } @@ -30,7 +30,7 @@ class DeviceDetector implements Module { * @description 获取通过执行`adb devices`得到的设备 * @returns {Promise} */ - public async getAdbDevices (): Promise { + public async getAdbDevices(): Promise { return await (await this.adapter).getAdbDevices() } } diff --git a/packages/main/deviceDetector/utils.ts b/packages/main/deviceDetector/utils.ts index 7f08e5fc..108f7d5d 100644 --- a/packages/main/deviceDetector/utils.ts +++ b/packages/main/deviceDetector/utils.ts @@ -15,11 +15,14 @@ export const defaultAdbPath = path.join( `adb${executableSuffix}` ) -export function isDefaultAdbExists (): boolean { +export function isDefaultAdbExists(): boolean { return fs.existsSync(defaultAdbPath) } -export async function getDeviceUuid (address: string, adbPath = defaultAdbPath): Promise { +export async function getDeviceUuid( + address: string, + adbPath = defaultAdbPath +): Promise { if (!adbPath) { logger.error('adb_path is null') return false @@ -30,7 +33,8 @@ export async function getDeviceUuid (address: string, adbPath = defaultAdbPath): } const connectResult = (await $$(adbPath, ['connect', address])).stdout if (connectResult.includes('connected')) { - const ret = await $`"${adbPath}" -s ${address} shell settings get secure android_id` + const ret = + await $`"${adbPath}" -s ${address} shell settings get secure android_id` if (ret) return _.trim(ret.stdout) } return false diff --git a/packages/main/downloadManager/index.ts b/packages/main/downloadManager/index.ts index 3332eda4..3ec10030 100644 --- a/packages/main/downloadManager/index.ts +++ b/packages/main/downloadManager/index.ts @@ -1,198 +1,212 @@ -import type { BrowserWindow } from 'electron' - -import path from 'path' -import fs from 'fs' - -import WindowManager from '@main/windowManager' -import ComponentInstaller from '@main/componentManager/componentInstaller' -import CoreInstaller from '@main/componentManager/installers/core' -import AdbInstaller from '@main/componentManager/installers/adb' -import { Singleton } from '@common/function/singletonDecorator' -import { getAppBaseDir } from '@main/utils/path' - -@Singleton -class DownloadManager { - constructor () { - // initialize variables - this.window_ = new WindowManager().getWindow() - this.tasks_ = { - 'Maa App': { - state: 'interrupted', - paused: false, - savePath: '', - progress: { - prevReceivedBytes: 0, - receivedBytes: 0 - } - }, - 'Maa Core': { - state: 'interrupted', - paused: false, - savePath: '', - progress: { - prevReceivedBytes: 0, - receivedBytes: 0 - } - }, - 'Android Platform Tools': { - state: 'interrupted', - paused: false, - savePath: '', - progress: { - prevReceivedBytes: 0, - receivedBytes: 0 - } - } - } - this.installers_ = { - 'Maa Core': new CoreInstaller(), - 'Android Platform Tools': new AdbInstaller() - } - - for (const installer of Object.values(this.installers_)) { - installer.downloader_ = this - } - if (!fs.existsSync(this.download_path)) { - fs.mkdirSync(this.download_path) - } - // register hook - this.window_.webContents.session.on('will-download', this.handleWillDownload) - } - - public get name (): string { - return 'DownloadManager' - } - - public get version (): string { - return '1.0.0' - } - - public async downloadComponent (url: string, component: ComponentType): Promise { - this.will_download_ = component - this.window_.webContents.downloadURL(url) - } - - // 用于应对强制关闭 - public pauseDownload = (component: ComponentType): void => { - this.tasks_[component]._sourceItem?.pause() - } - - // 用于应对强制关闭 - public cancelDownload = (component: ComponentType): void => { - this.tasks_[component]._sourceItem?.cancel() - } - - private readonly handleWillDownload = (event: Electron.Event, item: Electron.DownloadItem): void => { - const component = this.will_download_ - if (!component) { - event.preventDefault() - return - } - - item.setSavePath(path.join(this.download_path, item.getFilename())) - - this.tasks_[component] = { - state: item.getState(), - startTime: item.getStartTime() * 1000, - speed: 0, - progress: { - totalBytes: item.getTotalBytes(), - receivedBytes: 0, - prevReceivedBytes: 0, - precent: 0 - }, - paused: item.isPaused(), - savePath: item.getSavePath(), - _sourceItem: item - } - - // 将文件存储到this.download_path - if (fs.existsSync(item.getSavePath())) { - this.handleDownloadCompleted(event, item, component) - this.will_download_ = undefined - event.preventDefault() - return - } - - item.on('updated', (event, state) => { - this.handleDownloadUpdate(event, state, item, component) - }) - - item.on('done', (event, state) => { - const receivedBytes = item.getReceivedBytes() - this.tasks_[component].progress.receivedBytes = receivedBytes - this.tasks_[component].state = state - - switch (state) { - case 'completed': - this.handleDownloadCompleted(event, item, component) - break - case 'cancelled': - case 'interrupted': - this.handleDownloadInterrupted(event, item, component) - break - } - }) - - this.will_download_ = undefined - } - - private readonly handleDownloadUpdate = ( - event: Electron.Event, - state: 'interrupted' | 'progressing', - item: Electron.DownloadItem, - component: ComponentType - ): void => { - const receivedBytes = item.getReceivedBytes() - const totalBytes = item.getTotalBytes() - - this.tasks_[component].state = state - this.tasks_[component].speed = receivedBytes - this.tasks_[component].progress.prevReceivedBytes - this.tasks_[component].progress.receivedBytes = receivedBytes - this.tasks_[component].progress.prevReceivedBytes = receivedBytes - this.tasks_[component].progress.totalBytes = totalBytes - this.tasks_[component].progress.precent = totalBytes ? (receivedBytes / totalBytes) : undefined - this.tasks_[component].paused = item.isPaused() - - const installer = this.installers_[component] - if (installer) { - installer.downloadHandle.handleDownloadUpdate(this.tasks_[component]) - } - } - - private readonly handleDownloadInterrupted = ( - event: Electron.Event, - item: Electron.DownloadItem, - component: ComponentType - ): void => { - fs.rmSync(path.join(item.getSavePath(), item.getFilename())) - const installer = this.installers_[component] - if (installer) { - installer.downloadHandle.handleDownloadInterrupted() - } - } - - private readonly handleDownloadCompleted = ( - event: Electron.Event, - item: Electron.DownloadItem, - component: ComponentType - ): void => { - const installer = this.installers_[component] - if (installer) { - installer.downloadHandle.handleDownloadCompleted(this.tasks_[component]) - } - } - - private tasks_: { [component in ComponentType]: DownloadTask } - - // 这个变量用于存储已经触发window_.webContents.downloadURL但是还未触发'will-download'事件的临时变量 - // 理论上downloadURL后会立刻触发'will-download'事件 - // 但是未验证在两句不同的downloadURL后的行为是否能够达到预期 - private will_download_?: ComponentType - - private readonly window_: BrowserWindow - private readonly installers_: { [component in ComponentType]?: ComponentInstaller } - private readonly download_path = path.join(getAppBaseDir(), 'download') -} - -export default DownloadManager +import type { BrowserWindow } from 'electron' + +import path from 'path' +import fs from 'fs' + +import WindowManager from '@main/windowManager' +import ComponentInstaller from '@main/componentManager/componentInstaller' +import CoreInstaller from '@main/componentManager/installers/core' +import AdbInstaller from '@main/componentManager/installers/adb' +import { Singleton } from '@common/function/singletonDecorator' +import { getAppBaseDir } from '@main/utils/path' + +@Singleton +class DownloadManager { + constructor() { + // initialize variables + this.window_ = new WindowManager().getWindow() + this.tasks_ = { + 'Maa App': { + state: 'interrupted', + paused: false, + savePath: '', + progress: { + prevReceivedBytes: 0, + receivedBytes: 0, + }, + }, + 'Maa Core': { + state: 'interrupted', + paused: false, + savePath: '', + progress: { + prevReceivedBytes: 0, + receivedBytes: 0, + }, + }, + 'Android Platform Tools': { + state: 'interrupted', + paused: false, + savePath: '', + progress: { + prevReceivedBytes: 0, + receivedBytes: 0, + }, + }, + } + this.installers_ = { + 'Maa Core': new CoreInstaller(), + 'Android Platform Tools': new AdbInstaller(), + } + + for (const installer of Object.values(this.installers_)) { + installer.downloader_ = this + } + if (!fs.existsSync(this.download_path)) { + fs.mkdirSync(this.download_path) + } + // register hook + this.window_.webContents.session.on( + 'will-download', + this.handleWillDownload + ) + } + + public get name(): string { + return 'DownloadManager' + } + + public get version(): string { + return '1.0.0' + } + + public async downloadComponent( + url: string, + component: ComponentType + ): Promise { + this.will_download_ = component + this.window_.webContents.downloadURL(url) + } + + // 用于应对强制关闭 + public pauseDownload = (component: ComponentType): void => { + this.tasks_[component]._sourceItem?.pause() + } + + // 用于应对强制关闭 + public cancelDownload = (component: ComponentType): void => { + this.tasks_[component]._sourceItem?.cancel() + } + + private readonly handleWillDownload = ( + event: Electron.Event, + item: Electron.DownloadItem + ): void => { + const component = this.will_download_ + if (!component) { + event.preventDefault() + return + } + + item.setSavePath(path.join(this.download_path, item.getFilename())) + + this.tasks_[component] = { + state: item.getState(), + startTime: item.getStartTime() * 1000, + speed: 0, + progress: { + totalBytes: item.getTotalBytes(), + receivedBytes: 0, + prevReceivedBytes: 0, + precent: 0, + }, + paused: item.isPaused(), + savePath: item.getSavePath(), + _sourceItem: item, + } + + // 将文件存储到this.download_path + if (fs.existsSync(item.getSavePath())) { + this.handleDownloadCompleted(event, item, component) + this.will_download_ = undefined + event.preventDefault() + return + } + + item.on('updated', (event, state) => { + this.handleDownloadUpdate(event, state, item, component) + }) + + item.on('done', (event, state) => { + const receivedBytes = item.getReceivedBytes() + this.tasks_[component].progress.receivedBytes = receivedBytes + this.tasks_[component].state = state + + switch (state) { + case 'completed': + this.handleDownloadCompleted(event, item, component) + break + case 'cancelled': + case 'interrupted': + this.handleDownloadInterrupted(event, item, component) + break + } + }) + + this.will_download_ = undefined + } + + private readonly handleDownloadUpdate = ( + event: Electron.Event, + state: 'interrupted' | 'progressing', + item: Electron.DownloadItem, + component: ComponentType + ): void => { + const receivedBytes = item.getReceivedBytes() + const totalBytes = item.getTotalBytes() + + this.tasks_[component].state = state + this.tasks_[component].speed = + receivedBytes - this.tasks_[component].progress.prevReceivedBytes + this.tasks_[component].progress.receivedBytes = receivedBytes + this.tasks_[component].progress.prevReceivedBytes = receivedBytes + this.tasks_[component].progress.totalBytes = totalBytes + this.tasks_[component].progress.precent = totalBytes + ? receivedBytes / totalBytes + : undefined + this.tasks_[component].paused = item.isPaused() + + const installer = this.installers_[component] + if (installer) { + installer.downloadHandle.handleDownloadUpdate(this.tasks_[component]) + } + } + + private readonly handleDownloadInterrupted = ( + event: Electron.Event, + item: Electron.DownloadItem, + component: ComponentType + ): void => { + fs.rmSync(path.join(item.getSavePath(), item.getFilename())) + const installer = this.installers_[component] + if (installer) { + installer.downloadHandle.handleDownloadInterrupted() + } + } + + private readonly handleDownloadCompleted = ( + event: Electron.Event, + item: Electron.DownloadItem, + component: ComponentType + ): void => { + const installer = this.installers_[component] + if (installer) { + installer.downloadHandle.handleDownloadCompleted(this.tasks_[component]) + } + } + + private tasks_: { [component in ComponentType]: DownloadTask } + + // 这个变量用于存储已经触发window_.webContents.downloadURL但是还未触发'will-download'事件的临时变量 + // 理论上downloadURL后会立刻触发'will-download'事件 + // 但是未验证在两句不同的downloadURL后的行为是否能够达到预期 + private will_download_?: ComponentType + + private readonly window_: BrowserWindow + private readonly installers_: { + [component in ComponentType]?: ComponentInstaller + } + private readonly download_path = path.join(getAppBaseDir(), 'download') +} + +export default DownloadManager diff --git a/packages/main/hooks/asst.ts b/packages/main/hooks/asst.ts index 2c919d66..b870c43c 100644 --- a/packages/main/hooks/asst.ts +++ b/packages/main/hooks/asst.ts @@ -7,8 +7,8 @@ import logger from '@main/utils/logger' const core = new CoreLoader() const asstHooks: Record< -string, -(event: import('electron').IpcMainInvokeEvent, ...args: any[]) => Promise + string, + (event: import('electron').IpcMainInvokeEvent, ...args: any[]) => Promise > = { 'main.CoreLoader:loadResource': async (_event, arg) => { return core.LoadResource(arg.path) ?? false @@ -24,35 +24,22 @@ string, return true }, 'main.CoreLoader:connect': async (_event, arg) => { - return core.Connect( - arg.address, - arg.uuid, - arg.adb_path, - arg.config - ) + return core.Connect(arg.address, arg.uuid, arg.adb_path, arg.config) }, /** @Deprecated */ 'main.CoreLoader:initCore': async (_event, arg: InitCoreParam) => { const createStatus = core.CreateEx(arg.uuid) ?? false if (!createStatus) logger.warn(`重复创建 ${JSON.stringify(arg)}`) - if (!core.SetTouchMode(arg.uuid, arg.touch_mode)) logger.warn('Set touch mode failed', arg.touch_mode) - return core.Connect( - arg.address, - arg.uuid, - arg.adb_path, - arg.config - ) + if (!core.SetTouchMode(arg.uuid, arg.touch_mode)) + logger.warn('Set touch mode failed', arg.touch_mode) + return core.Connect(arg.address, arg.uuid, arg.adb_path, arg.config) }, 'main.CoreLoader:initCoreAsync': async (_event, arg: InitCoreParam) => { const createStatus = core.CreateEx(arg.uuid) ?? false if (!createStatus) logger.warn(`重复创建 ${JSON.stringify(arg)}`) - if (!core.SetTouchMode(arg.uuid, arg.touch_mode)) logger.warn('Set touch mode failed', arg.touch_mode) - return core.AsyncConnect( - arg.address, - arg.uuid, - arg.adb_path, - arg.config - ) + if (!core.SetTouchMode(arg.uuid, arg.touch_mode)) + logger.warn('Set touch mode failed', arg.touch_mode) + return core.AsyncConnect(arg.address, arg.uuid, arg.adb_path, arg.config) }, 'main.CoreLoader:disconnectAndDestroy': async (_event, arg) => { core.Stop(arg.uuid) @@ -61,11 +48,7 @@ string, }, 'main.CoreLoader:appendTask': async (_event, arg) => { - return core.AppendTask( - arg.uuid, - arg.type, - JSON.stringify(arg.params) - ) + return core.AppendTask(arg.uuid, arg.type, JSON.stringify(arg.params)) }, 'main.CoreLoader:setTaskParams': async (_event, arg) => { return core.SetTaskParams(arg.uuid, arg.task_id, JSON.stringify(arg.params)) @@ -76,14 +59,14 @@ string, 'main.CoreLoader:stop': async (_event, arg) => { return core.Stop(arg.uuid) }, - 'main.CoreLoader:supportedStages': async (_event) => { + 'main.CoreLoader:supportedStages': async _event => { if (!core.loadStatus) { logger.silly('core unloaded, return empty supported stages') return [] } const jsonPath = path.join(core.libPath, 'resource/tasks.json') const tasks = JSON.parse(String(fs.readFileSync(jsonPath))) - const stages = Object.keys(tasks).filter((s) => + const stages = Object.keys(tasks).filter(s => /[A-Z0-9]+-([A-Z0-9]+-?)?[0-9]/.test(s) ) return stages @@ -99,11 +82,11 @@ string, }, 'main.CoreLoader:asyncScreencap': async (_event, arg) => { return core.AsyncScreencap(arg.uuid) - } + }, } -export default function useAsstHooks (): void { - ipcMainHandle('main.CoreLoader:load', (_event) => { +export default function useAsstHooks(): void { + ipcMainHandle('main.CoreLoader:load', _event => { core.load() if (!core.loadStatus) { return false @@ -118,7 +101,7 @@ export default function useAsstHooks (): void { return true }) - ipcMainHandle('main.CoreLoader:dispose', (_event) => { + ipcMainHandle('main.CoreLoader:dispose', _event => { for (const eventName of Object.keys(asstHooks)) { ipcMainRemove(eventName as IpcMainHandleEvent) } diff --git a/packages/main/hooks/clear.ts b/packages/main/hooks/clear.ts index 4bc41d96..ea0d744b 100644 --- a/packages/main/hooks/clear.ts +++ b/packages/main/hooks/clear.ts @@ -1,64 +1,64 @@ -import fs from 'fs' -import path from 'path' -import { app } from 'electron' -import { manager } from '@main/utils/logger' -import { ipcMainHandle } from '@main/utils/ipc-main' -import { getAppBaseDir } from '@main/utils/path' - -export default function useClearHooks (): void { - ipcMainHandle('main.Util:GetCacheInfo', async (event) => { - const info = { - log: 0, - download: 0 - } - const logFileNames = fs.readdirSync(manager.logFileDir) - for (const file of logFileNames) { - const stat = fs.statSync(path.join(manager.logFileDir, file)) - if (stat.isFile()) { - info.log += stat.size - } - } - const downloadDir = path.join(getAppBaseDir(), 'download') - const downloadFileNames = fs.readdirSync(downloadDir) - - for (const file of downloadFileNames) { - const stat = fs.statSync(path.join(downloadDir, file)) - if (stat.isFile()) { - info.download += stat.size - } - } - return info - }) - - ipcMainHandle('main.Util:CleanLogs', async (event) => { - const logFileNames = fs.readdirSync(manager.logFileDir) - for (const file of logFileNames) { - const filepath = path.join(manager.logFileDir, file) - if (filepath === manager.logFilePath) { - continue - } - const stat = fs.statSync(filepath) - if (stat.isFile()) { - fs.rmSync(filepath) - } - } - }) - - ipcMainHandle('main.Util:CleanDownloadCache', async (event) => { - const downloadDir = path.join(getAppBaseDir(), 'download') - const downloadFileNames = fs.readdirSync(downloadDir) - - for (const file of downloadFileNames) { - const filepath = path.join(downloadDir, file) - const stat = fs.statSync(filepath) - if (stat.isFile()) { - fs.rmSync(filepath) - } - } - }) - - ipcMainHandle('main.Util:RemoveAllConfig', async (event) => { - fs.writeFileSync(path.join(app.getPath('temp'), 'clearConfigToken'), '1') - app.quit() - }) -} +import fs from 'fs' +import path from 'path' +import { app } from 'electron' +import { manager } from '@main/utils/logger' +import { ipcMainHandle } from '@main/utils/ipc-main' +import { getAppBaseDir } from '@main/utils/path' + +export default function useClearHooks(): void { + ipcMainHandle('main.Util:GetCacheInfo', async event => { + const info = { + log: 0, + download: 0, + } + const logFileNames = fs.readdirSync(manager.logFileDir) + for (const file of logFileNames) { + const stat = fs.statSync(path.join(manager.logFileDir, file)) + if (stat.isFile()) { + info.log += stat.size + } + } + const downloadDir = path.join(getAppBaseDir(), 'download') + const downloadFileNames = fs.readdirSync(downloadDir) + + for (const file of downloadFileNames) { + const stat = fs.statSync(path.join(downloadDir, file)) + if (stat.isFile()) { + info.download += stat.size + } + } + return info + }) + + ipcMainHandle('main.Util:CleanLogs', async event => { + const logFileNames = fs.readdirSync(manager.logFileDir) + for (const file of logFileNames) { + const filepath = path.join(manager.logFileDir, file) + if (filepath === manager.logFilePath) { + continue + } + const stat = fs.statSync(filepath) + if (stat.isFile()) { + fs.rmSync(filepath) + } + } + }) + + ipcMainHandle('main.Util:CleanDownloadCache', async event => { + const downloadDir = path.join(getAppBaseDir(), 'download') + const downloadFileNames = fs.readdirSync(downloadDir) + + for (const file of downloadFileNames) { + const filepath = path.join(downloadDir, file) + const stat = fs.statSync(filepath) + if (stat.isFile()) { + fs.rmSync(filepath) + } + } + }) + + ipcMainHandle('main.Util:RemoveAllConfig', async event => { + fs.writeFileSync(path.join(app.getPath('temp'), 'clearConfigToken'), '1') + app.quit() + }) +} diff --git a/packages/main/hooks/index.ts b/packages/main/hooks/index.ts index 6c5de668..b4d2e395 100644 --- a/packages/main/hooks/index.ts +++ b/packages/main/hooks/index.ts @@ -1,19 +1,19 @@ -import useVersionHooks from './version' -import useAsstHooks from './asst' -import useStorageHooks from './storage' -import useOsHooks from './os' -import useShutdownHooks from './shutdown' -import useClearHooks from './clear' -import useTaskHooks from './task' -import usePathHooks from './path' - -export default function useHooks (): void { - useVersionHooks() - useAsstHooks() - useStorageHooks() - useOsHooks() - useShutdownHooks() - useClearHooks() - useTaskHooks() - usePathHooks() -} +import useVersionHooks from './version' +import useAsstHooks from './asst' +import useStorageHooks from './storage' +import useOsHooks from './os' +import useShutdownHooks from './shutdown' +import useClearHooks from './clear' +import useTaskHooks from './task' +import usePathHooks from './path' + +export default function useHooks(): void { + useVersionHooks() + useAsstHooks() + useStorageHooks() + useOsHooks() + useShutdownHooks() + useClearHooks() + useTaskHooks() + usePathHooks() +} diff --git a/packages/main/hooks/os.ts b/packages/main/hooks/os.ts index 2720c4c0..7d5d9a96 100644 --- a/packages/main/hooks/os.ts +++ b/packages/main/hooks/os.ts @@ -1,8 +1,11 @@ -import { ipcMainHandle } from '@main/utils/ipc-main' -import { getArch, getPlatform, getSystemInformation } from '@main/utils/os' - -export default function useOsHooks (): void { - ipcMainHandle('main.Util:getOsArch', async (event) => getArch()) - ipcMainHandle('main.Util:getOsPlatform', async (event) => getPlatform()) - ipcMainHandle('main.Util:getSystemInformation', async (event) => await getSystemInformation()) -} +import { ipcMainHandle } from '@main/utils/ipc-main' +import { getArch, getPlatform, getSystemInformation } from '@main/utils/os' + +export default function useOsHooks(): void { + ipcMainHandle('main.Util:getOsArch', async event => getArch()) + ipcMainHandle('main.Util:getOsPlatform', async event => getPlatform()) + ipcMainHandle( + 'main.Util:getSystemInformation', + async event => await getSystemInformation() + ) +} diff --git a/packages/main/hooks/path.ts b/packages/main/hooks/path.ts index d3bc50d9..a195c79d 100644 --- a/packages/main/hooks/path.ts +++ b/packages/main/hooks/path.ts @@ -1,6 +1,6 @@ import { ipcMainHandle } from '@main/utils/ipc-main' import { openFolder } from '@main/utils/path' -export default function usePathHooks (): void { +export default function usePathHooks(): void { ipcMainHandle('main.Util:openFolder', async (event, type) => openFolder(type)) } diff --git a/packages/main/hooks/shutdown.ts b/packages/main/hooks/shutdown.ts index 89184676..4df41f9f 100644 --- a/packages/main/hooks/shutdown.ts +++ b/packages/main/hooks/shutdown.ts @@ -4,7 +4,7 @@ import WindowManager from '@main/windowManager' import logger from '@main/utils/logger' import { ipcMainHandle } from '@main/utils/ipc-main' -async function shutdownEmulator (pid: string): Promise { +async function shutdownEmulator(pid: string): Promise { logger.silly('Shutdown Emulator') const platform = getPlatform() @@ -17,14 +17,14 @@ async function shutdownEmulator (pid: string): Promise { } } -async function shutdownMAA (): Promise { +async function shutdownMAA(): Promise { logger.silly('Shutdown MAA') const win = new WindowManager().getWindow() win.close() } -async function shutdownComputer (): Promise { +async function shutdownComputer(): Promise { logger.silly('Shutdown MAA') const platform = getPlatform() @@ -37,7 +37,7 @@ async function shutdownComputer (): Promise { } } -export default function useShutdownHooks (): void { +export default function useShutdownHooks(): void { ipcMainHandle('main.ScheduleRunner:shutdown', async (event, arg) => { if (arg.option === 'shutdownEmulator') { await shutdownEmulator(arg.pid) diff --git a/packages/main/hooks/storage.ts b/packages/main/hooks/storage.ts index bc5a29be..54cdff17 100644 --- a/packages/main/hooks/storage.ts +++ b/packages/main/hooks/storage.ts @@ -3,15 +3,18 @@ import { ipcMainHandle } from '@main/utils/ipc-main' const storage = new Storage() -export default function useStorageHooks (): void { +export default function useStorageHooks(): void { ipcMainHandle('main.StorageManager:get', async (event, key: string) => { return storage.get(key) }) - ipcMainHandle('main.StorageManager:set', async (event, key: string, val: any) => { - storage.set(key, val) - return true - }) + ipcMainHandle( + 'main.StorageManager:set', + async (event, key: string, val: any) => { + storage.set(key, val) + return true + } + ) ipcMainHandle('main.StorageManager:has', async (event, key: string) => { return storage.has(key) diff --git a/packages/main/hooks/task.ts b/packages/main/hooks/task.ts index 8e59e1f9..fa84f38b 100644 --- a/packages/main/hooks/task.ts +++ b/packages/main/hooks/task.ts @@ -2,17 +2,20 @@ import fs from 'fs' import { ipcMainHandle } from '@main/utils/ipc-main' import logger from '@main/utils/logger' -export default function useTaskHooks (): void { - ipcMainHandle('main.Task:readInfrastConfig', async (event, args: {filepath: string}) => { - if (!fs.existsSync(args.filepath)) { - logger.error('readInfrastConfig error, file not exist', args.filepath) - return '' +export default function useTaskHooks(): void { + ipcMainHandle( + 'main.Task:readInfrastConfig', + async (event, args: { filepath: string }) => { + if (!fs.existsSync(args.filepath)) { + logger.error('readInfrastConfig error, file not exist', args.filepath) + return '' + } + try { + return fs.readFileSync(args.filepath, 'utf-8') + } catch (error) { + logger.error('readInfrastConfig error', error, args.filepath) + return '' + } } - try { - return fs.readFileSync(args.filepath, 'utf-8') - } catch (error) { - logger.error('readInfrastConfig error', error, args.filepath) - return '' - } - }) + ) } diff --git a/packages/main/hooks/version.ts b/packages/main/hooks/version.ts index 8127d2d6..12d8f269 100644 --- a/packages/main/hooks/version.ts +++ b/packages/main/hooks/version.ts @@ -1,13 +1,13 @@ -import { app } from 'electron' -import CoreLoader from '@main/coreLoader' -import { ipcMainHandle } from '@main/utils/ipc-main' - -export default function useVersionHooks (): void { - ipcMainHandle('main.Util:getUiVersion', async (event) => { - return app.getVersion() - }) - - ipcMainHandle('main.CoreLoader:getCoreVersion', async (event) => { - return (new CoreLoader()).GetCoreVersion() - }) -} +import { app } from 'electron' +import CoreLoader from '@main/coreLoader' +import { ipcMainHandle } from '@main/utils/ipc-main' + +export default function useVersionHooks(): void { + ipcMainHandle('main.Util:getUiVersion', async event => { + return app.getVersion() + }) + + ipcMainHandle('main.CoreLoader:getCoreVersion', async event => { + return new CoreLoader().GetCoreVersion() + }) +} diff --git a/packages/main/index.ts b/packages/main/index.ts index bf41719a..48dfa673 100644 --- a/packages/main/index.ts +++ b/packages/main/index.ts @@ -29,7 +29,7 @@ if (!app.requestSingleInstanceLock()) { process.exit(0) } -async function createApp (): Promise { +async function createApp(): Promise { const win = new WindowManager().getWindow() if (app.isPackaged || !isInDev()) { win.loadFile(join(__dirname, '../renderer/index.html')) @@ -46,7 +46,7 @@ async function createApp (): Promise { CoreLoader, DeviceDetector, DownloadManager, - ComponentManager + ComponentManager, ] for (const Ctor of modulesCtor) { diff --git a/packages/main/storageManager/index.ts b/packages/main/storageManager/index.ts index 7c285389..89ded0d4 100644 --- a/packages/main/storageManager/index.ts +++ b/packages/main/storageManager/index.ts @@ -14,11 +14,12 @@ type StorageOption = Partial<{ deserialize: (raw: string) => T }> -const convertExt = (str?: string): string | undefined => (/\.\w+/.test(str ?? '') ? str : undefined) +const convertExt = (str?: string): string | undefined => + /\.\w+/.test(str ?? '') ? str : undefined @Singleton class Storage implements Module { - constructor (option?: StorageOption) { + constructor(option?: StorageOption) { this.m_storage = option?.defaults ?? Object.create({}) this.m_cwd = option?.cwd ?? app.getPath('userData') this.m_ext = convertExt(option?.ext) ?? '.json' @@ -38,35 +39,35 @@ class Storage implements Module { } } - public get name (): string { + public get name(): string { return 'StorageManager' } - public get version (): string { + public get version(): string { return '1.0.0' } - get (key: string): any { + get(key: string): any { return _.get(this.m_storage, key) } - set (key: string, value: any): void { + set(key: string, value: any): void { _.set(this.m_storage, key, value) this.saveToFile() } - has (key: string): boolean { + has(key: string): boolean { return _.has(this.m_storage, key) } - public get filepath (): string { + public get filepath(): string { return path.join(this.m_cwd, this.m_name + this.m_ext) } private readonly saveToFile = _.debounce(() => { fs.writeFileSync(this.filepath, this.m_serialize(this.m_storage)) logger.silly('configuration saved') - }, 50); + }, 50) private readonly readFromFile = () => { try { @@ -75,13 +76,13 @@ class Storage implements Module { } catch (error) { logger.error('error while read config file:', this.filepath, error) } - }; + } - private readonly m_cwd: string; - private readonly m_name: string; - private readonly m_ext: string; - private readonly m_serialize; - private readonly m_deserialize; + private readonly m_cwd: string + private readonly m_name: string + private readonly m_ext: string + private readonly m_serialize + private readonly m_deserialize private m_storage: T } diff --git a/packages/main/utils/arguments.ts b/packages/main/utils/arguments.ts index 1a5e7f23..44160f80 100644 --- a/packages/main/utils/arguments.ts +++ b/packages/main/utils/arguments.ts @@ -1,6 +1,6 @@ type LexState = 'normal' | 'string' -export function parseArguments (command: string): string[] { +export function parseArguments(command: string): string[] { let state: LexState = 'normal' let singleQuote = false let tempString = '' @@ -17,7 +17,7 @@ export function parseArguments (command: string): string[] { } break } - case '\'': { + case "'": { if (state === 'normal') { state = 'string' singleQuote = true @@ -53,7 +53,9 @@ export function parseArguments (command: string): string[] { args.push(tempString.trim()) } if (state === 'string') { - throw Error(`${singleQuote ? 'Single quote' : 'Double quote'} is not closed`) + throw Error( + `${singleQuote ? 'Single quote' : 'Double quote'} is not closed` + ) } return args } diff --git a/packages/main/utils/debug.ts b/packages/main/utils/debug.ts index 09aed23a..21dc622e 100644 --- a/packages/main/utils/debug.ts +++ b/packages/main/utils/debug.ts @@ -1,34 +1,45 @@ -import installExtension from 'electron-devtools-installer' -import { BrowserWindow } from 'electron' -import logger from './logger' - -function useDebug (window: BrowserWindow): void { - installExtension('nhdogjmejiglipccpnnnanhbledajbpd') - .then((name) => logger.info(`Added Extension: ${name}`)) - .catch((err: Error) => - logger.error(`An error occurred while install extension: ${err.message}`) - ) - window.webContents.openDevTools({ mode: 'detach' }) - // Bypass CORS - window.webContents.session.webRequest.onBeforeSendHeaders( - { - urls: ['https://prts.wiki/*', 'https://maa.alisaqaq.moe/*', 'https://penguin-stats.io/*'] - }, - (details, callback) => { - details.requestHeaders.Origin = new URL(details.url).origin - callback(details) - } - ) - window.webContents.session.webRequest.onHeadersReceived( - { - urls: ['https://prts.wiki/*', 'https://maa.alisaqaq.moe/*', 'https://penguin-stats.io/*'] - }, - (details, callback) => { - const corsHeader = { 'access-control-allow-origin': '*' } - details.responseHeaders = Object.assign(details.responseHeaders ?? {}, corsHeader) - callback(details) - } - ) -} - -export default useDebug +import installExtension from 'electron-devtools-installer' +import { BrowserWindow } from 'electron' +import logger from './logger' + +function useDebug(window: BrowserWindow): void { + installExtension('nhdogjmejiglipccpnnnanhbledajbpd') + .then(name => logger.info(`Added Extension: ${name}`)) + .catch((err: Error) => + logger.error(`An error occurred while install extension: ${err.message}`) + ) + window.webContents.openDevTools({ mode: 'detach' }) + // Bypass CORS + window.webContents.session.webRequest.onBeforeSendHeaders( + { + urls: [ + 'https://prts.wiki/*', + 'https://maa.alisaqaq.moe/*', + 'https://penguin-stats.io/*', + ], + }, + (details, callback) => { + details.requestHeaders.Origin = new URL(details.url).origin + callback(details) + } + ) + window.webContents.session.webRequest.onHeadersReceived( + { + urls: [ + 'https://prts.wiki/*', + 'https://maa.alisaqaq.moe/*', + 'https://penguin-stats.io/*', + ], + }, + (details, callback) => { + const corsHeader = { 'access-control-allow-origin': '*' } + details.responseHeaders = Object.assign( + details.responseHeaders ?? {}, + corsHeader + ) + callback(details) + } + ) +} + +export default useDebug diff --git a/packages/main/utils/dialog.ts b/packages/main/utils/dialog.ts index 3da21d93..9261086a 100644 --- a/packages/main/utils/dialog.ts +++ b/packages/main/utils/dialog.ts @@ -1 +1 @@ -import { ipcMainHandle } from './ipc-main' +import { ipcMainHandle } from './ipc-main' diff --git a/packages/main/utils/encoding.ts b/packages/main/utils/encoding.ts index 1c61c0bb..b1c466c5 100644 --- a/packages/main/utils/encoding.ts +++ b/packages/main/utils/encoding.ts @@ -1,10 +1,12 @@ -import iconv from 'iconv-lite' - -export const utf8Encode = (str: string): Buffer => iconv.encode(str, 'utf8') -export const gb2312Encode = (str: string): Buffer => iconv.encode(str, 'gb2312') - -export const utf8Decode = (buf: Buffer): string => iconv.decode(buf, 'utf8') -export const gb2312Decode = (buf: Buffer): string => iconv.decode(buf, 'gb2312') - -export const utf8ToGb2312 = (str: string): string => gb2312Decode(utf8Encode(str)) -export const gb2312ToUtf8 = (str: string): string => utf8Decode(gb2312Encode(str)) +import iconv from 'iconv-lite' + +export const utf8Encode = (str: string): Buffer => iconv.encode(str, 'utf8') +export const gb2312Encode = (str: string): Buffer => iconv.encode(str, 'gb2312') + +export const utf8Decode = (buf: Buffer): string => iconv.decode(buf, 'utf8') +export const gb2312Decode = (buf: Buffer): string => iconv.decode(buf, 'gb2312') + +export const utf8ToGb2312 = (str: string): string => + gb2312Decode(utf8Encode(str)) +export const gb2312ToUtf8 = (str: string): string => + utf8Decode(gb2312Encode(str)) diff --git a/packages/main/utils/logger.ts b/packages/main/utils/logger.ts index b2ec5d0f..374d174f 100644 --- a/packages/main/utils/logger.ts +++ b/packages/main/utils/logger.ts @@ -6,12 +6,12 @@ import { createWriteStream, mkdirSync, existsSync, WriteStream } from 'fs' import { getAppBaseDir } from './path' class Logger { - public constructor () { + public constructor() { this.main_ = new tslog.Logger({ - name: 'main' + name: 'main', }) this.renderer_ = new tslog.Logger({ - name: 'renderer' + name: 'renderer', }) if (!existsSync(this.log_file_dir_)) { @@ -21,30 +21,33 @@ class Logger { const date = format(new Date(), 'yyyyMMdd') this.log_file_path_ = path.join(this.log_file_dir_, `Maa App-${date}.log`) - this.log_file_ = createWriteStream( - this.log_file_path_, - { flags: 'a' } + this.log_file_ = createWriteStream(this.log_file_path_, { flags: 'a' }) + + this.main_.attachTransport( + { + silly: this.logToTransport, + debug: this.logToTransport, + trace: this.logToTransport, + info: this.logToTransport, + warn: this.logToTransport, + error: this.logToTransport, + fatal: this.logToTransport, + }, + 'debug' ) - this.main_.attachTransport({ - silly: this.logToTransport, - debug: this.logToTransport, - trace: this.logToTransport, - info: this.logToTransport, - warn: this.logToTransport, - error: this.logToTransport, - fatal: this.logToTransport - }, 'debug') - - this.renderer_.attachTransport({ - silly: this.logToTransport, - debug: this.logToTransport, - trace: this.logToTransport, - info: this.logToTransport, - warn: this.logToTransport, - error: this.logToTransport, - fatal: this.logToTransport - }, 'debug') + this.renderer_.attachTransport( + { + silly: this.logToTransport, + debug: this.logToTransport, + trace: this.logToTransport, + info: this.logToTransport, + warn: this.logToTransport, + error: this.logToTransport, + fatal: this.logToTransport, + }, + 'debug' + ) ipcMainHandle('main.Util:LogSilly', (event, ...params) => { this.renderer_.silly(...params) @@ -70,26 +73,27 @@ class Logger { } private readonly logToTransport = (logObject: ILogObject): void => { - this.main_.getChildLogger({ - colorizePrettyLogs: false, - dateTimeTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone, - prettyInspectOptions: { - colors: false, - depth: 20 - } - }) + this.main_ + .getChildLogger({ + colorizePrettyLogs: false, + dateTimeTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + prettyInspectOptions: { + colors: false, + depth: 20, + }, + }) .printPrettyLog(this.log_file_, logObject) } - public get logFilePath (): string { + public get logFilePath(): string { return this.log_file_path_ } - public get logFileDir (): string { + public get logFileDir(): string { return this.log_file_dir_ } - public get main (): tslog.Logger { + public get main(): tslog.Logger { return this.main_ } diff --git a/packages/main/utils/os.ts b/packages/main/utils/os.ts index d7d15cbc..711a86f3 100644 --- a/packages/main/utils/os.ts +++ b/packages/main/utils/os.ts @@ -1,56 +1,57 @@ -import SystemInformation from 'systeminformation' -import electron from 'electron' - -export const getArch = (): Api.Maa.Arch => { - let arch: Api.Maa.Arch = 'NoArch' - switch (process.arch) { - case 'x64': - arch = 'x64' - break - case 'arm64': - arch = 'arm64' - break - } - return arch -} - -export const getPlatform = (): Api.Maa.Platform => { - let platform: Api.Maa.Platform = 'NoPlatform' - switch (process.platform) { - case 'win32': - platform = 'windows' - break - case 'darwin': - platform = 'macos' - break - case 'linux': - platform = 'linux' - break - } - return platform -} - -export const getSystemInformation = async (): Promise => { - return await SystemInformation.getStaticData() -} - -export const getDownloadUrlSuffix = (): string => { - let ret = '' - const platform = getPlatform() - switch (platform) { - case 'windows': - ret = '-win-x64' - break - case 'macos': - ret = '-macos' - break - } - return ret -} - -export const isInDev = (): boolean => { - // https://github.com/sindresorhus/electron-is-dev/blob/main/index.js - const isEnvSet = 'ELECTRON_IS_DEV' in process.env - const getFromEnv = Number.parseInt(process.env.ELECTRON_IS_DEV as string, 10) === 1 - return isEnvSet ? getFromEnv : !electron.app.isPackaged -} +import SystemInformation from 'systeminformation' +import electron from 'electron' + +export const getArch = (): Api.Maa.Arch => { + let arch: Api.Maa.Arch = 'NoArch' + switch (process.arch) { + case 'x64': + arch = 'x64' + break + case 'arm64': + arch = 'arm64' + break + } + return arch +} + +export const getPlatform = (): Api.Maa.Platform => { + let platform: Api.Maa.Platform = 'NoPlatform' + switch (process.platform) { + case 'win32': + platform = 'windows' + break + case 'darwin': + platform = 'macos' + break + case 'linux': + platform = 'linux' + break + } + return platform +} + +export const getSystemInformation = async (): Promise => { + return await SystemInformation.getStaticData() +} + +export const getDownloadUrlSuffix = (): string => { + let ret = '' + const platform = getPlatform() + switch (platform) { + case 'windows': + ret = '-win-x64' + break + case 'macos': + ret = '-macos' + break + } + return ret +} + +export const isInDev = (): boolean => { + // https://github.com/sindresorhus/electron-is-dev/blob/main/index.js + const isEnvSet = 'ELECTRON_IS_DEV' in process.env + const getFromEnv = + Number.parseInt(process.env.ELECTRON_IS_DEV as string, 10) === 1 + return isEnvSet ? getFromEnv : !electron.app.isPackaged +} diff --git a/packages/main/utils/path.ts b/packages/main/utils/path.ts index 4dd08ec2..2f69dadc 100644 --- a/packages/main/utils/path.ts +++ b/packages/main/utils/path.ts @@ -1,7 +1,8 @@ import { app, shell } from 'electron' import path from 'path' -export const getAppBaseDir = (): string => path.join(app.getPath('appData'), app.getName()) +export const getAppBaseDir = (): string => + path.join(app.getPath('appData'), app.getName()) export const openFolder = (type: 'core' | 'ui-log' | 'core-log'): void => { const baseAppDir = getAppBaseDir() diff --git a/packages/main/utils/proxy.ts b/packages/main/utils/proxy.ts index 5b27477c..e9332807 100644 --- a/packages/main/utils/proxy.ts +++ b/packages/main/utils/proxy.ts @@ -1,10 +1,12 @@ -import type { BrowserWindow } from 'electron' - -async function detectSystemProxy (window: BrowserWindow): Promise { - const ses = window.webContents.session - const resolved = await ses.resolveProxy('https://www.google.com') - const proxy = resolved.replace('PROXY', '').trim() - return proxy === 'DIRECT' ? undefined : `http://${proxy}` -} - -export default detectSystemProxy +import type { BrowserWindow } from 'electron' + +async function detectSystemProxy( + window: BrowserWindow +): Promise { + const ses = window.webContents.session + const resolved = await ses.resolveProxy('https://www.google.com') + const proxy = resolved.replace('PROXY', '').trim() + return proxy === 'DIRECT' ? undefined : `http://${proxy}` +} + +export default detectSystemProxy diff --git a/packages/main/utils/shell.ts b/packages/main/utils/shell.ts index 9c322ea4..33aa030a 100644 --- a/packages/main/utils/shell.ts +++ b/packages/main/utils/shell.ts @@ -8,14 +8,18 @@ interface ProcessOutput { stderr: string } -const textDecoder = (buf: Buffer): string => iconv.decode(buf, getPlatform() === 'windows' ? 'gb2312' : 'utf8') +const textDecoder = (buf: Buffer): string => + iconv.decode(buf, getPlatform() === 'windows' ? 'gb2312' : 'utf8') /** * 异步执行shell命令, * 示例: $\`tasklist | findstr ${name}\` * @returns Promise<{stdout:string, stderr:string}> */ -export async function $ (pieces: TemplateStringsArray, ...args: string[]): Promise { +export async function $( + pieces: TemplateStringsArray, + ...args: string[] +): Promise { const ret = { stderr: '', stdout: '' } let cmd = pieces[0] @@ -28,10 +32,10 @@ export async function $ (pieces: TemplateStringsArray, ...args: string[]): Promi logger.info(`exec: ${cmd}`) try { - const { stdout, stderr } = (await execa(cmd, { + const { stdout, stderr } = await execa(cmd, { shell: getPlatform() === 'windows' ? 'powershell' : 'bash', - encoding: null - })) + encoding: null, + }) ret.stdout = textDecoder(stdout) ret.stderr = textDecoder(stderr) } catch (err: any) { @@ -50,13 +54,17 @@ export async function $ (pieces: TemplateStringsArray, ...args: string[]): Promi * @param args 参数 * @returns Promise<{stdout:string, stderr:string}> */ -export async function $$ (file: string, args?: string[]): Promise { +export async function $$( + file: string, + args?: string[] +): Promise { const ret = { stderr: '', stdout: '' } logger.info(`exec: ${file} ${args ? args.join(' ') : ''}`) try { - const { stdout, stderr } = (await execa(file, args, { - encoding: null, detached: true - })) + const { stdout, stderr } = await execa(file, args, { + encoding: null, + detached: true, + }) ret.stdout = textDecoder(stdout) ret.stderr = textDecoder(stderr) } catch (err: any) { diff --git a/packages/main/utils/ui.ts b/packages/main/utils/ui.ts index b3f700f1..b31ca711 100644 --- a/packages/main/utils/ui.ts +++ b/packages/main/utils/ui.ts @@ -1,9 +1,10 @@ -import { ipcMainSend } from '@main/utils/ipc-main' -import type { MessageOptions } from 'naive-ui/lib/message/index' - -export default { - message: (message: string, options: MessageOptions = {}) => ipcMainSend('renderer.WindowManager:showMessage', { - message, - options - }) -} +import { ipcMainSend } from '@main/utils/ipc-main' +import type { MessageOptions } from 'naive-ui/lib/message/index' + +export default { + message: (message: string, options: MessageOptions = {}) => + ipcMainSend('renderer.WindowManager:showMessage', { + message, + options, + }), +} diff --git a/packages/main/vite.config.ts b/packages/main/vite.config.ts index 18943f6d..423d4786 100644 --- a/packages/main/vite.config.ts +++ b/packages/main/vite.config.ts @@ -10,15 +10,15 @@ export default defineConfig({ resolve: { alias: { '@main': __dirname, - '@common': path.join(__dirname, '../common') - } + '@common': path.join(__dirname, '../common'), + }, }, build: { outDir: '../../dist/main', lib: { entry: 'index.ts', formats: ['cjs'], - fileName: () => '[name].cjs' + fileName: () => '[name].cjs', }, minify: process.env./* from mode option */ NODE_ENV === 'production', sourcemap: true, @@ -27,8 +27,8 @@ export default defineConfig({ external: [ 'electron', ...builtinModules, - ...Object.keys(pkg.dependencies || {}) - ] - } - } + ...Object.keys(pkg.dependencies || {}), + ], + }, + }, }) diff --git a/packages/main/windowManager/control.ts b/packages/main/windowManager/control.ts index c67e054f..71074938 100644 --- a/packages/main/windowManager/control.ts +++ b/packages/main/windowManager/control.ts @@ -13,14 +13,14 @@ type DialogProperty = | 'treatPackageAsDirectory' | 'dontAddToRecent' -export default function useController (window: BrowserWindow): void { +export default function useController(window: BrowserWindow): void { ipcMain.on('main.WindowManager:closeWindow', () => { if (window.isClosable()) { window.close() } }) - ipcMain.handle('main.WindowManager:toggleMaximized', async (event) => { + ipcMain.handle('main.WindowManager:toggleMaximized', async event => { if (!window.isMaximized() && window.isMaximizable()) { window.maximize() return true @@ -32,35 +32,44 @@ export default function useController (window: BrowserWindow): void { } }) - ipcMain.handle('main.WindowManager:minimize', async (event) => { + ipcMain.handle('main.WindowManager:minimize', async event => { if (window.isMinimizable()) { window.minimize() return true } }) - ipcMain.handle('main.WindowManager:isMaximized', async (event) => { + ipcMain.handle('main.WindowManager:isMaximized', async event => { return window.isMaximized() }) window.on('maximize', () => { - window.webContents.send('renderer.WindowManager:updateMaximized', window.isMaximized()) + window.webContents.send( + 'renderer.WindowManager:updateMaximized', + window.isMaximized() + ) }) window.on('unmaximize', () => { - window.webContents.send('renderer.WindowManager:updateMaximized', window.isMaximized()) + window.webContents.send( + 'renderer.WindowManager:updateMaximized', + window.isMaximized() + ) }) - ipcMainHandle('main.WindowManager:openDialog', async ( - event, - title: string, - properties: DialogProperty[], - filters: Electron.FileFilter[] - ) => { - return await dialog.showOpenDialog(window, { - title: title, - properties: properties, - filters: filters - }) - }) + ipcMainHandle( + 'main.WindowManager:openDialog', + async ( + event, + title: string, + properties: DialogProperty[], + filters: Electron.FileFilter[] + ) => { + return await dialog.showOpenDialog(window, { + title: title, + properties: properties, + filters: filters, + }) + } + ) } diff --git a/packages/main/windowManager/index.ts b/packages/main/windowManager/index.ts index 10eaf06e..39ce50ce 100644 --- a/packages/main/windowManager/index.ts +++ b/packages/main/windowManager/index.ts @@ -6,24 +6,30 @@ import useTheme from './theme' import Storage from '@main/storageManager' import { getPlatform } from '@main/utils/os' -const createWindow = (options?: BrowserWindowConstructorOptions): BrowserWindow => { +const createWindow = ( + options?: BrowserWindowConstructorOptions +): BrowserWindow => { const storage = new Storage() - const module = getPlatform() === 'windows' && storage.get('theme.acrylic') - ? require('electron-acrylic-window') - : require('electron') + const module = + getPlatform() === 'windows' && storage.get('theme.acrylic') + ? require('electron-acrylic-window') + : require('electron') const Ctor = module.BrowserWindow return new Ctor(options) } @Singleton class WindowManager implements Module { - constructor () { + constructor() { const storage = new Storage() this.window_ = createWindow({ transparent: true, frame: false, icon: join(__dirname, '../renderer/assets/icon.png'), - vibrancy: getPlatform() === 'macos' && storage.get('theme.acrylic') ? 'under-window' : 'appearance-based', + vibrancy: + getPlatform() === 'macos' && storage.get('theme.acrylic') + ? 'under-window' + : 'appearance-based', width: 1024, height: 768, minWidth: 800, @@ -32,28 +38,28 @@ class WindowManager implements Module { // 编译后的模块仍然是main/index.cjs preload: join(__dirname, '../preload/index.cjs'), sandbox: true, - contextIsolation: true - } + contextIsolation: true, + }, }) useController(this.window_) useTheme(this.window_) } - public get name (): string { + public get name(): string { return 'WindowManager' } - public get version (): string { + public get version(): string { return '1.0.0' } private readonly window_: BrowserWindow - public getWindow (): BrowserWindow { + public getWindow(): BrowserWindow { return this.window_ } - public destoryWindow (): void { + public destoryWindow(): void { if (this.window_ !== null) { this.window_.destroy() } diff --git a/packages/main/windowManager/theme.ts b/packages/main/windowManager/theme.ts index 4dfbdb40..698dfd6a 100644 --- a/packages/main/windowManager/theme.ts +++ b/packages/main/windowManager/theme.ts @@ -3,28 +3,34 @@ import { nativeTheme, BrowserWindow } from 'electron' import { getPlatform } from '@main/utils/os' import Storage from '@main/storageManager' -export default function useTheme (window: BrowserWindow): void { +export default function useTheme(window: BrowserWindow): void { const themeEvent = (): void => { const isDark = nativeTheme.shouldUseDarkColors - ipcMainSend('renderer.AppearanceManager:systemThemeUpdated', isDark ? 'maa-dark' : 'maa-light') + ipcMainSend( + 'renderer.AppearanceManager:systemThemeUpdated', + isDark ? 'maa-dark' : 'maa-light' + ) } - ipcMainHandle('main.AppearanceManager:themeUpdated', async (event, isDark) => { - const storage = new Storage() - if (getPlatform() === 'windows' && storage.get('theme.acrylic')) { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { setVibrancy } = require('electron-acrylic-window') - setVibrancy(window, { - theme: isDark ? 'dark' : 'light', - effect: 'acrylic' - }) + ipcMainHandle( + 'main.AppearanceManager:themeUpdated', + async (event, isDark) => { + const storage = new Storage() + if (getPlatform() === 'windows' && storage.get('theme.acrylic')) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { setVibrancy } = require('electron-acrylic-window') + setVibrancy(window, { + theme: isDark ? 'dark' : 'light', + effect: 'acrylic', + }) + } + if (getPlatform() === 'macos' && storage.get('theme.acrylic')) { + window.setVibrancy(isDark ? 'dark' : 'light') + } } - if (getPlatform() === 'macos' && storage.get('theme.acrylic')) { - window.setVibrancy(isDark ? 'dark' : 'light') - } - }) + ) - ipcMainHandle('main.WindowManager:loaded', async (event) => { + ipcMainHandle('main.WindowManager:loaded', async event => { themeEvent() ipcMainSend('renderer.WindowManager:loaded') }) diff --git a/packages/preload/Loading.vue b/packages/preload/Loading.vue index 00423cd0..3f6213b2 100644 --- a/packages/preload/Loading.vue +++ b/packages/preload/Loading.vue @@ -1,8 +1,6 @@ {{ - (device?.status === "tasking" ? "当前设备正在进行任务," : "") + - "确定断开连接?" + (device?.status === 'tasking' ? '当前设备正在进行任务,' : '') + + '确定断开连接?' }} -import { NPopover, NImage, NInput, NIcon, NDescriptions, NDescriptionsItem, NDivider, NTooltip, NText } from 'naive-ui' +import { + NPopover, + NImage, + NInput, + NIcon, + NDescriptions, + NDescriptionsItem, + NDivider, + NTooltip, + NText, +} from 'naive-ui' import useDeviceStore from '@/store/devices' import { ref, watch } from 'vue' import logger from '@/hooks/caller/logger' @@ -8,7 +18,7 @@ import IconPencilAlt from '@/assets/icons/pencil-alt.svg?component' const deviceStore = useDeviceStore() const props = defineProps<{ - uuid: string; + uuid: string }>() // const emit = defineEmits(['update:show']) @@ -16,20 +26,28 @@ const show = ref(false) const device = deviceStore.getDevice(props.uuid) as Device const screenshot = ref('') -let timer: NodeJS.Timer|null = null +let timer: NodeJS.Timer | null = null const startGetScreenshot = async () => { logger.info('send get') - window.ipcRenderer.on('renderer.Device:getScreenshot', async (event, data) => { - if (data.uuid === props.uuid) { - const imageData = await window.ipcRenderer.invoke('main.CoreLoader:getScreencap', { uuid: props.uuid }) - screenshot.value = imageData.screenshot + window.ipcRenderer.on( + 'renderer.Device:getScreenshot', + async (event, data) => { + if (data.uuid === props.uuid) { + const imageData = await window.ipcRenderer.invoke( + 'main.CoreLoader:getScreencap', + { uuid: props.uuid } + ) + screenshot.value = imageData.screenshot + } } - }) + ) if (timer) clearInterval(timer) timer = setInterval(async () => { logger.info('send asyncScreencap') - await window.ipcRenderer.invoke('main.CoreLoader:asyncScreencap', { uuid: props.uuid }) + await window.ipcRenderer.invoke('main.CoreLoader:asyncScreencap', { + uuid: props.uuid, + }) }, 3000) } @@ -39,7 +57,7 @@ const stopGetScreenshot = () => { window.ipcRenderer.off('renderer.Device:getScreenshot', () => {}) } -watch(show, (newShowValue) => { +watch(show, newShowValue => { // if (newShowValue) { // startGetScreenshot() // } else { @@ -50,7 +68,6 @@ watch(show, (newShowValue) => { const updateDisplayName = (displayName: string) => { deviceStore.updateDeviceDisplayName(props.uuid, displayName) } - - 模拟器自动启动命令, 非必要请不要修改这里的内容, 留空将会在下一次链接时尝试自动获取 + 模拟器自动启动命令, 非必要请不要修改这里的内容, + 留空将会在下一次链接时尝试自动获取 - + @@ -139,15 +145,15 @@ const updateDisplayName = (displayName: string) => { padding-top: 0 !important; padding-bottom: 0 !important; padding-left: 5px !important; - height:15px !important; + height: 15px !important; } -:deep(.n-input){ +:deep(.n-input) { --n-color: rgba(255, 255, 255, 0) !important; } :deep(.n-input__border) { - border:none !important; + border: none !important; } :deep(.n-input-wrapper) { diff --git a/packages/renderer/src/components/GlobalNotifier.vue b/packages/renderer/src/components/GlobalNotifier.vue index 950520e9..b84a2861 100644 --- a/packages/renderer/src/components/GlobalNotifier.vue +++ b/packages/renderer/src/components/GlobalNotifier.vue @@ -15,13 +15,7 @@ const taskStore = useTaskStore() const connectMessage: Ref = ref() -deviceStore.$onAction(({ - name, - store, - args, - after, - onError -}) => { +deviceStore.$onAction(({ name, store, args, after, onError }) => { if (name === 'updateDeviceStatus') { const [uuid, status] = args const device = store.devices.find(device => device.uuid === uuid) @@ -47,35 +41,20 @@ deviceStore.$onAction(({ } }) -componentStore.$onAction(({ - name, - store, - args, - after, - onError -}) => { +componentStore.$onAction(({ name, store, args, after, onError }) => { if (name === 'updateComponentStatus') { const [componentType, status] = args if (status.installerStatus === 'exception') { message.error( - t( - 'download.installFailed', - { componentType: t(`download["${componentType}"]`) } - ) + t('download.installFailed', { + componentType: t(`download["${componentType}"]`), + }) ) } } }) -taskStore.$onAction(({ - name, - store, - args, - after, - onError -}) => { - -}) +taskStore.$onAction(({ name, store, args, after, onError }) => {})