From 18dcd24e656801fbb811ccc46496e4037d28a137 Mon Sep 17 00:00:00 2001 From: viarotel Date: Wed, 23 Oct 2024 16:14:25 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E2=9A=A1=EF=B8=8F=20Optimize=20scrcpy?= =?UTF-8?q?=20parameter=20conversion=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/exposes/scrcpy/index.js | 34 ++++++---- src/store/preference/index.js | 62 ++++++------------ src/utils/command/index.js | 109 +++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 54 deletions(-) create mode 100644 src/utils/command/index.js diff --git a/electron/exposes/scrcpy/index.js b/electron/exposes/scrcpy/index.js index 9a10cd3..5d11ffa 100644 --- a/electron/exposes/scrcpy/index.js +++ b/electron/exposes/scrcpy/index.js @@ -3,12 +3,13 @@ import util from 'node:util' import { adbPath, scrcpyPath } from '$electron/configs/index.js' import appStore from '$electron/helpers/store.js' import { replaceIP, sleep } from '$renderer/utils/index.js' +import commandHelper from '$renderer/utils/command/index.js' let adbkit const exec = util.promisify(_exec) -const shell = async (command, { stdout, stderr } = {}) => { +async function shell(command, { stdout, stderr } = {}) { const spawnPath = appStore.get('common.scrcpyPath') || scrcpyPath const ADB = appStore.get('common.adbPath') || adbPath const args = command.split(' ') @@ -58,7 +59,7 @@ const shell = async (command, { stdout, stderr } = {}) => { }) } -const execShell = async (command) => { +async function execShell(command) { const spawnPath = appStore.get('common.scrcpyPath') || scrcpyPath const ADB = appStore.get('common.adbPath') || adbPath @@ -71,7 +72,7 @@ const execShell = async (command) => { return res } -const getEncoders = async (serial) => { +async function getEncoders(serial) { const res = await execShell(`--serial="${serial}" --list-encoders`) const stdout = res.stdout @@ -97,29 +98,26 @@ const getEncoders = async (serial) => { return value } -const mirror = async ( +async function mirror( serial, { title, args = '', exec = false, ...options } = {}, -) => { - const mirrorShell = exec ? execShell : shell +) { + const currentShell = exec ? execShell : shell - return mirrorShell( + return currentShell( `--serial="${serial}" --window-title="${title}" ${args}`, options, ) } -const record = async ( - serial, - { title, args = '', savePath, ...options } = {}, -) => { +async function record(serial, { title, args = '', savePath, ...options } = {}) { return shell( `--serial="${serial}" --window-title="${title}" --record="${savePath}" ${args}`, options, ) } -const mirrorGroup = async (serial, { openNum = 1, ...options } = {}) => { +async function mirrorGroup(serial, { openNum = 1, ...options } = {}) { const overlayDisplay = appStore.get(`scrcpy.${replaceIP(serial)}.--display-overlay`) || appStore.get('scrcpy.global.--display-overlay') @@ -170,6 +168,17 @@ const mirrorGroup = async (serial, { openNum = 1, ...options } = {}) => { return Promise.allSettled(results) } +async function control(serial, { command, exec = true, ...options } = {}) { + const currentShell = exec ? execShell : shell + + const stringCommand = commandHelper.stringify(command) + + return currentShell( + `--serial="${serial}" --no-video --no-audio ${stringCommand}`, + options, + ) +} + export default (options = {}) => { adbkit = options.adbkit @@ -180,5 +189,6 @@ export default (options = {}) => { mirror, record, mirrorGroup, + control, } } diff --git a/src/store/preference/index.js b/src/store/preference/index.js index 98487b6..5c96946 100644 --- a/src/store/preference/index.js +++ b/src/store/preference/index.js @@ -13,6 +13,8 @@ import { } from './helpers/index.js' import model from './model/index.js' +import command from '$/utils/command/index.js' + const { adbPath, scrcpyPath, gnirehtetPath } = window.electron?.configs || {} export const usePreferenceStore = defineStore({ @@ -130,7 +132,7 @@ export const usePreferenceStore = defineStore({ return value }, - getScrcpyArgs( + scrcpyParameter( scope = this.deviceScope, { isRecord = false, isCamera = false, isOtg = false, excludes = [] } = {}, ) { @@ -140,55 +142,31 @@ export const usePreferenceStore = defineStore({ return '' } - const valueList = Object.entries(data).reduce((arr, [key, value]) => { - if (!value && typeof value !== 'number') { - return arr + const params = Object.entries(data).reduce((obj, [key, value]) => { + const shouldExclude + = (!value && typeof value !== 'number') + || this.excludeKeys.includes(key) + || (!isRecord && this.recordKeys.includes(key)) + || (!isCamera && this.cameraKeys.includes(key)) + || (!isOtg && this.otgKeys.includes(key)) + || excludes.includes(key) + || excludes.includes(`${key}=${value}`) + + if (shouldExclude) { + return obj } - if (this.excludeKeys.includes(key)) { - return arr - } + obj[key] = value - if (!isRecord) { - if (this.recordKeys.includes(key)) { - return arr - } - } + return obj + }, {}) - if (!isCamera) { - if (this.cameraKeys.includes(key)) { - return arr - } - } - - if (!isOtg) { - if (this.otgKeys.includes(key)) { - return arr - } - } - - if (excludes.includes(key) || excludes.includes(`${key}=${value}`)) { - return arr - } - - if (typeof value === 'boolean') { - arr.push(key) - } - else { - arr.push(`${key}="${value}"`) - } - - return arr - }, []) + let value = command.stringify(params) if (data.scrcpyAppend) { - valueList.push(...data.scrcpyAppend.split(' ')) + value += ` ${data.scrcpyAppend}` } - const value = valueList.join(' ') - - // console.log('value', value) - return value }, getModel(path) { diff --git a/src/utils/command/index.js b/src/utils/command/index.js new file mode 100644 index 0000000..f277af9 --- /dev/null +++ b/src/utils/command/index.js @@ -0,0 +1,109 @@ +/** + * Convert object parameters to command line arguments string + * @param {Object} options - The options object containing parameters + * @returns {string} The formatted command line arguments string + */ +function stringify(options) { + if (!options || typeof options !== 'object' || Array.isArray(options)) { + console.warn('Options must be a plain object') + return '' + } + + const args = [] + + // Helper function to format parameter names + const formatParamName = (name) => { + // 验证参数名称的合法性 + if (typeof name !== 'string' || !name.length) { + throw new TypeError('Parameter name must be a non-empty string') + } + + if (name.startsWith('-')) { + return name + } + + return name.length === 1 + ? `-${name}` + : `--${name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)}` + } + + // Helper function to format values based on their types + const formatValue = (value) => { + if (value === null || value === undefined) { + throw new TypeError('Value cannot be null or undefined') + } + + if (typeof value === 'string') { + // 处理空字符串 + if (!value.length) { + return '""' + } + // 转义引号并在需要时添加引号 + const needsQuotes = /[\s"']/.test(value) + return needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value + } + + if (typeof value === 'number') { + if (!Number.isFinite(value)) { + throw new TypeError('Number values must be finite') + } + return value.toString() + } + + if (typeof value === 'boolean') { + return '' // 布尔值不需要返回值 + } + + if (Array.isArray(value)) { + return formatValue(value.join(',')) + } + + throw new TypeError(`Unsupported value type: ${typeof value}`) + } + + // Process each option + for (const [key, value] of Object.entries(options)) { + // Skip null or undefined values + if ([null, undefined, false].includes(value)) { + continue + } + + const paramName = formatParamName(key) + + // Handle boolean flags + if (typeof value === 'boolean') { + if (value) { + args.push(paramName) + } + continue + } + + // Handle array values + if (Array.isArray(value)) { + if (value.length === 0) { + continue // 跳过空数组 + } + value.forEach((item) => { + if (item !== null && item !== undefined) { + const formattedValue = formatValue(item) + if (formattedValue) { + args.push(`${paramName} ${formattedValue}`) + } + } + }) + continue + } + + // Handle regular key-value pairs + const formattedValue = formatValue(value) + if (formattedValue) { + args.push(`${paramName}=${formattedValue}`) + } + } + + return args.join(' ') +} + +export { stringify } + +export default { stringify }