Merge branch 'next'

This commit is contained in:
viarotel 2023-10-16 16:44:25 +08:00
commit 817c147515
84 changed files with 1247 additions and 1422 deletions

View File

@ -1,9 +0,0 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -1,12 +1,10 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = { module.exports = {
extends: ['@electron-toolkit', '@viarotel-org'], extends: ['@viarotel-org'],
rules: { rules: {
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'eqeqeq': 'off', 'eqeqeq': 'off',
'prefer-promise-reject-errors': 'off', 'prefer-promise-reject-errors': 'off',
'antfu/top-level-function': 'off', 'antfu/top-level-function': 'off',
'import/default': 'off',
}, },
} }

View File

@ -48,16 +48,17 @@ jobs:
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:
draft: true draft: true
prerelease: true
files: | files: |
dist/*.exe dist-release/*.exe
dist/*.zip dist-release/*.zip
dist/*.dmg dist-release/*.dmg
dist/*.AppImage dist-release/*.AppImage
dist/*.snap dist-release/*.snap
dist/*.deb dist-release/*.deb
dist/*.rpm dist-release/*.rpm
dist/*.tar.gz dist-release/*.tar.gz
dist/*.yml dist-release/*.yml
dist/*.blockmap dist-release/*.blockmap
env: env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

26
.gitignore vendored
View File

@ -1,4 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules node_modules
dist dist
out dist-ssr
*.log* dist-electron
dist-release
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

2
.npmrc
View File

@ -1,4 +1,4 @@
registry=https://registry.npmmirror.com/ registry=https://registry.npmmirror.com/
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/
shamefully-hoist=true shamefully-hoist=true

View File

@ -1,6 +0,0 @@
out
dist
pnpm-lock.yaml
LICENSE.md
jsconfig.json
jsconfig.*.json

View File

@ -1,4 +0,0 @@
singleQuote: true
semi: false
printWidth: 100
trailingComma: none

View File

@ -1,3 +1,3 @@
{ {
"recommendations": ["dbaeumer.vscode-eslint"] "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
} }

39
.vscode/launch.json vendored
View File

@ -1,39 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
},
"runtimeArgs": ["--sourcemap"],
"env": {
"REMOTE_DEBUGGING_PORT": "9222"
}
},
{
"name": "Debug Renderer Process",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}/src/renderer",
"timeout": 60000,
"presentation": {
"hidden": true
}
}
],
"compounds": [
{
"name": "Debug All",
"configurations": ["Debug Main Process", "Debug Renderer Process"],
"presentation": {
"order": 1
}
}
]
}

View File

@ -2,8 +2,6 @@
📱 使用图形界面的 Scrcpy 显示和控制您的 Android 设备,由 Electron 驱动 📱 使用图形界面的 Scrcpy 显示和控制您的 Android 设备,由 Electron 驱动
📱 Use Scrcpy with a graphical interface to display and control your Android device, driven by Electron
<div style="display:flex;"> <div style="display:flex;">
<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%;"> <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> </div>
@ -15,7 +13,7 @@
- ⚡️ 性能30~120 帧每秒,取决于设备 - ⚡️ 性能30~120 帧每秒,取决于设备
- 🌟 质量1920×1080 或更高 - 🌟 质量1920×1080 或更高
- 🕒 低延迟35~70 毫秒 - 🕒 低延迟35~70 毫秒
- 🚀 快速启动显示第一张图片仅需约1秒钟 - 🚀 快速启动:显示第一张图片仅需约 1 秒钟
- 🙅‍♂️ 非侵入性:不会在安卓设备上留下任何安装文件 - 🙅‍♂️ 非侵入性:不会在安卓设备上留下任何安装文件
- 🤩 用户收益:无需账户、无广告、无需互联网连接 - 🤩 用户收益:无需账户、无广告、无需互联网连接
- 🗽 自由:免费且开源软件 - 🗽 自由:免费且开源软件
@ -39,7 +37,7 @@
> 注意:如果首次无线连接失败,你可能需要无线配对请参阅 [常见问题](#常见问题) > 注意:如果首次无线连接失败,你可能需要无线配对请参阅 [常见问题](#常见问题)
> >
> 注意需同时开启无线调试功能并在无线调试页面中获取你的当前设备的无线地址通常为你连接WIFI时分配的IP地址及端口号默认为 5555 > 注意:需同时开启无线调试功能,并在无线调试页面中获取你的当前设备的无线地址(通常为你连接 WIFI 时分配的 IP 地址)及端口号(默认为 5555
1. 同 USB 连接中的 1-2 步骤 1. 同 USB 连接中的 1-2 步骤
2. 将获取到的设备 IP 地址及端口号填写到 Escrcpy 中,然后点击连接设备 2. 将获取到的设备 IP 地址及端口号填写到 Escrcpy 中,然后点击连接设备
@ -54,29 +52,43 @@
> 持续完善中 目前支持 Scrcpy 中以下常用配置 > 持续完善中 目前支持 Scrcpy 中以下常用配置
### 显示配置 ### 视频控制
- 分辨率 - 分辨率
- 比特率 - 比特率
- 刷新率 - 刷新率
- 屏幕旋转
- 视频解码器 - 视频解码器
- 视频编码器 - 视频编码器
- 屏幕旋转
- 屏幕裁剪
- 多显示器
- 视频缓冲
- 音频缓冲
- 接收器(v4l2)缓冲
- 禁用视频
### 设备控制 ### 设备控制
- 保持设备清醒 - 展示触摸点
- 连接设备后自动关闭屏幕 - 保持清醒
- 控制时关闭屏幕
### 音频控制 - 控制结束关闭屏幕
- 控制时停止充电
- 镜像时禁用音频
### 窗口控制 ### 窗口控制
- 无边框模式 - 无边框模式
- 全屏幕模式 - 全屏幕模式
### 音视频录制
- 文件保存路径
- 录制视频格式
### 音频控制
- 禁用音频
## 下一步做什么? ## 下一步做什么?
> 优先级从高到低 > 优先级从高到低
@ -85,10 +97,11 @@
2. 内置的软件更新功能 ✅ 2. 内置的软件更新功能 ✅
3. 录制和保存音视频 ✅ 3. 录制和保存音视频 ✅
4. 添加设备快捷交互控制栏 ✅ 4. 添加设备快捷交互控制栏 ✅
5. 支持自定义 Adb 及 Scrcpy 依赖,并支持生成精简版本和完整版本以满足不同用户需求 5. 支持自定义 Adb 及 Scrcpy 依赖,并支持生成精简版本和完整版本以满足不同用户需求 🚧
6. 添加 macOS 及 linux 操作系统的支持 🚧 6. 支持自定义设备名称,以及用户配置的导出及导入 🚧
7. 支持语言国际化功能 🚧 7. 添加 macOS 及 linux 操作系统的支持 🚧
8. 添加对游戏的增强功能,如游戏键位映射 🚧 8. 支持语言国际化功能 🚧
9. 添加对游戏的增强功能,如游戏键位映射 🚧
## 常见问题 ## 常见问题
@ -102,7 +115,7 @@
该问题是已知的, Scrcpy 似乎并未直接对中文输入进行测试和支持 需要在手机端安装第三方输入法 以下输入法经测试可以很好支持 该问题是已知的, Scrcpy 似乎并未直接对中文输入进行测试和支持 需要在手机端安装第三方输入法 以下输入法经测试可以很好支持
- 搜狗输入法 - 搜狗输入法
- QQ输入法 - QQ 输入法
- 谷歌拼音输入法 - 谷歌拼音输入法
- Gboard - Gboard
@ -115,7 +128,7 @@
### 无线连接提示: 目标计算机积极拒绝访问 ### 无线连接提示: 目标计算机积极拒绝访问
第一次无线连接可能需要配对 或 插入USB 以保证与电脑建立连接即授权成功后方可使用 第一次无线连接可能需要配对 或 插入 USB 以保证与电脑建立连接即授权成功后方可使用
### 通过数据线连接后点击无线模式没有反应 ### 通过数据线连接后点击无线模式没有反应
@ -148,6 +161,8 @@
> 如果该项目帮到你的话,可以请我吃包辣条,可以使我更有动力完善该项目 > 如果该项目帮到你的话,可以请我吃包辣条,可以使我更有动力完善该项目
> 注意:非 BUG 或计划外的需求,有偿处理;至于金额,根据问题难易程度,你觉得帮助了多少,看着给吧(维护这些项目已经耗费了大量精力,还要免费花时间解答问题就说不过去了吧...所以白嫖的一律不通过。)
<div style="display:flex;"> <div style="display:flex;">
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79dcbc40246743e2b6870419e88e0392~tplv-k3u1fbpfcp-watermark.image?" alt="viarotel-wepay" style="width: 36%;"> <img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79dcbc40246743e2b6870419e88e0392~tplv-k3u1fbpfcp-watermark.image?" alt="viarotel-wepay" style="width: 36%;">
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1e5e69b83dd746deade95afd4a6864ec~tplv-k3u1fbpfcp-watermark.image?" alt="viarotel-alipay" style="width: 36%;"> <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1e5e69b83dd746deade95afd4a6864ec~tplv-k3u1fbpfcp-watermark.image?" alt="viarotel-alipay" style="width: 36%;">

63
electron-builder.json Normal file
View File

@ -0,0 +1,63 @@
{
"$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
"appId": "org.viarotel.escrcpy",
"asar": true,
"productName": "Escrcpy",
"directories": {
"output": "dist-release/${version}",
"buildResources": "electron/resources/build"
},
"files": ["dist", "dist-electron"],
"extraResources": ["electron/resources/extra"],
"publish": {
"provider": "github",
"owner": "viarotel-org",
"repo": "escrcpy",
"updaterCacheDirName": "escrcpy-updater"
},
"mac": {
"icon": "logo.icns",
"target": ["dmg"],
"artifactName": "${productName}-${version}-mac-installer.${ext}",
"entitlementsInherit": "electron/resources/build/entitlements.mac.plist",
"extendInfo": {
"NSCameraUsageDescription": "Application requests access to the device's camera.",
"NSMicrophoneUsageDescription": "Application requests access to the device's microphone.",
"NSDocumentsFolderUsageDescription": "Application requests access to the user's Documents folder.",
"NSDownloadsFolderUsageDescription": "Application requests access to the user's Downloads folder."
},
"notarize": false
},
"win": {
"icon": "logo.ico",
"target": [
{
"target": "nsis",
"arch": ["x64"]
},
{
"target": "zip"
},
{
"target": "portable"
}
]
},
"nsis": {
"artifactName": "${productName}-${version}-win-setup.${ext}",
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"portable": {
"artifactName": "${productName}-${version}-win-portable.${ext}",
"requestExecutionLevel": "user"
},
"linux": {
"icon": "logo.png",
"target": ["AppImage"],
"artifactName": "${productName}-${version}-linux.${ext}"
},
"npmRebuild": false
}

View File

@ -1,51 +0,0 @@
appId: com.electron.app
productName: escrcpy
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
asarUnpack:
- resources/**
win:
executableName: escrcpy
target:
- nsis
- zip
- portable
nsis:
artifactName: ${productName}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
portable:
artifactName: '${productName}-${version}-portable.${ext}'
requestExecutionLevel: user
mac:
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
notarize: false
dmg:
artifactName: ${name}-${version}.${ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: electronjs.org
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
provider: github
owner: viarotel-org
repo: escrcpy
updaterCacheDirName: escrcpy-updater

View File

@ -1,37 +0,0 @@
import { resolve } from 'node:path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
import useEslint from 'vite-plugin-eslint'
import useUnoCSS from 'unocss/vite'
import postcssConfig from '@viarotel-org/postcss-config'
export default defineConfig({
main: {
resolve: {
alias: {
'@root': resolve('./'),
},
},
plugins: [externalizeDepsPlugin({ exclude: [] })],
},
preload: {
resolve: {
alias: {
'@resources': resolve('resources'),
},
},
plugins: [externalizeDepsPlugin({ exclude: [] })],
},
renderer: {
resolve: {
alias: {
'@root': resolve('./'),
'@renderer': resolve('src/renderer/src'),
},
},
plugins: [useEslint(), vue(), useUnoCSS()],
css: {
postcss: postcssConfig(),
},
},
})

View File

@ -1,23 +1,21 @@
import path from 'node:path'
import { app, ipcMain } from 'electron' import { app, ipcMain } from 'electron'
import { is } from '@electron-toolkit/utils' import { is } from '@electron-toolkit/utils'
import { autoUpdater } from 'electron-updater' import { autoUpdater } from 'electron-updater'
import devPublishPath from '@root/dev-publish.yml?path'
export default (mainWindow) => { export default (mainWindow) => {
// dev-start, 这里是为了在本地做应用升级测试使用,正式环境请务必删除 // dev-start, 这里是为了在本地做应用升级测试使用,正式环境请务必删除
if (is.dev && process.env.ELECTRON_RENDERER_URL) { // if (is.dev && process.env.ELECTRON_RENDERER_URL) {
const updateConfigPath = path.join(process.cwd(), './dev-app-update.yml') if (is.dev && process.env.VITE_DEV_SERVER_URL) {
// console.log('updateConfigPath', updateConfigPath) const updateConfigPath = devPublishPath
autoUpdater.updateConfigPath = updateConfigPath autoUpdater.updateConfigPath = updateConfigPath
Object.defineProperty(app, 'isPackaged', {
get() {
return true
},
})
} }
Object.defineProperty(app, 'isPackaged', {
get() {
return true
},
})
// dev-end
// 触发检查更新(此方法用于被渲染线程调用,例如页面点击检查更新按钮来调用此方法) // 触发检查更新(此方法用于被渲染线程调用,例如页面点击检查更新按钮来调用此方法)
ipcMain.on('check-for-update', () => { ipcMain.on('check-for-update', () => {
console.log('ipcMain:check-for-update') console.log('ipcMain:check-for-update')

View File

@ -4,7 +4,7 @@ import path from 'node:path'
import fs from 'node:fs' import fs from 'node:fs'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Adb } from '@devicefarmer/adbkit' import { Adb } from '@devicefarmer/adbkit'
import adbPath from '@resources/core/adb.exe?asset&asarUnpack' import adbPath from '@resources/extra/core/adb.exe?path'
const exec = util.promisify(child_process.exec) const exec = util.promisify(child_process.exec)
@ -18,7 +18,8 @@ window.addEventListener('beforeunload', () => {
const shell = async command => exec(`${adbPath} ${command}`) const shell = async command => exec(`${adbPath} ${command}`)
const getDevices = async () => await client.listDevicesWithPaths() const getDevices = async () => await client.listDevicesWithPaths()
const deviceShell = async (id, command) => await client.getDevice(id).shell(command) const deviceShell = async (id, command) =>
await client.getDevice(id).shell(command)
const kill = async (...params) => await client.kill(...params) const kill = async (...params) => await client.kill(...params)
const connect = async (...params) => await client.connect(...params) const connect = async (...params) => await client.connect(...params)
const disconnect = async (...params) => await client.disconnect(...params) const disconnect = async (...params) => await client.disconnect(...params)
@ -97,7 +98,7 @@ const watch = async (callback) => {
export default () => { export default () => {
client = Adb.createClient({ bin: adbPath }) client = Adb.createClient({ bin: adbPath })
console.log('client', client) // console.log('client', client)
return { return {
shell, shell,

View File

@ -4,9 +4,8 @@ import adbkit from './adbkit/index.js'
import scrcpy from './scrcpy/index.js' import scrcpy from './scrcpy/index.js'
export default { export default {
install(expose) { init(expose) {
expose('nodePath', path) expose('nodePath', path)
expose('electron', electron()) expose('electron', electron())
expose('adbkit', adbkit()) expose('adbkit', adbkit())
expose('scrcpy', scrcpy()) expose('scrcpy', scrcpy())

View File

@ -1,7 +1,7 @@
import util from 'node:util' import util from 'node:util'
import child_process from 'node:child_process' import child_process from 'node:child_process'
import adbPath from '@resources/core/adb.exe?asset&asarUnpack' import adbPath from '@resources/extra/core/adb.exe?path'
import scrcpyPath from '@resources/core/scrcpy.exe?asset&asarUnpack' import scrcpyPath from '@resources/extra/core/scrcpy.exe?path'
const exec = util.promisify(child_process.exec) const exec = util.promisify(child_process.exec)

175
electron/loading/index.js Normal file
View File

@ -0,0 +1,175 @@
// --------- Preload scripts loading ---------
function domReady(condition = ['complete', 'interactive']) {
return new Promise((resolve) => {
if (condition.includes(document.readyState)) {
resolve(true)
}
else {
document.addEventListener('readystatechange', () => {
if (condition.includes(document.readyState)) {
resolve(true)
}
})
}
})
}
const safeDOM = {
append(parent, child) {
if (!Array.from(parent.children).find(e => e === child)) {
parent.appendChild(child)
}
},
remove(parent, child) {
if (Array.from(parent.children).find(e => e === child)) {
parent.removeChild(child)
}
},
}
/**
* https://tobiasahlin.com/spinkit
* https://connoratherton.com/loaders
* https://projects.lukehaas.me/css-loaders
* https://matejkustec.github.io/SpinThatShit
*/
function useLoading() {
const className = 'electron-loading'
const loginStyles = `
.${className}-core {
font-size: 30px;
text-indent: -9999em;
overflow: hidden;
width: 1em;
height: 1em;
border-radius: 50%;
margin: 72px auto;
position: relative;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation: electron-loading-dots 1.7s infinite ease, electron-loading-spin 1.7s infinite ease;
animation: electron-loading-dots 1.7s infinite ease, electron-loading-spin 1.7s infinite ease;
}
@-webkit-keyframes electron-loading-dots {
0% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
5%,
95% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
10%,
59% {
box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em;
}
20% {
box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em;
}
38% {
box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em;
}
100% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
}
@keyframes electron-loading-dots {
0% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
5%,
95% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
10%,
59% {
box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em;
}
20% {
box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em;
}
38% {
box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em;
}
100% {
box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
}
}
@-webkit-keyframes electron-loading-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes electron-loading-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.${className}-wrap {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 3000;
background: white;
color: #028D71;
}
.${className}-text {
margin-top: -45px;
}
`
const styleEl = document.createElement('style')
styleEl.id = `${className}-style`
styleEl.innerHTML = loginStyles
const divEl = document.createElement('div')
divEl.className = `${className}-wrap`
divEl.innerHTML = `
<div class="${className}-core"></div>
<div class="${className}-text"> 初始化服务中...</div>
`
return {
appendLoading() {
safeDOM.append(document.head, styleEl)
safeDOM.append(document.body, divEl)
},
removeLoading() {
safeDOM.remove(document.head, styleEl)
safeDOM.remove(document.body, divEl)
},
}
}
// ----------------------------------------------------------------------
const { appendLoading, removeLoading } = useLoading()
domReady().then(appendLoading)
window.onmessage = (ev) => {
ev.data.payload === 'removeLoading' && removeLoading()
}
setTimeout(removeLoading, 4999)

106
electron/main.js Normal file
View File

@ -0,0 +1,106 @@
import path from 'node:path'
import { BrowserWindow, app, shell } from 'electron'
import { electronApp, optimizer } from '@electron-toolkit/utils'
import logoPath from '@resources/build/logo.png?path'
import icoLogoPath from '@resources/build/logo.ico?path'
import icnsLogoPath from '@resources/build/logo.icns?path'
import events from './events/index.js'
// The built directory structure
//
// ├─┬─┬ dist
// │ │ └── index.html
// │ │
// │ ├─┬ dist-electron
// │ │ ├── main.js
// │ │ └── preload.js
// │
process.env.DIST = path.join(__dirname, '../dist')
let mainWindow
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x
const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL
function createWindow() {
let icon = logoPath
if (process.platform === 'win32') {
icon = icoLogoPath
}
else if (process.platform === 'darwin') {
icon = icnsLogoPath
}
mainWindow = new BrowserWindow({
show: false,
icon,
minWidth: 1000,
minHeight: 700,
autoHideMenuBar: true,
webPreferences: {
// nodeIntegration: true,
// contextIsolation: false,
preload: path.join(__dirname, './preload.js'),
sandbox: false,
},
backgroundColor: 'white',
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// Test active push message to Renderer-process.
mainWindow.webContents.on('did-finish-load', () => {
mainWindow?.webContents.send(
'main-process-message',
new Date().toLocaleString(),
)
})
if (VITE_DEV_SERVER_URL) {
mainWindow.loadURL(VITE_DEV_SERVER_URL)
}
else {
// win.loadFile('dist/index.html')
mainWindow.loadFile(path.join(process.env.DIST, 'index.html'))
}
events(mainWindow)
}
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
mainWindow = null
}
})
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
app.whenReady().then(() => {
electronApp.setAppUserModelId('com.viarotel.escrcpy')
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
createWindow()
})

7
electron/preload.js Normal file
View File

@ -0,0 +1,7 @@
import exposes from './exposes/index.js'
import { exposeContext } from './helpers/index.js'
import './loading/index.js'
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'
exposes.init(exposeContext)

View File

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Escrcpy</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@ -1,11 +1,12 @@
{ {
"exclude": ["node_modules", "dist", "out"],
"include": ["src/**/*"],
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@renderer/*": ["src/renderer/src/*"], "@/*": ["src/*"],
"@resources/*": ["resources/*"] "@root/*": ["*"],
"@resources/*": ["electron/resources/*"]
} }
} },
} "exclude": ["node_modules", "dist", "dist-electron", "dist-release"],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.vue", "electron"]
}

View File

@ -1,52 +1,46 @@
{ {
"name": "escrcpy", "name": "escrcpy",
"version": "1.6.2", "version": "1.6.2",
"private": true,
"description": "Scrcpy Powered by Electron", "description": "Scrcpy Powered by Electron",
"author": "viarotel", "author": "viarotel",
"homepage": "https://github.com/viarotel-org/escrcpy", "homepage": "https://github.com/viarotel-org/escrcpy",
"main": "./out/main/index.js", "main": "dist-electron/main.js",
"scripts": { "scripts": {
"format": "prettier --write .", "dev": "vite",
"build": "vite build && electron-builder",
"build:win": "vite build && electron-builder --win",
"build:mac": "vite build && electron-builder --mac",
"build:linux": "vite build && electron-builder --linux",
"preview": "vite preview",
"lint": "eslint . --ext .md,.vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .eslintignore --fix", "lint": "eslint . --ext .md,.vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .eslintignore --fix",
"start": "electron-vite preview", "postinstall": "electron-builder install-app-deps"
"dev": "electron-vite dev",
"build": "electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:win": "npm run build && electron-builder --win --config",
"build:mac": "npm run build && electron-builder --mac --config",
"build:linux": "npm run build && electron-builder --linux --config"
}, },
"dependencies": { "dependencies": {
"@devicefarmer/adbkit": "^3.2.5", "vue": "^3.3.4"
"@electron-toolkit/preload": "^2.0.0",
"@electron-toolkit/utils": "^2.0.0",
"@viarotel-org/design": "^0.7.0",
"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"
}, },
"devDependencies": { "devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.1", "@devicefarmer/adbkit": "^3.2.5",
"@rushstack/eslint-patch": "^1.3.3", "@electron-toolkit/preload": "^2.0.0",
"@electron-toolkit/utils": "^2.0.1",
"@viarotel-org/design": "^0.7.0",
"@viarotel-org/eslint-config": "^0.7.0", "@viarotel-org/eslint-config": "^0.7.0",
"@viarotel-org/postcss-config": "^0.7.0", "@viarotel-org/postcss-config": "^0.7.0",
"@viarotel-org/unocss-config": "^0.7.4", "@viarotel-org/unocss-config": "^0.7.4",
"@vitejs/plugin-vue": "^4.3.1", "@viarotel-org/vite-plugin-path": "^0.8.1",
"@vue/eslint-config-prettier": "^8.0.0", "@vitejs/plugin-vue": "^4.3.4",
"electron": "^25.6.0", "dayjs": "^1.11.10",
"electron": "^26.1.0",
"electron-builder": "^24.6.4", "electron-builder": "^24.6.4",
"electron-vite": "^1.0.28", "electron-updater": "^6.1.4",
"eslint": "8.49.0", "element-plus": "^2.4.0",
"eslint-plugin-vue": "^9.17.0", "lodash-es": "^4.17.21",
"less": "^4.2.0", "pinia": "^2.1.7",
"prettier": "^3.0.2",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^4.4.9", "vite": "^4.4.9",
"vite-plugin-electron": "^0.14.0",
"vite-plugin-electron-renderer": "^0.14.5",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vue": "^3.3.4" "vue-tsc": "^1.8.8"
} }
} }

1745
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

File diff suppressed because one or more lines are too long

View File

@ -8,10 +8,7 @@
:name="item.prop" :name="item.prop"
lazy lazy
> >
<component <component :is="item.prop" :ref="item.prop" />
:is="item.prop"
:ref="item.prop"
/>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-col items-center justify-center h-full -mt-8"> <div class="flex flex-col items-center justify-center h-full -mt-8">
<div class=""> <div class="">
<img src="@renderer/assets/icon.png" class="h-48" alt="" /> <img src="@/assets/icon.png" class="h-48" alt="" />
</div> </div>
<div class="pt-4 text-xl text-center italic text-gray-700"> <div class="pt-4 text-xl text-center italic text-gray-700">
📱 使用图形化的 📱 使用图形化的
@ -22,7 +22,7 @@
</template> </template>
<script> <script>
import { version } from '@root/package.json' import { version } from '/package.json'
export default { export default {
data() { data() {

View File

@ -108,7 +108,7 @@
<script> <script>
import { debounce } from 'lodash-es' import { debounce } from 'lodash-es'
import { useScrcpyStore } from '@renderer/store/index.js' import { useScrcpyStore } from '@/store/index.js'
export default { export default {
data() { data() {

View File

@ -58,10 +58,10 @@ export default {
tips: '可以用来开启或关闭屏幕', tips: '可以用来开启或关闭屏幕',
}, },
{ {
label: '截屏快照', label: '截取屏幕',
icon: 'Crop', icon: 'Crop',
handle: this.handleScreenCap, handle: this.handleScreenCap,
tips: '不要和切换键搞错啦', tips: '',
}, },
], ],
} }

View File

@ -1,7 +1,7 @@
<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"> <div class="flex items-center flex-none space-x-2">
<el-input v-model="formData.host" placeholder="192.168.0.1" class="w-86 flex-none" clearable> <el-input v-model="formData.host" placeholder="192.168.0.1" class="!w-86 flex-none" clearable>
<template #prepend> <template #prepend>
无线连接 无线连接
</template> </template>
@ -15,7 +15,7 @@
placeholder="5555" placeholder="5555"
:min="0" :min="0"
clearable clearable
class="w-32 flex-none" class="!w-32 flex-none"
> >
</el-input> </el-input>
@ -69,7 +69,7 @@
{{ row.name }} {{ row.name }}
<el-tag v-if="row.$wireless" type="primary" effect="light" class="ml-2"> <el-tag v-if="row.$wireless" effect="light" class="ml-2">
WIFI WIFI
</el-tag> </el-tag>
</div> </div>
@ -140,11 +140,11 @@
</template> </template>
<script> <script>
import { isIPWithPort, sleep } from '@renderer/utils/index.js'
import storage from '@renderer/utils/storages'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import PairDialog from './PairDialog/index.vue' import PairDialog from './PairDialog/index.vue'
import ControlBar from './ControlBar/index.vue' import ControlBar from './ControlBar/index.vue'
import storage from '@/utils/storages'
import { isIPWithPort, sleep } from '@/utils/index.js'
export default { export default {
components: { components: {
@ -155,7 +155,7 @@ export default {
const adbCache = storage.get('adbCache') || {} const adbCache = storage.get('adbCache') || {}
return { return {
loading: false, loading: false,
loadingText: '初始化中...', loadingText: '努力加载中...',
connectLoading: false, connectLoading: false,
deviceList: [], deviceList: [],
formData: { formData: {
@ -347,6 +347,7 @@ export default {
console.log('getDeviceData.data', this.deviceList) console.log('getDeviceData.data', this.deviceList)
} }
catch (error) { catch (error) {
console.log(error)
if (error.message) { if (error.message) {
this.$message.warning(error.message) this.$message.warning(error.message)
} }

View File

@ -20,3 +20,8 @@ app.config.globalProperties.$scrcpy = window.scrcpy
app.config.globalProperties.$path = window.nodePath app.config.globalProperties.$path = window.nodePath
app.mount('#app') app.mount('#app')
app.$nextTick(() => {
// Remove Preload scripts loading
postMessage({ payload: 'removeLoading' }, '*')
})

View File

@ -1,89 +0,0 @@
import { join } from 'node:path'
import { BrowserWindow, app, shell } from 'electron'
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
import iconPath from '../../resources/icons/icon.png?asset'
import winIconPath from '../../resources/icons/icon.ico?asset'
import macIconPath from '../../resources/icons/icon.icns?asset'
import ipcManage from './ipcManage/index.js'
function createWindow() {
let icon = iconPath
if (process.platform === 'win32') {
icon = winIconPath
}
else if (process.platform === 'darwin') {
icon = macIconPath
}
// Create the browser window.
const mainWindow = new BrowserWindow({
icon,
minWidth: 1000,
minHeight: 700,
show: false,
autoHideMenuBar: true,
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
},
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env.ELECTRON_RENDERER_URL) {
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL)
}
else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
ipcManage(mainWindow)
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
createWindow()
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0)
createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.

View File

@ -1,4 +0,0 @@
import plugins from './plugins/index.js'
import { exposeContext } from './helpers/index.js'
plugins.install(exposeContext)

View File

@ -1,18 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Escrcpy</title>
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
/>
<link rel="icon" href="/src/assets/logo.jpg" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,7 +1,7 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import storage from '@renderer/utils/storages'
import { pickBy } from 'lodash-es' import { pickBy } from 'lodash-es'
import * as scrcpyModel from './model/index.js' import * as scrcpyModel from './model/index.js'
import storage from '@/utils/storages/index.js'
/** /**
* 获取 Scrcpy 默认配置 * 获取 Scrcpy 默认配置

View File

@ -3,7 +3,7 @@ export default () => {
return [ return [
{ {
label: '文件存储地址', label: '文件保存路径',
type: 'input.directory', type: 'input.directory',
field: '--record', field: '--record',
value: $path.resolve('../'), value: $path.resolve('../'),

52
vite.config.js Normal file
View File

@ -0,0 +1,52 @@
import { resolve } from 'node:path'
import { defineConfig, mergeConfig } from 'vite'
import useElectron from 'vite-plugin-electron'
import useRenderer from 'vite-plugin-electron-renderer'
import useVue from '@vitejs/plugin-vue'
import useEslint from 'vite-plugin-eslint'
import useUnoCSS from 'unocss/vite'
import usePath from '@viarotel-org/vite-plugin-path'
const merge = config =>
mergeConfig(
{
resolve: {
alias: {
'@root': resolve('./'),
'@resources': resolve('./electron/resources'),
},
},
plugins: [usePath()],
},
config,
)
// https://vitejs.dev/config/
export default defineConfig({
assetsInclude: ['**/*.exe'],
resolve: {
alias: {
'@': resolve('./src'),
},
},
plugins: [
useEslint(),
useUnoCSS(),
useVue(),
useElectron([
{
entry: 'electron/main.js',
vite: merge({}),
},
{
entry: 'electron/preload.js',
onstart(args) {
args.reload()
},
vite: merge({}),
},
]),
useRenderer(),
],
})