diff --git a/README-CN.md b/README-CN.md index 9b03ed5..9fe4dd3 100644 --- a/README-CN.md +++ b/README-CN.md @@ -174,8 +174,8 @@ Windows 及 Linux 端内部集成了 Gnirehtet, 用于提供 PC 到安卓设 10. 对深色模式的支持 ✅ 11. 添加 Gnirehtet 反向供网功能 ✅ 12. 添加新的相机镜像相关功能 ✅ -13. 添加独立的剪切板同步功能 🚧 -14. 更好的多屏协同 🚧 +13. 更好的多屏协同 ✅ +14. 添加独立的剪切板同步功能 🚧 15. 添加 Scrcpy 快捷键查询页面 🚧 16. 添加对游戏的增强功能,如游戏键位映射 🚧 diff --git a/README.md b/README.md index d75e196..fde878b 100644 --- a/README.md +++ b/README.md @@ -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 🚧 diff --git a/electron/exposes/adbkit/index.js b/electron/exposes/adbkit/index.js index faf8b17..d0d86a7 100644 --- a/electron/exposes/adbkit/index.js +++ b/electron/exposes/adbkit/index.js @@ -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, } } diff --git a/electron/exposes/index.js b/electron/exposes/index.js index 0ebc81d..c203d94 100644 --- a/electron/exposes/index.js +++ b/electron/exposes/index.js @@ -28,7 +28,7 @@ export default { expose('adbkit', adbkitExecute) - expose('scrcpy', scrcpy()) + expose('scrcpy', scrcpy({ adbkit: adbkitExecute })) expose('gnirehtet', gnirehtet({ adbkit: adbkitExecute })) }, diff --git a/electron/exposes/scrcpy/index.js b/electron/exposes/scrcpy/index.js index fb7296b..e124baf 100644 --- a/electron/exposes/scrcpy/index.js +++ b/electron/exposes/scrcpy/index.js @@ -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 () => ({ - 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, + } +} diff --git a/jsconfig.json b/jsconfig.json index bc7e623..6e35d74 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -4,7 +4,8 @@ "paths": { "@/*": ["src/*"], "@root/*": ["*"], - "@electron/*": ["electron/*"] + "@electron/*": ["electron/*"], + "@renderer/*": ["src/*"] } }, "exclude": ["node_modules", "dist", "dist-electron", "dist-release"], diff --git a/src/components/Device/components/ControlBar/MirrorGroup/index.vue b/src/components/Device/components/ControlBar/MirrorGroup/index.vue new file mode 100644 index 0000000..cf6be0a --- /dev/null +++ b/src/components/Device/components/ControlBar/MirrorGroup/index.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/src/components/Device/components/ControlBar/index.vue b/src/components/Device/components/ControlBar/index.vue index fe9bea4..7d91096 100644 --- a/src/components/Device/components/ControlBar/index.vue +++ b/src/components/Device/components/ControlBar/index.vue @@ -18,22 +18,29 @@ : {}), }" > - - - {{ $t(item.label) }} - + @@ -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', + }, ], } }, diff --git a/src/icons/svg/multi-screen.svg b/src/icons/svg/multi-screen.svg new file mode 100644 index 0000000..555fe26 --- /dev/null +++ b/src/icons/svg/multi-screen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/locales/languages/en_US.json b/src/locales/languages/en_US.json index 2225845..c7ba07d 100644 --- a/src/locales/languages/en_US.json +++ b/src/locales/languages/en_US.json @@ -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", diff --git a/src/locales/languages/zh_CN.json b/src/locales/languages/zh_CN.json index 0c847ef..74eb4f3 100644 --- a/src/locales/languages/zh_CN.json +++ b/src/locales/languages/zh_CN.json @@ -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": "无边框模式", diff --git a/src/store/preference/index.js b/src/store/preference/index.js index a954c7d..ea12f90 100644 --- a/src/store/preference/index.js +++ b/src/store/preference/index.js @@ -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 diff --git a/src/store/preference/model/device/index.js b/src/store/preference/model/device/index.js index 9d939f9..013cae6 100644 --- a/src/store/preference/model/device/index.js +++ b/src/store/preference/model/device/index.js @@ -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', + }, }, } diff --git a/vite.config.js b/vite.config.js index 8d6a560..d77dedb 100644 --- a/vite.config.js +++ b/vite.config.js @@ -17,6 +17,7 @@ const merge = (config, { command = '' } = {}) => alias: { '@root': resolve(), '@electron': resolve('electron'), + '@renderer': resolve('src'), }, }, plugins: [...(command === 'serve' ? [notBundle()] : [])],