fix: 🚀 修复对设备进行独立配置时的一些问题

This commit is contained in:
viarotel 2023-10-20 14:23:48 +08:00
parent 385f402179
commit 6ccd6d09a4
19 changed files with 194 additions and 92 deletions

View File

@ -9,6 +9,7 @@
## 特点
- 🏃 同步:得益于 Web 技术,将更快速的与 Scrcpy 保持同步
- 💡 定制化:支持对多个设备偏好进行独立配置,并且能够添加备注以及导入导出所有配置的功能
- 😎 轻巧度:本机支持,仅显示设备屏幕
- ⚡️ 性能30~120 帧每秒,取决于设备
- 🌟 质量1920×1080 或更高
@ -113,12 +114,13 @@
2. 内置的软件更新功能 ✅
3. 录制和保存音视频 ✅
4. 添加设备快捷交互控制栏 ✅
5. 支持自定义设备名称,以及用户配置的导出及导入 ✅
6. 支持自定义 Adb 及 Scrcpy 依赖 ✅
7. 支持精简版本(不包含 Adb 及 Scrcpy 依赖)和完整版本以满足不同用户需求 🚧
8. 添加 macOS 及 linux 操作系统的支持 🚧
9. 支持语言国际化功能 🚧
10. 添加对游戏的增强功能,如游戏键位映射 🚧
5. 支持自定义 Adb 及 Scrcpy 依赖 ✅
6. 支持自定义设备名称,以及偏好设置的导出及导入 ✅
7. 定制化,支持对单个设备进行独立配置 ✅
8. 支持精简版本(不包含 Adb 及 Scrcpy 依赖)和完整版本以满足不同用户需求 🚧
9. 添加 macOS 及 linux 操作系统的支持 🚧
10. 支持语言国际化功能 🚧
11. 添加对游戏的增强功能,如游戏键位映射 🚧
## 常见问题

View File

@ -2,6 +2,8 @@ import { resolve } from 'node:path'
import { buildResolve, extraResolve } from '@electron/helpers/index.js'
export const desktopPath = process.env.DESKTOP_PATH
export const devPublishPath = resolve('dev-publish.yml')
export const logoPath = buildResolve('logo.png')

View File

@ -17,12 +17,26 @@ window.addEventListener('beforeunload', () => {
}
})
appStore.onDidChange('scrcpy.adbPath', async (value) => {
console.log('onDidChange.scrcpy.adbPath.value', value)
appStore.onDidChange('scrcpy.global.adbPath', async (value, oldValue) => {
console.log('onDidChange.scrcpy.global.adbPath', value)
if (!value) {
return false
}
if (value === oldValue) {
return false
}
if (value === client?.options?.bin) {
return false
}
if (client) {
await client.kill().catch(e => console.warn(e))
client = null
}
client = Adb.createClient({ bin: value })
})
@ -108,7 +122,12 @@ const watch = async (callback) => {
}
export default () => {
client = Adb.createClient({ bin: appStore.get('scrcpy.adbPath') || adbPath })
const binPath = appStore.get('scrcpy.global.adbPath') || adbPath
client = Adb.createClient({
bin: binPath,
})
console.log('client', client)
return {

View File

@ -3,15 +3,13 @@ import appStore from '@electron/helpers/store.js'
import { adbPath, scrcpyPath } from '@electron/configs/index.js'
const shell = async (command, { stdout, stderr } = {}) => {
const spawnPath = appStore.get('scrcpy.global.scrcpyPath') || scrcpyPath
const args = command.split(' ')
const scrcpyProcess = spawn(
appStore.get('scrcpy.scrcpyPath') || scrcpyPath,
args,
{
env: { ...process.env, ADB: adbPath },
shell: true,
},
)
const scrcpyProcess = spawn(spawnPath, args, {
env: { ...process.env, ADB: adbPath },
shell: true,
})
scrcpyProcess.stdout.on('data', (data) => {
const stringData = data.toString()

View File

@ -1,6 +1,8 @@
/** 在主进程中获取项目打包状态并将该值存储到环境变量中,从而在预加载脚本中及渲染进程中使用 */
/** 在主进程中获取关键信息存储到环境变量中,从而在预加载脚本中及渲染进程中使用 */
/** 注意: app.isPackaged 可能被被某些方法改变所以请将该文件放到 main.js 必须位于非依赖项的顶部 */
import { app } from 'electron'
process.env.IS_PACKAGED = JSON.stringify(app.isPackaged)
process.env.DESKTOP_PATH = app.getPath('desktop')

View File

@ -3,8 +3,8 @@ import { createProxy } from './index.js'
const appStore = new Store()
appStore.onDidAnyChange(() => {
console.log('appStore.onDidAnyChange', appStore.store)
appStore.onDidAnyChange((value) => {
console.log('appStore.onDidAnyChange.value', value)
})
export default {

View File

@ -2,8 +2,8 @@ import path from 'node:path'
import { BrowserWindow, app, shell } from 'electron'
import { electronApp, optimizer } from '@electron-toolkit/utils'
// packaged.js 必须位于非依赖项的顶部
import './helpers/packaged.js'
// process.js 必须位于非依赖项的顶部
import './helpers/process.js'
import './helpers/store.js'
import { icnsLogoPath, icoLogoPath, logoPath } from './configs/index.js'

BIN
public/screenshot/about.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

View File

@ -83,12 +83,11 @@ export default {
],
}
},
computed: {
scrcpyConfig() {
return this.$store.scrcpy.config
},
},
computed: {},
methods: {
scrcpyConfig(...args) {
return this.$store.scrcpy.getConfig(...args)
},
async handleInstall(device) {
let files = null
@ -109,7 +108,7 @@ export default {
}
const messageEl = this.$message({
message: ` 正在为 ${device.name} 安装应用中...`,
message: ` 正在为 ${device.$name} 安装应用中...`,
icon: LoadingIcon,
duration: 0,
})
@ -132,11 +131,11 @@ export default {
if (successCount) {
if (totalCount > 1) {
this.$message.success(
`已成功将应用安装到 ${device.name} 中,共 ${totalCount}个,成功 ${successCount}个,失败 ${failCount}`,
`已成功将应用安装到 ${device.$name} 中,共 ${totalCount}个,成功 ${successCount}个,失败 ${failCount}`,
)
}
else {
this.$message.success(`已成功将应用安装到 ${device.name}`)
this.$message.success(`已成功将应用安装到 ${device.$name}`)
}
return
}
@ -156,15 +155,19 @@ export default {
},
async handleScreenCap(device) {
const messageEl = this.$message({
message: ` 正在截取 ${device.name} 的屏幕快照...`,
message: ` 正在截取 ${device.$name} 的屏幕快照...`,
icon: LoadingIcon,
duration: 0,
})
const fileName = `${device.name}-screencap-${dayjs().format(
const fileName = `${device.$remark ? `${device.$remark}-` : ''}${
device.$name
}-${this.$replaceIP(device.id)}-screencap-${dayjs().format(
'YYYY-MM-DD-HH-mm-ss',
)}.png`
const savePath = this.$path.resolve(this.scrcpyConfig.savePath, fileName)
const deviceConfig = this.scrcpyConfig(device.id)
const savePath = this.$path.resolve(deviceConfig.savePath, fileName)
try {
await this.$adb.screencap(device.id, { savePath })

View File

@ -65,12 +65,7 @@
align="left"
width="200"
/>
<el-table-column
prop="name"
label="设备名称"
show-overflow-tooltip
align="left"
>
<el-table-column label="设备名称" show-overflow-tooltip align="left">
<template #default="{ row }">
<div class="flex items-center">
<el-tooltip
@ -83,7 +78,7 @@
</el-icon>
</el-tooltip>
{{ row.name }}
{{ row.$name }}
<el-tag v-if="row.$wifi" effect="light" class="ml-2">
WIFI
@ -188,11 +183,7 @@ export default {
},
}
},
computed: {
scrcpyConfig() {
return this.$store.scrcpy.config
},
},
computed: {},
async created() {
this.getDeviceData()
@ -217,6 +208,9 @@ export default {
}
},
methods: {
scrcpyConfig(...args) {
return this.$store.scrcpy.getConfig(...args)
},
scrcpyArgs(...args) {
return this.$store.scrcpy.getStringConfig(...args)
},
@ -225,13 +219,19 @@ export default {
this.$refs.elTable.toggleRowExpansion(...params)
},
getRecordPath(row) {
const basePath = this.scrcpyConfig.savePath
const recordFormat = this.scrcpyConfig['--record-format']
const fileName = `${row.name || row.id}-recording-${dayjs().format(
const rowConfig = this.scrcpyConfig(row.id)
const basePath = rowConfig.savePath
const recordFormat = rowConfig['--record-format']
const fileName = `${row.$remark ? `${row.$remark}-` : ''}${
row.$name
}-${this.$replaceIP(row.id)}-recording-${dayjs().format(
'YYYY-MM-DD-HH-mm-ss',
)}.${recordFormat}`
const joinValue = this.$path.join(basePath, fileName)
const value = this.$path.normalize(joinValue)
return value
},
async handleRecord(row) {
@ -243,9 +243,9 @@ export default {
try {
const command = `--serial=${row.id} --window-title=${
row.$remark ? `${row.$remark}-` : ''
}${row.name}-${
}${row.$name}-${
row.id
}-🎥录制中... --record=${savePath} ${this.scrcpyArgs()}`
}-🎥录制中... --record=${savePath} ${this.scrcpyArgs(row.id)}`
console.log('handleRecord.command', command)
@ -279,7 +279,7 @@ export default {
await this.$scrcpy.shell(
`--serial=${row.id} --window-title=${
row.$remark ? `${row.$remark}-` : ''
}${row.name}-${row.id} ${this.scrcpyArgs()}`,
}${row.$name}-${row.id} ${this.scrcpyArgs(row.id)}`,
{ stdout: this.onStdout },
)
}
@ -373,22 +373,15 @@ export default {
this.loading = true
await sleep(500)
try {
const data = await this.$adb.getDevices()
const data = await this.$store.device.getList()
this.deviceList
= data?.map(item => ({
...item,
id: item.id,
name: item.model ? item.model.split(':')[1] : '未授权设备',
$loading: false,
$recordLoading: false,
$stopLoading: false,
$unauthorized: item.type === 'unauthorized',
$wifi: isIPWithPort(item.id),
$remark: this.$store.device.getRemark(item.id),
})) || []
this.$store.device.setList(this.deviceList)
console.log('getDeviceData.data', this.deviceList)
}
catch (error) {

View File

@ -5,7 +5,6 @@
<el-select
v-model="scopeValue"
value-key=""
title="该选项用于设置偏好设置的作用域范围"
placeholder="偏好设置的作用域范围"
filterable
no-data-text="暂无数据"
@ -13,9 +12,24 @@
@change="onScopeChange"
>
<template #prefix>
<el-icon class="text-primary-300 hover:text-primary-500">
<QuestionFilled />
</el-icon>
<el-tooltip class="" effect="dark" placement="bottom-start">
<el-icon class="text-primary-300 hover:text-primary-500">
<QuestionFilled />
</el-icon>
<template #content>
<div class="space-y-1">
<div class="pb-1">
对全局或者单个设备的设置不同的偏好配置
</div>
<div class="">
全局将对所有设备生效
</div>
<div class="">
单个设备继承于全局配置并对单个设备进行独立设置仅对此设备生效
</div>
</div>
</template>
</el-tooltip>
</template>
<el-option
v-for="item in scopeList"
@ -36,6 +50,9 @@
<el-button type="" plain @click="handleEdit">
编辑配置
</el-button>
<el-button type="" plain @click="handleResetAll">
重置配置
</el-button>
</div>
</div>
<div class="grid gap-6 pr-2">
@ -213,15 +230,15 @@ export default {
scopeList() {
const value = this.$store.device.list.map(item => ({
...item,
label: `${item.id}${item.name}${
label: `${item.id}${item.$name}${
item.$remark ? `${item.$remark}` : ''
}`,
value: this.$store.device.removeDots(item.id),
value: item.id,
}))
value.unshift({
label: '全局范围',
label: 'Global全局',
value: 'global',
})
@ -243,8 +260,13 @@ export default {
})
},
methods: {
handleResetAll() {
this.$store.scrcpy.reset(this.scopeValue)
this.scrcpyForm = this.$store.scrcpy.config
},
onScopeChange(value) {
this.$store.scrcpy.setScope(value)
const replaceIPValue = this.$store.device.replaceIP(value)
this.$store.scrcpy.setScope(replaceIPValue)
this.scrcpyForm = this.$store.scrcpy.config
},
async handleImport() {

View File

@ -6,6 +6,8 @@ import store from './store/index.js'
import plugins from './plugins/index.js'
import icons from './icons/index.js'
import { replaceIP } from '@/utils/index.js'
import 'virtual:uno.css'
import './styles/index.js'
@ -22,6 +24,8 @@ app.config.globalProperties.$scrcpy = window.scrcpy
app.config.globalProperties.$path = window.nodePath
app.config.globalProperties.$appStore = window.appStore
app.config.globalProperties.$replaceIP = replaceIP
app.mount('#app').$nextTick(() => {
// Remove Preload scripts loading
postMessage({ payload: 'removeLoading' }, '*')

View File

@ -1,8 +1,8 @@
import { defineStore } from 'pinia'
const $appStore = window.appStore
import { isIPWithPort, replaceIP } from '@/utils/index.js'
const removeDots = value => value.replaceAll('.', '_')
const $appStore = window.appStore
export const useDeviceStore = defineStore({
id: 'app-device',
@ -14,7 +14,7 @@ export const useDeviceStore = defineStore({
},
getters: {},
actions: {
removeDots,
replaceIP,
init() {
this.config = {
...($appStore.get('device') || {}),
@ -25,16 +25,33 @@ export const useDeviceStore = defineStore({
setList(data) {
this.list = data
},
async getList() {
const res = await window.adbkit.getDevices()
const data
= res?.map(item => ({
...item,
id: item.id,
$name: item.model ? item.model.split(':')[1] : '未授权设备',
$unauthorized: item.type === 'unauthorized',
$wifi: isIPWithPort(item.id),
$remark: this.getRemark(item.id),
})) || []
this.list = data
return data
},
setConfig(value, key = 'device') {
$appStore.set(key, value)
this.init()
},
setRemark(deviceId, value) {
$appStore.set(`device.${removeDots(deviceId)}.remark`, value)
$appStore.set(`device.${replaceIP(deviceId)}.remark`, value)
this.init()
},
getRemark(deviceId) {
const value = $appStore.get(`device.${removeDots(deviceId)}.remark`)
const value = $appStore.get(`device.${replaceIP(deviceId)}.remark`)
return value
},
},

View File

@ -1,9 +1,29 @@
import { defineStore } from 'pinia'
import { pickBy } from 'lodash-es'
import { cloneDeep, mergeWith } from 'lodash-es'
import * as scrcpyModel from './model/index.js'
import { replaceIP } from '@/utils/index.js'
const $appStore = window.appStore
function mergeConfig(object, sources, { debug = false } = {}) {
const customizer = (objValue, srcValue) => {
if (debug) {
console.log('objValue', typeof objValue)
console.log('srcValue', typeof srcValue)
}
if (typeof srcValue === 'boolean') {
return srcValue
}
return srcValue || objValue
}
const value = mergeWith(cloneDeep(object), cloneDeep(sources), customizer)
return value
}
/**
* 获取 Scrcpy 默认配置
*/
@ -40,28 +60,48 @@ export const useScrcpyStore = defineStore({
}
},
actions: {
replaceIP,
getDefaultConfig,
init(scope = this.scope) {
this.config = {
...this.defaultConfig,
...($appStore.get(`scrcpy.${scope}`) || {}),
let tempConfig = mergeConfig(
this.defaultConfig,
$appStore.get('scrcpy.global') || {},
)
if (scope !== 'global') {
const scopeConfig = $appStore.get(`scrcpy.${replaceIP(scope)}`) || {}
tempConfig = mergeConfig(tempConfig, scopeConfig, { debug: true })
}
this.config = tempConfig
return this.config
},
reset(scope) {
if (scope) {
this.scope = scope
$appStore.set(`scrcpy.${replaceIP(scope)}`, {})
}
else {
this.scope = 'global'
$appStore.set('scrcpy', {})
}
this.init()
},
setScope(value) {
this.scope = value
$appStore.set('scrcpy.scope', value)
this.scope = replaceIP(value)
$appStore.set('scrcpy.scope', this.scope)
this.init()
},
getStringConfig(scope = this.scope) {
const scopeConfig = $appStore.get(`scrcpy.${scope}`)
const config = this.getConfig(scope)
if (!scopeConfig) {
if (!config) {
return ''
}
const value = Object.entries(scopeConfig)
const value = Object.entries(config)
.reduce((arr, [key, value]) => {
if (!value) {
return arr
@ -77,6 +117,7 @@ export const useScrcpyStore = defineStore({
else {
arr.push(`${key}=${value}`)
}
return arr
}, [])
.join(' ')
@ -86,15 +127,12 @@ export const useScrcpyStore = defineStore({
return value
},
setConfig(data, scope = this.scope) {
const pickConfig = pickBy(data, value => !!value)
// console.log('pickConfig', pickConfig)
$appStore.set(`scrcpy.${scope}`, pickConfig)
$appStore.set(`scrcpy.${replaceIP(scope)}`, { ...data })
this.init(scope)
},
getConfig(scope = this.scope) {
const value = $appStore.get(`scrcpy.${scope}`)
const value = this.init(scope)
return value
},
getModel(key, params) {

View File

@ -1,14 +1,12 @@
export default () => {
const $path = window.nodePath
const { adbPath, scrcpyPath } = window?.electron?.configs || {}
const { adbPath, scrcpyPath, desktopPath } = window?.electron?.configs || {}
return [
{
label: '文件存储路径',
type: 'input.path',
field: 'savePath',
value: $path.resolve('../'),
value: desktopPath,
placeholder: '默认值为执行应用的同级目录',
tips: '截图和录制的音视频都存在这里',
properties: ['openDirectory'],
@ -18,7 +16,7 @@ export default () => {
field: 'adbPath',
type: 'input.path',
value: adbPath,
tips: '用于连接设备的 adb.exe 的地址,注意:改变此选项时需要重启服务',
tips: '用于连接设备的 adb.exe 的地址,注意:该选项不受针对于单个设备配置的影响',
placeholder: '请选择 Adb.exe',
properties: ['openFile'],
filters: [{ name: '请选择 Adb.exe', extensions: ['exe'] }],
@ -28,7 +26,7 @@ export default () => {
field: 'scrcpyPath',
type: 'input.path',
value: scrcpyPath,
tips: '用于控制设备的 Scrcpy.exe 的地址',
tips: '用于控制设备的 Scrcpy.exe 的地址,注意:该选项不受针对于单个设备配置的影响',
placeholder: '请选择 Scrcpy.exe',
properties: ['openFile'],
filters: [{ name: '请选择 Scrcpy.exe', extensions: ['exe'] }],

View File

@ -14,3 +14,7 @@ export function isIPWithPort(ip) {
return regex.test(ip)
}
export function replaceIP(value, to = '_') {
return value.replaceAll('.', to).replaceAll(':', to)
}