Skip to content

Commit

Permalink
feat: 🚀 增加了对设备交互控制栏的支持
Browse files Browse the repository at this point in the history
  • Loading branch information
viarotel committed Oct 13, 2023
1 parent 6ab0b1a commit fd20736
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 66 deletions.
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
📱 Use Scrcpy with a graphical interface to display and control your Android device, driven by Electron

<div style="display:flex;">
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d75ea2c87c734591b2b5c337d8c8b365~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1359&h=693&s=135919&e=jpg&b=ffffff" alt="viarotel-escrcpy" style="width: 100%;">
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/570065a5683b4cf7af9cfa9743c06ab4~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1360&h=693&s=140693&e=jpg&b=ffffff" alt="viarotel-escrcpy" style="width: 100%;">
</div>

## 特点
Expand Down Expand Up @@ -37,6 +37,8 @@

### WIFI 连接

> 注意:如果首次无线连接失败,你可能需要无线配对请参阅 [常见问题](#常见问题)
>
> 注意:需同时开启无线调试功能,并在无线调试页面中获取你的当前设备的无线地址(通常为你连接WIFI时分配的IP地址)及端口号(默认为 5555)
1. 同 USB 连接中的 1-2 步骤
Expand Down Expand Up @@ -82,10 +84,11 @@
1. 用户界面进行优化,制作合适的 Logo ✅
2. 内置的软件更新功能 ✅
3. 录制和保存音视频 ✅
4. 添加设备交互控制栏 🚧
5. 添加 macOS 及 linux 操作系统的支持 🚧
6. 支持语言国际化功能 🚧
7. 添加对游戏的增强功能 如游戏键位映射 🚧
4. 添加设备快捷交互控制栏 ✅
5. 支持自定义 Adb 及 Scrcpy 依赖,并支持生成精简版本和完整版本以满足不同用户需求
6. 添加 macOS 及 linux 操作系统的支持 🚧
7. 支持语言国际化功能 🚧
8. 添加对游戏的增强功能,如游戏键位映射 🚧

## 常见问题

Expand Down Expand Up @@ -118,6 +121,10 @@

请再点一次,或点击刷新设备,一般不会超过两次,如果还不行,请提供机型和安卓版本信息到 [Issues](https://github.com/viarotel-org/escrcpy/issues)

### 设备交互控制栏为什么不设计为自动跟踪吸附的悬浮菜单?

采用悬浮菜单方案不可避免地会增加对 Scrcpy 的耦合性,并增加与 Scrcpy 同步更新的难度。许多类似的 ScrcpyGUI 软件在使用此方案后不得不投入大量精力,最终因难以维护而放弃开发。因此,综合考虑,我们决定采用现有的方案,并期待 Scrcpy 未来能够增加原生交互控制栏的支持。

## 获得帮助

> 因为是开源项目 全靠爱发电 所以支持有限 更新节奏不固定
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dayjs": "^1.11.10",
"electron-updater": "^6.1.1",
"element-plus": "^2.3.14",
"fs-extra": "^11.1.1",
"lodash-es": "^4.17.21",
"pinia": "^2.1.6",
"ufo": "^1.3.1"
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 37 additions & 1 deletion src/preload/plugins/adbkit/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import util from 'node:util'
import child_process from 'node:child_process'
import path from 'node:path'
import fs from 'node:fs'
import dayjs from 'dayjs'
import { Adb } from '@devicefarmer/adbkit'
import adbPath from '@resources/core/adb.exe?asset&asarUnpack'

Expand Down Expand Up @@ -36,6 +39,38 @@ const getDeviceIP = async (id) => {

const tcpip = async (id, port = 5555) => await client.getDevice(id).tcpip(port)

const screencap = async (deviceId, options = {}) => {
let fileStream = null
try {
const device = client.getDevice(deviceId)
fileStream = await device.screencap()
console.log('fileStream', fileStream)
}
catch (error) {
console.warn(error?.message || error)
return false
}

if (!fileStream) {
return false
}

const fileName = `Screencap-${dayjs().format('YYYY-MM-DD-HH-mm-ss')}.png`
const savePath = options.savePath || path.resolve('../', fileName)

return new Promise((resolve, reject) => {
fileStream
.pipe(fs.createWriteStream(savePath))
.on('finish', () => {
resolve(true)
})
.on('error', (error) => {
console.warn(error?.message || error)
reject(false)
})
})
}

const watch = async (callback) => {
const tracker = await client.trackDevices()
tracker.on('add', async (ret) => {
Expand Down Expand Up @@ -71,8 +106,9 @@ export default () => {
kill,
connect,
disconnect,
watch,
getDeviceIP,
tcpip,
screencap,
watch,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template>
<el-icon class="is-loading">
<Loading />
</el-icon>
</template>

<script>
export default {
name: 'LoadingIcon',
}
</script>

<style></style>
130 changes: 130 additions & 0 deletions src/renderer/src/components/Devices/ControlBar/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<template>
<div class="bg-primary-100 -my-[8px]">
<el-button
v-for="(item, index) in controlModel"
:key="index"
type="primary"
plain
class="!border-none !mx-0 bg-transparent !rounded-0"
:icon="item.icon"
:disabled="device.$unauthorized"
:title="item.tips"
@click="handleClick(item)"
>
{{ item.label }}
</el-button>
</div>
</template>

<script>
import dayjs from 'dayjs'
import LoadingIcon from './LoadingIcon/index.vue'
export default {
props: {
device: {
type: Object,
default: () => ({}),
},
},
data() {
return {
controlModel: [
{
label: '切换键',
icon: 'Switch',
command: 'input keyevent KEYCODE_APP_SWITCH',
},
{
label: '主屏幕键',
icon: 'HomeFilled',
command: 'input keyevent KEYCODE_HOME',
},
{
label: '返回键',
icon: 'Back',
command: 'input keyevent KEYCODE_BACK',
},
{
label: '菜单键',
icon: 'Menu',
command: 'input keyevent KEYCODE_MENU',
tips: '不要和切换键搞错啦',
},
{
label: '电源键',
icon: 'SwitchButton',
command: 'input keyevent KEYCODE_POWER',
tips: '可以用来开启或关闭屏幕',
},
{
label: '截屏快照',
icon: 'Crop',
handle: this.handleScreenCap,
tips: '不要和切换键搞错啦',
},
],
}
},
computed: {
scrcpyConfig() {
return this.$store.scrcpy.config
},
},
methods: {
handleClick(row) {
if (row.command) {
this.$adb.deviceShell(this.device.id, row.command)
}
else if (row.handle) {
row.handle(this.device)
}
else {
return false
}
},
async handleScreenCap(device) {
const deviceName = device.name || device.id
const messageEl = this.$message({
message: ` 正在截取 ${deviceName} 的屏幕快照...`,
icon: LoadingIcon,
duration: 0,
})
const fileName = `${deviceName}-screencap-${dayjs().format('YYYY-MM-DD-HH-mm-ss')}.png`
const savePath = this.$path.resolve(this.scrcpyConfig['--record'], fileName)
try {
await this.$adb.screencap(device.id, { savePath })
this.handleScreencapSuccess(savePath)
}
catch (error) {
if (error.message) {
this.$message.warning(error.message)
}
}
messageEl.close()
},
async handleScreencapSuccess(savePath) {
try {
await this.$confirm('是否前往截屏位置进行查看?', '录制成功', {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
type: 'success',
})
this.$electron.ipcRenderer.invoke('show-item-in-folder', savePath)
}
catch (error) {
if (error.message) {
this.$message.warning(error.message)
}
}
},
},
}
</script>

<style></style>
Loading

0 comments on commit fd20736

Please sign in to comment.