mirror of
https://github.com/viarotel-org/escrcpy.git
synced 2024-11-24 07:30:15 +01:00
feat: 🎉 Add mirror group function
This commit is contained in:
parent
df1d9da65e
commit
0c9d36fddb
@ -174,8 +174,8 @@ Windows 及 Linux 端内部集成了 Gnirehtet, 用于提供 PC 到安卓设
|
||||
10. 对深色模式的支持 ✅
|
||||
11. 添加 Gnirehtet 反向供网功能 ✅
|
||||
12. 添加新的相机镜像相关功能 ✅
|
||||
13. 添加独立的剪切板同步功能 🚧
|
||||
14. 更好的多屏协同 🚧
|
||||
13. 更好的多屏协同 ✅
|
||||
14. 添加独立的剪切板同步功能 🚧
|
||||
15. 添加 Scrcpy 快捷键查询页面 🚧
|
||||
16. 添加对游戏的增强功能,如游戏键位映射 🚧
|
||||
|
||||
|
@ -172,8 +172,8 @@ Refer to [scrcpy/doc/shortcuts](https://github.com/Genymobile/scrcpy/blob/master
|
||||
10. Support for dark mode ✅
|
||||
11. Add Gnirehtet reverse network function ✅
|
||||
12. Add new camera mirror related features ✅
|
||||
13. Add an clipboard synchronization function 🚧
|
||||
14. Better multi -screen collaboration 🚧
|
||||
13. Better multi -screen collaboration ✅
|
||||
14. Add an clipboard synchronization function 🚧
|
||||
15. Add Scrcpy shortcut key query page 🚧
|
||||
16. Add game enhancement features such as game keyboard mapping 🚧
|
||||
|
||||
|
@ -125,6 +125,13 @@ const display = async (deviceId) => {
|
||||
return value
|
||||
}
|
||||
|
||||
const clearOverlayDisplayDevices = async (deviceId) => {
|
||||
return deviceShell(
|
||||
deviceId,
|
||||
'settings put global overlay_display_devices none',
|
||||
)
|
||||
}
|
||||
|
||||
const watch = async (callback) => {
|
||||
const tracker = await client.trackDevices()
|
||||
tracker.on('add', async (ret) => {
|
||||
@ -174,6 +181,7 @@ export default () => {
|
||||
isInstalled,
|
||||
version,
|
||||
display,
|
||||
clearOverlayDisplayDevices,
|
||||
watch,
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export default {
|
||||
|
||||
expose('adbkit', adbkitExecute)
|
||||
|
||||
expose('scrcpy', scrcpy())
|
||||
expose('scrcpy', scrcpy({ adbkit: adbkitExecute }))
|
||||
|
||||
expose('gnirehtet', gnirehtet({ adbkit: adbkitExecute }))
|
||||
},
|
||||
|
@ -2,6 +2,9 @@ import util from 'node:util'
|
||||
import { exec as _exec, spawn } from 'node:child_process'
|
||||
import appStore from '@electron/helpers/store.js'
|
||||
import { adbPath, scrcpyPath } from '@electron/configs/index.js'
|
||||
import { replaceIP, sleep } from '@renderer/utils/index.js'
|
||||
|
||||
let adbkit
|
||||
|
||||
const exec = util.promisify(_exec)
|
||||
|
||||
@ -102,8 +105,13 @@ const getEncoders = async (serial) => {
|
||||
return value
|
||||
}
|
||||
|
||||
const mirror = async (serial, { title, args = '', ...options } = {}) => {
|
||||
return shell(
|
||||
const mirror = async (
|
||||
serial,
|
||||
{ title, args = '', exec = false, ...options } = {},
|
||||
) => {
|
||||
const mirrorShell = exec ? execShell : shell
|
||||
|
||||
return mirrorShell(
|
||||
`--serial="${serial}" --window-title="${title}" ${args}`,
|
||||
options,
|
||||
)
|
||||
@ -119,10 +127,74 @@ const record = async (
|
||||
)
|
||||
}
|
||||
|
||||
export default () => ({
|
||||
const mirrorGroup = async (serial, { open = 1, ...options } = {}) => {
|
||||
const overlayDisplay
|
||||
= appStore.get(`scrcpy.${replaceIP(serial)}.--display-overlay`)
|
||||
|| appStore.get('scrcpy.global.--display-overlay')
|
||||
|| '1080x1920/320,secure'
|
||||
|
||||
const command = `settings put global overlay_display_devices "${[
|
||||
...Array.from({ length: open }).keys(),
|
||||
]
|
||||
.map(() => overlayDisplay)
|
||||
.join(';')}"`
|
||||
|
||||
await adbkit.deviceShell(serial, command)
|
||||
|
||||
await sleep()
|
||||
|
||||
const displayList = await adbkit.display(serial, command)
|
||||
|
||||
const filterList = displayList.filter(item => item !== '0')
|
||||
console.log('filterList', filterList)
|
||||
|
||||
const results = []
|
||||
|
||||
for (let index = 0; index < filterList.length; index++) {
|
||||
const displayId = filterList[index]
|
||||
|
||||
let args = options.args || ''
|
||||
|
||||
if (args.includes('--display-id')) {
|
||||
args = args.replace(/(--display-id=)"[^"]*"/, `$1"${displayId}"`)
|
||||
}
|
||||
else {
|
||||
args += ` --display-id="${displayId}"`
|
||||
}
|
||||
|
||||
const title = options?.title?.({ displayId, index }) || options?.title
|
||||
|
||||
const promise = mirror(serial, {
|
||||
...options,
|
||||
title,
|
||||
args,
|
||||
exec: true,
|
||||
}).catch((error) => {
|
||||
console.warn(
|
||||
'error',
|
||||
error?.message
|
||||
|| error?.cause?.message
|
||||
|| `display-id-${displayId}: Open failed`,
|
||||
)
|
||||
})
|
||||
|
||||
results.push(promise)
|
||||
|
||||
await sleep(1500)
|
||||
}
|
||||
|
||||
return Promise.allSettled(results)
|
||||
}
|
||||
|
||||
export default (options = {}) => {
|
||||
adbkit = options.adbkit
|
||||
|
||||
return {
|
||||
shell,
|
||||
execShell,
|
||||
getEncoders,
|
||||
mirror,
|
||||
record,
|
||||
})
|
||||
mirrorGroup,
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@root/*": ["*"],
|
||||
"@electron/*": ["electron/*"]
|
||||
"@electron/*": ["electron/*"],
|
||||
"@renderer/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "dist-electron", "dist-release"],
|
||||
|
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<el-dropdown :disabled="loading" @command="handleMirror">
|
||||
<div class="">
|
||||
<slot :loading="loading" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item v-for="item of 4" :key="item" :command="item">
|
||||
{{ $t("device.control.mirror-group.open", { num: item }) }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
device: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
scrcpyArgs(...args) {
|
||||
return this.$store.preference.getScrcpyArgs(...args)
|
||||
},
|
||||
preferenceData(...args) {
|
||||
return this.$store.preference.getData(...args)
|
||||
},
|
||||
async handleMirror(open) {
|
||||
console.log('handleMirror.open', open)
|
||||
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
await this.$scrcpy.mirrorGroup(this.device.id, {
|
||||
open,
|
||||
title: ({ displayId }) =>
|
||||
`${this.device.$remark ? `${this.device.$remark}-` : ''}${
|
||||
this.device.$name
|
||||
}-${this.device.id}-display-${displayId}`,
|
||||
args: this.scrcpyArgs(this.device.id),
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.warn(error.message)
|
||||
if (error?.message || error?.cause?.message) {
|
||||
this.$message.warning(error?.message || error?.cause?.message)
|
||||
}
|
||||
}
|
||||
|
||||
this.$adb.clearOverlayDisplayDevices(this.device.id)
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
@ -18,22 +18,29 @@
|
||||
: {}),
|
||||
}"
|
||||
>
|
||||
<template #default="{ loading = false } = {}">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
class="!border-none !mx-0 bg-transparent !rounded-0"
|
||||
:disabled="device.$unauthorized"
|
||||
:title="item.tips ? $t(item.tips) : ''"
|
||||
:loading="loading"
|
||||
@wheel.prevent="onWheel"
|
||||
>
|
||||
<template #icon>
|
||||
<svg-icon v-if="item.svgIcon" :name="item.svgIcon"></svg-icon>
|
||||
<el-icon v-else-if="item.elIcon" class="">
|
||||
<svg-icon
|
||||
v-if="item.svgIcon"
|
||||
:name="item.svgIcon"
|
||||
:class="item.iconClass"
|
||||
></svg-icon>
|
||||
<el-icon v-else-if="item.elIcon" :class="item.iconClass">
|
||||
<component :is="item.elIcon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
{{ $t(item.label) }}
|
||||
</el-button>
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
@ -42,12 +49,14 @@
|
||||
import Screenshot from './Screenshot/index.vue'
|
||||
import AppInstall from './AppInstall/index.vue'
|
||||
import Gnirehtet from './Gnirehtet/index.vue'
|
||||
import MirrorGroup from './MirrorGroup/index.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Screenshot,
|
||||
AppInstall,
|
||||
Gnirehtet,
|
||||
MirrorGroup,
|
||||
},
|
||||
props: {
|
||||
device: {
|
||||
@ -108,6 +117,13 @@ export default {
|
||||
component: 'Gnirehtet',
|
||||
tips: 'device.control.gnirehtet.tips',
|
||||
},
|
||||
{
|
||||
label: 'device.control.mirror-group.name',
|
||||
svgIcon: 'multi-screen',
|
||||
iconClass: '',
|
||||
component: 'MirrorGroup',
|
||||
tips: 'device.control.mirror-group.tips',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
|
1
src/icons/svg/multi-screen.svg
Normal file
1
src/icons/svg/multi-screen.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1699319726512" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20556" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M787.692308 669.538462h-118.153846v118.153846a118.153846 118.153846 0 0 1-118.153847 118.153846H196.923077a118.153846 118.153846 0 0 1-118.153846-118.153846v-354.461539a118.153846 118.153846 0 0 1 118.153846-118.153846h118.153846V196.923077a118.153846 118.153846 0 0 1 118.153846-118.153846h354.461539a118.153846 118.153846 0 0 1 118.153846 118.153846v354.461538a118.153846 118.153846 0 0 1-118.153846 118.153847zM196.923077 374.153846A59.076923 59.076923 0 0 0 137.846154 433.230769v354.461539A59.076923 59.076923 0 0 0 196.923077 846.769231h354.461538a59.076923 59.076923 0 0 0 59.076923-59.076923v-354.461539A59.076923 59.076923 0 0 0 551.384615 374.153846H196.923077zM846.769231 196.923077A59.076923 59.076923 0 0 0 787.692308 137.846154h-354.461539A59.076923 59.076923 0 0 0 374.153846 196.923077v118.153846H551.384615a118.153846 118.153846 0 0 1 118.153847 118.153846v177.230769h118.153846a59.076923 59.076923 0 0 0 59.076923-59.076923V196.923077z" p-id="20557"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -97,6 +97,9 @@
|
||||
"device.control.gnirehtet.tips": "Gnirehtet provides reverse tethering for Android; Note: Initial connection requires authorization on the device.",
|
||||
"device.control.gnirehtet.progress": "Starting Gnirehtet reverse tethering service...",
|
||||
"device.control.gnirehtet.success": "Gnirehtet reverse tethering feature started successfully",
|
||||
"device.control.mirror-group.name": "Mirror Group",
|
||||
"device.control.mirror-group.tips": "When enabled, can mirror multiple simulated secondary displays and achieve multi-screen collaboration by operating each mirrored window. Note this requires ROM support and desktop mode enabled.",
|
||||
"device.control.mirror-group.open": "Open {num} windows",
|
||||
|
||||
"preferences.name": "Preferences",
|
||||
"preferences.reset": "Reset to Default",
|
||||
@ -187,6 +190,9 @@
|
||||
"preferences.device.control-in-stop-charging.name": "Stop Charging",
|
||||
"preferences.device.control-in-stop-charging.placeholder": "Stop charging when controlling",
|
||||
"preferences.device.control-in-stop-charging.tips": "May not work on some models",
|
||||
"preferences.device.display-overlay.name": "Simulated Display",
|
||||
"preferences.device.display-overlay.placeholder": "Size and resolution of simulated secondary display, default 1080x1920/320,secure",
|
||||
"preferences.device.display-overlay.tips": "Mirroring group relies on this option",
|
||||
|
||||
"preferences.window.name": "Window",
|
||||
"preferences.window.borderless.name": "Borderless",
|
||||
|
@ -95,6 +95,9 @@
|
||||
"device.control.gnirehtet.tips": "使用 Gnirehtet 为 Android 提供反向网络共享;注意:首次连接需要在设备上进行授权",
|
||||
"device.control.gnirehtet.progress": "正在启动 Gnirehtet 反向供网服务中...",
|
||||
"device.control.gnirehtet.success": "Gnirehtet 反向网络共享功能启动成功",
|
||||
"device.control.mirror-group.name": "多屏协同",
|
||||
"device.control.mirror-group.tips": "开启后,可以同时镜像多个模拟辅助显示设备,并通过操作各个镜像窗口实现多屏协同功能。请注意,此功能需要手机 ROM 支持,并且必须开启强制使用桌面模式选项。",
|
||||
"device.control.mirror-group.open": "开启 {num} 个窗口",
|
||||
|
||||
"preferences.name": "偏好设置",
|
||||
"preferences.reset": "恢复默认值",
|
||||
@ -185,6 +188,9 @@
|
||||
"preferences.device.control-in-stop-charging.name": "控制时停止充电",
|
||||
"preferences.device.control-in-stop-charging.placeholder": "开启后控制设备时将停止充电",
|
||||
"preferences.device.control-in-stop-charging.tips": "某些机型上似乎不起作用",
|
||||
"preferences.device.display-overlay.name": "模拟辅助显示器",
|
||||
"preferences.device.display-overlay.placeholder": "用于调整模拟辅助显示器的大小和分辨率,默认值为 1080x1920/320,secure",
|
||||
"preferences.device.display-overlay.tips": "多屏协同(镜像组)依赖于此选项",
|
||||
|
||||
"preferences.window.name": "窗口控制",
|
||||
"preferences.window.borderless.name": "无边框模式",
|
||||
|
@ -39,6 +39,7 @@ export const usePreferenceStore = defineStore({
|
||||
data: { ...getDefaultData() },
|
||||
deviceScope,
|
||||
excludeKeys: [
|
||||
'--display-overlay',
|
||||
'--camera',
|
||||
'--video-code',
|
||||
'--audio-code',
|
||||
@ -169,7 +170,7 @@ export const usePreferenceStore = defineStore({
|
||||
arr.push(key)
|
||||
}
|
||||
else {
|
||||
arr.push(`${key}=${value}`)
|
||||
arr.push(`${key}="${value}"`)
|
||||
}
|
||||
|
||||
return arr
|
||||
|
@ -40,5 +40,13 @@ export default {
|
||||
placeholder: 'preferences.device.control-in-stop-charging.placeholder',
|
||||
tips: 'preferences.device.control-in-stop-charging.tips',
|
||||
},
|
||||
overlayDisplay: {
|
||||
label: 'preferences.device.display-overlay.name',
|
||||
field: '--display-overlay',
|
||||
type: 'Input',
|
||||
value: '',
|
||||
placeholder: 'preferences.device.display-overlay.placeholder',
|
||||
tips: 'preferences.device.display-overlay.tips',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ const merge = (config, { command = '' } = {}) =>
|
||||
alias: {
|
||||
'@root': resolve(),
|
||||
'@electron': resolve('electron'),
|
||||
'@renderer': resolve('src'),
|
||||
},
|
||||
},
|
||||
plugins: [...(command === 'serve' ? [notBundle()] : [])],
|
||||
|
Loading…
Reference in New Issue
Block a user