Skip to content

Commit

Permalink
feat: 🎉 Add mirror group function
Browse files Browse the repository at this point in the history
  • Loading branch information
viarotel committed Nov 7, 2023
1 parent df1d9da commit 0c9d36f
Show file tree
Hide file tree
Showing 14 changed files with 218 additions and 32 deletions.
4 changes: 2 additions & 2 deletions README-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ Windows 及 Linux 端内部集成了 Gnirehtet, 用于提供 PC 到安卓设
10. 对深色模式的支持 ✅
11. 添加 Gnirehtet 反向供网功能 ✅
12. 添加新的相机镜像相关功能 ✅
13. 添加独立的剪切板同步功能 🚧
14. 更好的多屏协同 🚧
13. 更好的多屏协同 ✅
14. 添加独立的剪切板同步功能 🚧
15. 添加 Scrcpy 快捷键查询页面 🚧
16. 添加对游戏的增强功能,如游戏键位映射 🚧

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 🚧

Expand Down
8 changes: 8 additions & 0 deletions electron/exposes/adbkit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -174,6 +181,7 @@ export default () => {
isInstalled,
version,
display,
clearOverlayDisplayDevices,
watch,
}
}
2 changes: 1 addition & 1 deletion electron/exposes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default {

expose('adbkit', adbkitExecute)

expose('scrcpy', scrcpy())
expose('scrcpy', scrcpy({ adbkit: adbkitExecute }))

expose('gnirehtet', gnirehtet({ adbkit: adbkitExecute }))
},
Expand Down
90 changes: 81 additions & 9 deletions electron/exposes/scrcpy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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,
)
Expand All @@ -119,10 +127,74 @@ const record = async (
)
}

export default () => ({
shell,
execShell,
getEncoders,
mirror,
record,
})
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,
}
}
3 changes: 2 additions & 1 deletion jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"paths": {
"@/*": ["src/*"],
"@root/*": ["*"],
"@electron/*": ["electron/*"]
"@electron/*": ["electron/*"],
"@renderer/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist", "dist-electron", "dist-release"],
Expand Down
66 changes: 66 additions & 0 deletions src/components/Device/components/ControlBar/MirrorGroup/index.vue
Original file line number Diff line number Diff line change
@@ -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>
48 changes: 32 additions & 16 deletions src/components/Device/components/ControlBar/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,29 @@
: {}),
}"
>
<el-button
type="primary"
plain
class="!border-none !mx-0 bg-transparent !rounded-0"
:disabled="device.$unauthorized"
:title="item.tips ? $t(item.tips) : ''"
@wheel.prevent="onWheel"
>
<template #icon>
<svg-icon v-if="item.svgIcon" :name="item.svgIcon"></svg-icon>
<el-icon v-else-if="item.elIcon" class="">
<component :is="item.elIcon" />
</el-icon>
</template>
{{ $t(item.label) }}
</el-button>
<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"
: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>
Expand All @@ -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: {
Expand Down Expand Up @@ -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',
},
],
}
},
Expand Down
1 change: 1 addition & 0 deletions src/icons/svg/multi-screen.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/locales/languages/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions src/locales/languages/zh_CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "恢复默认值",
Expand Down Expand Up @@ -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": "无边框模式",
Expand Down
3 changes: 2 additions & 1 deletion src/store/preference/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const usePreferenceStore = defineStore({
data: { ...getDefaultData() },
deviceScope,
excludeKeys: [
'--display-overlay',
'--camera',
'--video-code',
'--audio-code',
Expand Down Expand Up @@ -169,7 +170,7 @@ export const usePreferenceStore = defineStore({
arr.push(key)
}
else {
arr.push(`${key}=${value}`)
arr.push(`${key}="${value}"`)
}

return arr
Expand Down
8 changes: 8 additions & 0 deletions src/store/preference/model/device/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
}
1 change: 1 addition & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const merge = (config, { command = '' } = {}) =>
alias: {
'@root': resolve(),
'@electron': resolve('electron'),
'@renderer': resolve('src'),
},
},
plugins: [...(command === 'serve' ? [notBundle()] : [])],
Expand Down

0 comments on commit 0c9d36f

Please sign in to comment.