perf: Support batch screenshot and other performance optimization

This commit is contained in:
viarotel 2024-07-11 17:18:27 +08:00
parent 49ae54166a
commit db9e3e791e
22 changed files with 123 additions and 121 deletions

View File

@ -49,5 +49,6 @@
], ],
"cSpell.words": [ "cSpell.words": [
"bhsn" "bhsn"
] ],
"common-intellisense.ui": []
} }

View File

@ -1,5 +1,24 @@
# Changelog # Changelog
## [1.20.1](https://github.com/viarotel-org/escrcpy/compare/v1.20.0...v1.20.1) (2024-07-04)
### Bug Fixes
* 🐛 Fix batch text spelling errors ([062c689](https://github.com/viarotel-org/escrcpy/commit/062c689755df5bcc5f8e38605c7f101762d7ada0))
## [1.20.0](https://github.com/viarotel-org/escrcpy/compare/v1.19.4...v1.20.0) (2024-07-04)
### Features
* ✨ Add batch installation application function ([37ce245](https://github.com/viarotel-org/escrcpy/commit/37ce2457bce9a1b661c6db7162023f53268833f5))
### Performance Improvements
* 🚀 Add mouse binding options ([7ee4ba4](https://github.com/viarotel-org/escrcpy/commit/7ee4ba4f2b177e6dbfce85036425b51bfa35ecff))
## [1.19.4](https://github.com/viarotel-org/escrcpy/compare/v1.19.3...v1.19.4) (2024-07-02) ## [1.19.4](https://github.com/viarotel-org/escrcpy/compare/v1.19.3...v1.19.4) (2024-07-02)

View File

@ -203,7 +203,7 @@ Windows 及 Linux 端内部集成了 Gnirehtet 用于提供 PC 到安卓设
15. 支持批量连接历史设备功能 ✅ 15. 支持批量连接历史设备功能 ✅
16. 支持使用内置终端执行自定义命令 ✅ 16. 支持使用内置终端执行自定义命令 ✅
17. 支持设备自动执行镜像 ✅ 17. 支持设备自动执行镜像 ✅
18. 添加批量安装应用功能 ✅ 18. 支持常用批量功能 ✅
19. 支持更多批量处理功能 🚧 19. 支持更多批量处理功能 🚧
20. 支持对设备进行分组 🚧 20. 支持对设备进行分组 🚧
21. 添加文件传输助手功能 🚧 21. 添加文件传输助手功能 🚧

View File

@ -201,7 +201,7 @@ Refer to [scrcpy/doc/shortcuts](https://github.com/Genymobile/scrcpy/blob/master
15. Support bulk connecting to historical devices ✅ 15. Support bulk connecting to historical devices ✅
16. Support to use built-in terminals to execute custom commands ✅ 16. Support to use built-in terminals to execute custom commands ✅
17. Supports automatic execution of mirror on devices ✅ 17. Supports automatic execution of mirror on devices ✅
18. Add batch installation application function ✅ 18. Support common batch processing function ✅
19. Support more batch processing functions 🚧 19. Support more batch processing functions 🚧
20. Support the device to group 🚧 20. Support the device to group 🚧
21. Add file transmission assistant function 🚧 21. Add file transmission assistant function 🚧

32
components.d.ts vendored
View File

@ -7,21 +7,11 @@ export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
About: typeof import('./src/components/About/index.vue')['default']
AppInstall: typeof import('./src/components/Device/components/BatchActions/AppInstall/index.vue')['default']
AppSearch: typeof import('./src/components/AppSearch/index.vue')['default']
AudioCodecSelect: typeof import('./src/components/Preference/components/AudioCodecSelect/index.vue')['default']
BatchActions: typeof import('./src/components/Device/components/BatchActions/index.vue')['default']
Camera: typeof import('./src/components/Device/components/MoreDropdown/components/Camera/index.vue')['default']
ControlBar: typeof import('./src/components/Device/components/ControlBar/index.vue')['default']
Device: typeof import('./src/components/Device/index.vue')['default']
DisplaySelect: typeof import('./src/components/Preference/components/DisplaySelect/index.vue')['default']
ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete'] ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElCollapseTransition: typeof import('element-plus/es')['ElCollapseTransition']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
@ -44,28 +34,6 @@ declare module 'vue' {
ElTabs: typeof import('element-plus/es')['ElTabs'] ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
FileManage: typeof import('./src/components/Device/components/ControlBar/FileManage/index.vue')['default']
Gnirehtet: typeof import('./src/components/Device/components/ControlBar/Gnirehtet/index.vue')['default']
KeyboardInjectSelect: typeof import('./src/components/Preference/components/KeyboardInjectSelect/index.vue')['default']
LanguageSelect: typeof import('./src/components/Preference/components/LanguageSelect/index.vue')['default']
LoadingIcon: typeof import('./src/components/Device/components/LoadingIcon/index.vue')['default']
MirrorAction: typeof import('./src/components/Device/components/MirrorAction/index.vue')['default']
MirrorGroup: typeof import('./src/components/Device/components/ControlBar/MirrorGroup/index.vue')['default']
MoreDropdown: typeof import('./src/components/Device/components/MoreDropdown/index.vue')['default']
Otg: typeof import('./src/components/Device/components/MoreDropdown/components/Otg/index.vue')['default']
PairDialog: typeof import('./src/components/Device/components/Wireless/PairDialog/index.vue')['default']
PathInput: typeof import('./src/components/Preference/components/PathInput/index.vue')['default']
Preference: typeof import('./src/components/Preference/index.vue')['default']
Record: typeof import('./src/components/Device/components/MoreDropdown/components/Record/index.vue')['default']
Remark: typeof import('./src/components/Device/components/Remark/index.vue')['default']
Rotation: typeof import('./src/components/Device/components/ControlBar/Rotation/index.vue')['default']
Screenshot: typeof import('./src/components/Device/components/ControlBar/Screenshot/index.vue')['default']
TerminalAction: typeof import('./src/components/Device/components/TerminalAction/index.vue')['default']
TerminalDialog: typeof import('./src/components/Device/components/TerminalAction/components/TerminalDialog/index.vue')['default']
VideoCodecSelect: typeof import('./src/components/Preference/components/VideoCodecSelect/index.vue')['default']
Volume: typeof import('./src/components/Device/components/ControlBar/Volume/index.vue')['default']
Wireless: typeof import('./src/components/Device/components/Wireless/index.vue')['default']
WirelessAction: typeof import('./src/components/Device/components/WirelessAction/index.vue')['default']
} }
export interface ComponentCustomProperties { export interface ComponentCustomProperties {
vLoading: typeof import('element-plus/es')['ElLoadingDirective'] vLoading: typeof import('element-plus/es')['ElLoadingDirective']

View File

@ -27,8 +27,6 @@ log.initialize({ preload: true })
const debug = !!appStore.get('common.debug') const debug = !!appStore.get('common.debug')
log.info('Debug Status:', debug)
if (!debug) { if (!debug) {
log.warn( log.warn(
'Debug Tips:', 'Debug Tips:',

View File

@ -1,7 +1,7 @@
{ {
"name": "escrcpy", "name": "escrcpy",
"type": "module", "type": "module",
"version": "1.19.4", "version": "1.20.1",
"private": true, "private": true,
"description": "Scrcpy Powered by Electron", "description": "Scrcpy Powered by Electron",
"author": "viarotel", "author": "viarotel",

View File

@ -33,7 +33,7 @@ import { ElMessageBox } from 'element-plus'
import Device from './components/Device/index.vue' import Device from './components/Device/index.vue'
import Preference from './components/Preference/index.vue' import Preference from './components/Preference/index.vue'
import About from './components/About/index.vue' import About from './components/About/index.vue'
import AppSearch from './components/AppSearch/index.vue' import AppSearch from './components/Search/index.vue'
import { useThemeStore } from '$/store/theme/index.js' import { useThemeStore } from '$/store/theme/index.js'
import { usePreferenceStore } from '$/store/preference/index.js' import { usePreferenceStore } from '$/store/preference/index.js'

View File

@ -1,17 +1,17 @@
<template> <template>
<div class="" @click="handleClick"> <div class="" @click="handleClick">
<slot /> <slot />
<AppInstallProxy ref="appInstallProxyRef" /> <ApplicationProxy ref="applicationProxyRef" />
</div> </div>
</template> </template>
<script> <script>
import AppInstallProxy from '$/components/Device/components/ControlBar/AppInstall/index.vue' import ApplicationProxy from '$/components/Device/components/ControlBar/Application/index.vue'
import { sleep } from '$/utils' import { sleep } from '$/utils'
export default { export default {
components: { components: {
AppInstallProxy, ApplicationProxy,
}, },
props: { props: {
devices: { devices: {
@ -23,7 +23,7 @@ export default {
async handleClick() { async handleClick() {
for (let index = 0; index < this.devices.length; index++) { for (let index = 0; index < this.devices.length; index++) {
const item = this.devices[index] const item = this.devices[index]
await this.$refs.appInstallProxyRef.handleInstall(item) await this.$refs.applicationProxyRef.invoke(item)
await sleep(2 * 1000) await sleep(2 * 1000)
} }
}, },

View File

@ -0,0 +1,34 @@
<template>
<div class="" @click="handleClick">
<slot />
<ScreenshotProxy ref="screenshotProxyRef" />
</div>
</template>
<script>
import ScreenshotProxy from '$/components/Device/components/ControlBar/Screenshot/index.vue'
import { sleep } from '$/utils'
export default {
components: {
ScreenshotProxy,
},
props: {
devices: {
type: Array,
default: () => [],
},
},
methods: {
async handleClick() {
for (let index = 0; index < this.devices.length; index++) {
const item = this.devices[index]
await this.$refs.screenshotProxyRef.invoke(item)
await sleep(2 * 1000)
}
},
},
}
</script>
<style></style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="flex items-center"> <div class="flex items-center space-x-2">
<component <component
:is="item.component || 'div'" :is="item.component || 'div'"
v-for="(item, index) in actionModel" v-for="(item, index) in actionModel"
@ -31,7 +31,7 @@
<component :is="item.elIcon" /> <component :is="item.elIcon" />
</el-icon> </el-icon>
</template> </template>
{{ $t('common.batch') }}{{ $t(item.label) }} {{ $t('common.batch') }}-{{ $t(item.label) }}
</el-button> </el-button>
</template> </template>
</component> </component>
@ -39,11 +39,13 @@
</template> </template>
<script> <script>
import AppInstall from './AppInstall/index.vue' import Application from './Application/index.vue'
import Screenshot from './Screenshot/index.vue'
export default { export default {
components: { components: {
AppInstall, Application,
Screenshot,
}, },
props: { props: {
devices: { devices: {
@ -54,10 +56,15 @@ export default {
data() { data() {
return { return {
actionModel: [ actionModel: [
{
label: 'device.control.capture',
elIcon: 'Crop',
component: 'Screenshot',
},
{ {
label: 'device.control.install', label: 'device.control.install',
svgIcon: 'install', svgIcon: 'install',
component: 'AppInstall', component: 'Application',
}, },
], ],
} }

View File

@ -5,8 +5,6 @@
</template> </template>
<script> <script>
import LoadingIcon from '$/components/Device/components/LoadingIcon/index.vue'
export default { export default {
props: { props: {
device: { device: {
@ -18,6 +16,9 @@ export default {
return {} return {}
}, },
methods: { methods: {
invoke(...args) {
return this.handleInstall(...args)
},
preferenceData(...args) { preferenceData(...args) {
return this.$store.preference.getData(...args) return this.$store.preference.getData(...args)
}, },
@ -46,13 +47,11 @@ export default {
return false return false
} }
const messageEl = this.$message({ const messageEl = this.$message.loading(
message: this.$t('device.control.install.progress', { this.$t('device.control.install.progress', {
deviceName: this.$store.device.getLabel(device), deviceName: this.$store.device.getLabel(device),
}), }),
icon: LoadingIcon, )
duration: 0,
})
let failCount = 0 let failCount = 0

View File

@ -1,12 +1,10 @@
<template> <template>
<div class="" @click="handleScreenCap(device)"> <div class="" @click="handleCapture(device)">
<slot /> <slot />
</div> </div>
</template> </template>
<script> <script>
import LoadingIcon from '$/components/Device/components/LoadingIcon/index.vue'
export default { export default {
props: { props: {
device: { device: {
@ -18,21 +16,22 @@ export default {
return {} return {}
}, },
methods: { methods: {
invoke(...args) {
return this.handleCapture(...args)
},
preferenceData(...args) { preferenceData(...args) {
return this.$store.preference.getData(...args) return this.$store.preference.getData(...args)
}, },
async handleScreenCap(device) { async handleCapture(device) {
const messageEl = this.$message({ const messageEl = this.$message.loading(
message: this.$t('device.control.capture.progress', { this.$t('device.control.capture.progress', {
deviceName: this.$store.device.getLabel(device), deviceName: this.$store.device.getLabel(device),
}), }),
icon: LoadingIcon, )
duration: 0,
})
const fileName = this.$store.device.getLabel( const fileName = this.$store.device.getLabel(
device, device,
({ time }) => `screenshot-${time}.png`, ({ time }) => `screenshot-${time}.jpg`,
) )
const deviceConfig = this.preferenceData(device.id) const deviceConfig = this.preferenceData(device.id)
@ -40,7 +39,7 @@ export default {
try { try {
await this.$adb.screencap(device.id, { savePath }) await this.$adb.screencap(device.id, { savePath })
this.handleScreencapSuccess(savePath) await this.handleSuccess(savePath)
} }
catch (error) { catch (error) {
if (error.message) { if (error.message) {
@ -50,29 +49,12 @@ export default {
messageEl.close() messageEl.close()
}, },
async handleScreencapSuccess(savePath) { async handleSuccess(savePath) {
try { return this.$message.success(
await this.$confirm( `${this.$t(
this.$t('device.control.capture.success.message'), 'device.control.capture.success.message.title',
this.$t('device.control.capture.success.message.title'), )}: ${savePath}`,
{
confirmButtonText: this.$t('common.confirm'),
cancelButtonText: this.$t('common.cancel'),
closeOnClickModal: false,
type: 'success',
},
) )
await this.$electron.ipcRenderer.invoke(
'show-item-in-folder',
savePath,
)
}
catch (error) {
if (error.message) {
this.$message.warning(error.message)
}
}
}, },
}, },
} }

View File

@ -66,7 +66,7 @@
<script> <script>
import Screenshot from './Screenshot/index.vue' import Screenshot from './Screenshot/index.vue'
import AppInstall from './AppInstall/index.vue' import Application from './Application/index.vue'
import Gnirehtet from './Gnirehtet/index.vue' import Gnirehtet from './Gnirehtet/index.vue'
import MirrorGroup from './MirrorGroup/index.vue' import MirrorGroup from './MirrorGroup/index.vue'
import Rotation from './Rotation/index.vue' import Rotation from './Rotation/index.vue'
@ -76,7 +76,7 @@ import FileManage from './FileManage/index.vue'
export default { export default {
components: { components: {
Screenshot, Screenshot,
AppInstall, Application,
Gnirehtet, Gnirehtet,
MirrorGroup, MirrorGroup,
Rotation, Rotation,
@ -142,7 +142,7 @@ export default {
{ {
label: 'device.control.install', label: 'device.control.install',
svgIcon: 'install', svgIcon: 'install',
component: 'AppInstall', component: 'Application',
}, },
{ {
label: 'device.control.file.name', label: 'device.control.file.name',

View File

@ -51,7 +51,7 @@ export default {
await recording await recording
this.onRecordSuccess(savePath) await this.handleSuccess(savePath)
} }
catch (error) { catch (error) {
console.error('record.args', args) console.error('record.args', args)
@ -79,24 +79,10 @@ export default {
return value return value
}, },
async onRecordSuccess(savePath) { async handleSuccess(savePath) {
try { return this.$message.success(
await this.$confirm( `${this.$t('device.record.success.title')}: ${savePath}`,
this.$t('device.record.success.message'),
this.$t('device.record.success.title'),
{
confirmButtonText: this.$t('common.confirm'),
cancelButtonText: this.$t('common.cancel'),
closeOnClickModal: false,
type: 'success',
},
) )
await this.$electron.ipcRenderer.invoke('show-item-in-folder', savePath)
}
catch (error) {
console.warn(error)
}
}, },
}, },
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="h-full flex flex-col"> <div class="h-full flex flex-col">
<div class="flex items-center flex-none space-x-2 pt-1"> <div class="flex items-center flex-none space-x-2 py-1 overflow-x-auto">
<Wireless ref="wireless" :reload="getDeviceData" /> <Wireless ref="wireless" :reload="getDeviceData" />
<div class="w-px h-7 !ml-4 !mr-2 bg-gray-200"></div> <div class="w-px h-7 !ml-4 !mr-2 bg-gray-200"></div>
@ -33,7 +33,7 @@
<BatchActions <BatchActions
class="overflow-hidden transition-all" class="overflow-hidden transition-all"
:class="isMultipleRow ? 'h-12 opacity-100 mt-4' : 'h-0 opacity-0 mt-0'" :class="isMultipleRow ? 'h-12 opacity-100 mt-3' : 'h-0 opacity-0 mt-0'"
:devices="selectionRows" :devices="selectionRows"
/> />
@ -68,6 +68,7 @@
sortable sortable
show-overflow-tooltip show-overflow-tooltip
align="left" align="left"
min-width="200"
> >
<template #default="{ row }"> <template #default="{ row }">
<div class="flex items-center"> <div class="flex items-center">
@ -96,7 +97,7 @@
<el-table-column <el-table-column
v-slot="{ row, $index }" v-slot="{ row, $index }"
:label="$t('device.control.name')" :label="$t('device.control.name')"
width="450" width="400"
align="left" align="left"
> >
<MirrorAction <MirrorAction

View File

@ -208,7 +208,6 @@ import DisplaySelect from './components/DisplaySelect/index.vue'
import KeyboardInjectSelect from './components/KeyboardInjectSelect/index.vue' import KeyboardInjectSelect from './components/KeyboardInjectSelect/index.vue'
import { usePreferenceStore } from '$/store/index.js' import { usePreferenceStore } from '$/store/index.js'
import LoadingIcon from '$/components/Device/components/LoadingIcon/index.vue'
export default { export default {
components: { components: {
@ -349,11 +348,7 @@ export default {
}, },
async handleExport() { async handleExport() {
const messageEl = this.$message({ const messageEl = this.$message.loading(this.$t('preferences.config.export.message'))
message: this.$t('preferences.config.export.message'),
icon: LoadingIcon,
duration: 0,
})
try { try {
await this.$electron.ipcRenderer.invoke('show-save-dialog', { await this.$electron.ipcRenderer.invoke('show-save-dialog', {

View File

@ -16,6 +16,7 @@ export default () => {
}), }),
useAutoComponents({ useAutoComponents({
resolvers, resolvers,
dirs: 'none',
}), }),
] ]
} }

View File

@ -6,7 +6,7 @@
<script> <script>
export default { export default {
name: 'LoadingIcon', name: 'EleIconLoading',
} }
</script> </script>

View File

@ -8,12 +8,22 @@ import 'element-plus/theme-chalk/el-message-box.css'
import 'element-plus/theme-chalk/dark/css-vars.css' import 'element-plus/theme-chalk/dark/css-vars.css'
import './restyle.css' import './restyle.css'
import EleIconLoading from './components/EleIconLoading/index.vue'
export default { export default {
install(app) { install(app) {
for (const [key, component] of Object.entries(ElementPlusIcons)) { for (const [key, component] of Object.entries(ElementPlusIcons)) {
app.component(key, component) app.component(key, component)
} }
ElMessage.loading = (message, options = {}) =>
ElMessage({
duration: 0,
...options,
message,
icon: EleIconLoading,
})
app.use(ElMessage) app.use(ElMessage)
app.use(ElMessageBox) app.use(ElMessageBox)
app.use(ElLoading) app.use(ElLoading)

View File

@ -7,6 +7,7 @@ html {
@screen sm { @screen sm {
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {