Showing preview only (413K chars total). Download the full file or copy to clipboard to get everything.
Repository: barry-ran/QtScrcpy
Branch: dev
Commit: 7b8a9580a698
Files: 93
Total size: 388.8 KB
Directory structure:
gitextract_v8zjkv6h/
├── .clang-format
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── macos.yml
│ ├── ubuntu.yml
│ └── windows.yml
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── LICENSE
├── QtScrcpy/
│ ├── CMakeLists.txt
│ ├── appversion
│ ├── audio/
│ │ ├── audiooutput.cpp
│ │ └── audiooutput.h
│ ├── clang-format-all.sh
│ ├── fontawesome/
│ │ ├── iconhelper.cpp
│ │ └── iconhelper.h
│ ├── groupcontroller/
│ │ ├── groupcontroller.cpp
│ │ └── groupcontroller.h
│ ├── main.cpp
│ ├── render/
│ │ ├── qyuvopenglwidget.cpp
│ │ └── qyuvopenglwidget.h
│ ├── res/
│ │ ├── Info_Mac.plist.in
│ │ ├── QtScrcpy.icns
│ │ ├── QtScrcpy.rc
│ │ ├── i18n/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── en_US.qm
│ │ │ ├── en_US.ts
│ │ │ ├── ja_JP.qm
│ │ │ ├── ja_JP.ts
│ │ │ ├── ko_KR.qm
│ │ │ ├── ko_KR.ts
│ │ │ ├── zh_CN.qm
│ │ │ └── zh_CN.ts
│ │ ├── qss/
│ │ │ └── psblack.css
│ │ └── res.qrc
│ ├── sndcpy/
│ │ ├── sndcpy.apk
│ │ ├── sndcpy.bat
│ │ └── sndcpy.sh
│ ├── ui/
│ │ ├── dialog.cpp
│ │ ├── dialog.h
│ │ ├── dialog.ui
│ │ ├── toolform.cpp
│ │ ├── toolform.h
│ │ ├── toolform.ui
│ │ ├── videoform.cpp
│ │ ├── videoform.h
│ │ └── videoform.ui
│ ├── uibase/
│ │ ├── keepratiowidget.cpp
│ │ ├── keepratiowidget.h
│ │ ├── magneticwidget.cpp
│ │ └── magneticwidget.h
│ └── util/
│ ├── config.cpp
│ ├── config.h
│ ├── mousetap/
│ │ ├── cocoamousetap.h
│ │ ├── cocoamousetap.mm
│ │ ├── mousetap.cpp
│ │ ├── mousetap.h
│ │ ├── winmousetap.cpp
│ │ ├── winmousetap.h
│ │ ├── xmousetap.cpp
│ │ └── xmousetap.h
│ ├── path.h
│ ├── path.mm
│ ├── winutils.cpp
│ └── winutils.h
├── README.md
├── README_zh.md
├── backup/
│ └── myconfig.sh
├── ci/
│ ├── generate-version.py
│ ├── linux/
│ │ ├── build_for_linux.sh
│ │ ├── package_appimage.sh
│ │ └── publish_for_ubuntu.sh.todo
│ ├── lrelease.sh
│ ├── lupdate.sh
│ ├── mac/
│ │ ├── build_for_mac.sh
│ │ ├── package/
│ │ │ ├── dmg-settings.json
│ │ │ ├── package.py
│ │ │ └── requirements.txt
│ │ ├── package_for_mac.sh
│ │ └── publish_for_mac.sh
│ └── win/
│ ├── build_for_win.bat
│ └── publish_for_win.bat
├── config/
│ └── config.ini
├── docs/
│ ├── DEVELOP.md
│ ├── FAQ.md
│ ├── KeyMapDes.md
│ ├── KeyMapDes_zh.md
│ └── TODO.md
└── keymap/
├── FRAG.json
├── gameforpeace.json
├── identityv.json
├── test.json
└── tiktok.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .clang-format
================================================
---
# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto
Language: Cpp
# BasedOnStyle: WebKit
# 访问说明符(public、private等)的偏移
AccessModifierOffset: -4
# 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行)
AlignAfterOpenBracket: AlwaysBreak
# 连续赋值时,对齐所有等号
AlignConsecutiveAssignments: false
# 连续声明时,对齐所有声明的变量名
AlignConsecutiveDeclarations: false
# 左对齐逃脱换行(使用反斜杠换行)的反斜杠
AlignEscapedNewlines: Right
# 水平对齐二元和三元表达式的操作数
AlignOperands: true
# 对齐连续的尾随的注释
AlignTrailingComments: true
# 允许函数声明的所有参数在放在下一行
AllowAllParametersOfDeclarationOnNextLine: false
# 允许短的块放在同一行
AllowShortBlocksOnASingleLine: false
# 允许短的case标签放在同一行
AllowShortCaseLabelsOnASingleLine: false
# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All
AllowShortFunctionsOnASingleLine: Empty
# 允许短的if语句保持在同一行
AllowShortIfStatementsOnASingleLine: false
# 允许短的循环保持在同一行
AllowShortLoopsOnASingleLine: false
# 总是在定义返回类型后换行(deprecated)
AlwaysBreakAfterDefinitionReturnType: None
# 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数),
# AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义)
AlwaysBreakAfterReturnType: None
# 总是在多行string字面量前换行
AlwaysBreakBeforeMultilineStrings: false
# 总是在template声明后换行
AlwaysBreakTemplateDeclarations: true
# false表示函数实参要么都在同一行,要么都各自一行
BinPackArguments: false
# false表示所有形参要么都在同一行,要么都各自一行
BinPackParameters: false
# 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似),
# Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似),
# Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom
# 注:这里认为语句块也属于函数
BreakBeforeBraces: Custom
# 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效
BraceWrapping:
# class定义后面
AfterClass: true
# 控制语句后面
AfterControlStatement: false
# enum定义后面
AfterEnum: true
# 函数定义后面
AfterFunction: true
# 命名空间定义后面
AfterNamespace: true
# ObjC定义后面
AfterObjCDeclaration: false
# struct定义后面
AfterStruct: true
# union定义后面
AfterUnion: true
# extern 定义后面
AfterExternBlock: true
# catch之前
BeforeCatch: false
# else 之前
BeforeElse: false
# 缩进大括号
IndentBraces: false
# 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行)
BreakBeforeBinaryOperators: All
# 继承列表的逗号前换行
BreakBeforeInheritanceComma: true
# 继承列表换行
#BreakInheritanceList: BeforeColon
# 在三元运算符前换行
BreakBeforeTernaryOperators: true
# 在构造函数的初始化列表的逗号前换行
BreakConstructorInitializersBeforeComma: true
# 初始化列表前换行
BreakConstructorInitializers: BeforeComma
# Java注解后换行
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
# 每行字符的限制,0表示没有限制
ColumnLimit: 160
# 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变
CommentPragmas: '^ IWYU pragma:'
# 紧凑 命名空间
CompactNamespaces: false
# 构造函数的初始化列表要么都在同一行,要么都各自一行
ConstructorInitializerAllOnOneLineOrOnePerLine: true
# 构造函数的初始化列表的缩进宽度
ConstructorInitializerIndentWidth: 4
# 延续的行的缩进宽度
ContinuationIndentWidth: 4
# 去除C++11的列表初始化的大括号{后和}前的空格
Cpp11BracedListStyle: false
# 继承最常用的指针和引用的对齐方式
DerivePointerAlignment: false
# 关闭格式化
DisableFormat: false
# 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental)
ExperimentalAutoDetectBinPacking: false
# 固定命名空间注释
FixNamespaceComments: true
# 需要被解读为foreach循环而不是函数调用的宏
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
# 对#include进行排序,匹配了某正则表达式的#include拥有对应的优先级,匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前),
# 可以定义负数优先级从而保证某些#include永远在最前面
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
- Regex: 'stdafx\.'
Priority: 1
- Regex: '.*'
Priority: 1
IncludeIsMainRegex: '(Test)?$'
# 缩进case标签
IndentCaseLabels: false
IndentPPDirectives: None
# 缩进宽度
IndentWidth: 4
# 函数返回类型换行时,缩进函数声明或函数定义的函数名
IndentWrappedFunctionNames: true
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
# 保留在块开始处的空行
KeepEmptyLinesAtTheStartOfBlocks: true
# 开始一个块的宏的正则表达式
MacroBlockBegin: ''
# 结束一个块的宏的正则表达式
MacroBlockEnd: ''
# 连续空行的最大数量
MaxEmptyLinesToKeep: 1
# 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
# 使用ObjC块时缩进宽度
ObjCBlockIndentWidth: 4
# 在ObjC的@property后添加一个空格
ObjCSpaceAfterProperty: true
# 在ObjC的protocol列表前添加一个空格
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
# 在一个注释中引入换行的penalty
PenaltyBreakComment: 300
# 第一次在<<前换行的penalty
PenaltyBreakFirstLessLess: 120
# 在一个字符串字面量中引入换行的penalty
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
# 对于每个在行字符数限制之外的字符的penalty
PenaltyExcessCharacter: 1000000
# 将函数的返回类型放到它自己的行的penalty
PenaltyReturnTypeOnItsOwnLine: 60
# 指针和引用的对齐: Left, Right, Middle
PointerAlignment: Right
#RawStringFormats:
# - Delimiter: pb
# Language: TextProto
# BasedOnStyle: google
# 允许重新排版注释
ReflowComments: false
# 允许排序#include
SortIncludes: true
SortUsingDeclarations: true
# 在C风格类型转换后添加空格
SpaceAfterCStyleCast: false
# 模板关键字后面添加空格
SpaceAfterTemplateKeyword: true
# 在赋值运算符之前添加空格
SpaceBeforeAssignmentOperators: true
# 开圆括号之前添加一个空格: Never, ControlStatements, Always
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
# 在空的圆括号中添加空格
SpaceInEmptyParentheses: false
# 在尾随的评论前添加的空格数(只适用于//)
SpacesBeforeTrailingComments: 1
# 在尖括号的<后和>前添加空格
SpacesInAngles: false
# 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格
SpacesInContainerLiterals: true
# 在C风格类型转换的括号中添加空格
SpacesInCStyleCastParentheses: false
# 在圆括号的(后和)前添加空格
SpacesInParentheses: false
# 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响
SpacesInSquareBrackets: false
# 标准: Cpp03, Cpp11, Auto
Standard: Cpp11
# tab宽度
TabWidth: 4
# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always
UseTab: Never
...
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: barry-ran
patreon: # Replace with a single Patreon username
open_collective: QtScrcpy
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ["https://paypal.me/QtScrcpy", "https://gitee.com/Barryda/MyPictureBed/blob/master/QtScrcpy/payme.md"]
================================================
FILE: .github/workflows/macos.yml
================================================
name: MacOS
on:
push:
paths:
- 'QtScrcpy/**'
- '!QtScrcpy/res/**'
- '.github/workflows/macos.yml'
pull_request:
paths:
- 'QtScrcpy/**'
- '!QtScrcpy/res/**'
- '.github/workflows/macos.yml'
jobs:
build:
name: Build
# install-qt-action在arm上执行macdeployqt会报parse otool错误,所以在intel mac上执行:
# 用qt6时在arm mac上编译arm和intel都没有问题
# qt5+intel mac编译intel没问题
# qt5+arm mac编译intel会报错
# https://github.com/actions/runner-images?tab=readme-ov-file#available-images
runs-on: macos-13
strategy:
matrix:
qt-ver: [5.15.2, 6.5.3]
# 配置qt-ver的额外设置qt-arch-install,build-arch
include:
- qt-ver: 5.15.2
qt-arch-install: clang_64
build-arch: x64
- qt-ver: 6.5.3
qt-arch-install: arm64
build-arch: arm64
env:
target-name: QtScrcpy
qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
plantform-des: mac
steps:
- name: Cache Qt
id: cache-qt
uses: actions/cache@v4
with:
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
- name: Install Qt5
if: startsWith(matrix.qt-ver, '5.')
uses: jurplel/install-qt-action@v4.1.1
with:
version: ${{ matrix.qt-ver }}
cached: ${{ steps.cache-qt.outputs.cache-hit }}
- name: Install Qt6
if: startsWith(matrix.qt-ver, '6.')
uses: jurplel/install-qt-action@v4.1.1
with:
version: ${{ matrix.qt-ver }}
modules: qtmultimedia
cached: ${{ steps.cache-qt.outputs.cache-hit }}
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: 'true'
ssh-key: ${{ secrets.BOT_SSH_KEY }}
# 编译
- name: Build MacOS
env:
ENV_QT_PATH: ${{ env.qt-install-path }}
run: |
python ci/generate-version.py
ci/mac/build_for_mac.sh RelWithDebInfo ${{ matrix.build-arch }}
# 获取ref最后一个/后的内容
- name: Get the version
shell: bash
id: get-version
# ${ GITHUB_REF/refs\/tags\// }是linux shell ${}的变量替换语法
run: echo ::set-output name=version::${GITHUB_REF##*/}
# 打包
- name: Package
id: package
env:
ENV_QT_PATH: ${{ env.qt-install-path }}
publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.build-arch }}-Qt${{matrix.qt-ver}}-${{ steps.get-version.outputs.version }}
run: |
ci/mac/publish_for_mac.sh ../build ${{ matrix.build-arch }}
ci/mac/package_for_mac.sh
mv ci/build/QtScrcpy.app ci/build/${{ env.publish_name }}.app
mv ci/build/QtScrcpy.dmg ci/build/${{ env.publish_name }}.dmg
echo "::set-output name=package-name::${{ env.publish_name }}"
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.package.outputs.package-name }}.zip
path: ci/build/${{ steps.package.outputs.package-name }}.dmg
# Upload to release
- name: Upload Release
if: startsWith(github.ref, 'refs/tags/')
uses: svenstaro/upload-release-action@v1-release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ci/build/${{ steps.package.outputs.package-name }}.dmg
asset_name: ${{ steps.package.outputs.package-name }}.dmg
tag: ${{ github.ref }}
overwrite: true
================================================
FILE: .github/workflows/ubuntu.yml
================================================
name: Ubuntu
on:
push:
paths:
- 'QtScrcpy/**'
- '!QtScrcpy/res/**'
- '.github/workflows/ubuntu.yml'
- 'ci/linux/**'
pull_request:
paths:
- 'QtScrcpy/**'
- '!QtScrcpy/res/**'
- '.github/workflows/ubuntu.yml'
- 'ci/linux/**'
jobs:
build:
name: Build
runs-on: ubuntu-22.04
container:
image: ubuntu:20.04
options: --privileged
strategy:
matrix:
qt-ver: [5.15.2]
qt-arch-install: [gcc_64]
gcc-arch: [x64]
env:
target-name: QtScrcpy
qt-install-path: ${{ github.workspace }}/Qt/${{ matrix.qt-ver }}
plantform-des: ubuntu
DEBIAN_FRONTEND: noninteractive
steps:
- name: Install Git and basic dependencies
run: |
apt-get update
apt-get install -y git ca-certificates sudo
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: 'true'
ssh-key: ${{ secrets.BOT_SSH_KEY }}
- name: Install system dependencies
run: |
apt-get update
apt-get install -y \
build-essential \
cmake \
libglew-dev \
libglfw3-dev \
imagemagick \
wget \
patchelf \
zip \
libxcb1-dev \
libxkbcommon-dev \
libxkbcommon-x11-dev \
libx11-dev \
libx11-xcb-dev \
libfontconfig1-dev \
libfreetype6-dev \
libxrender-dev \
libxext-dev \
gnupg \
lsb-release \
python3 \
python3-pip \
fuse \
libasound2-dev
- name: Setup FUSE
run: |
apt-get install -y fuse
if [ ! -e /dev/fuse ]; then
mknod /dev/fuse c 10 229 || true
chmod 666 /dev/fuse || true
fi
export APPIMAGE_EXTRACT_AND_RUN=1
- name: Install CMake 3.19+
run: |
apt-get install -y software-properties-common
apt-get update
apt-get install -y cmake=3.19.* || {
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | apt-key add -
apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main'
apt-get update
apt-get install -y cmake
}
- name: Cache Qt
id: cache-qt
uses: actions/cache@v4
with:
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
key: ubuntu-20.04/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
- name: Install Qt
uses: jurplel/install-qt-action@v4.1.1
with:
version: ${{ matrix.qt-ver }}
cache: ${{ steps.cache-qt.outputs.cache-hit }}
setup-python: false
- name: Build Release
shell: bash
env:
ENV_QT_PATH: ${{ github.workspace }}/Qt/${{ matrix.qt-ver }}
run: |
python3 ci/generate-version.py
ci/linux/build_for_linux.sh "Release"
- name: Get the version
shell: bash
id: get-version
run: echo ::set-output name=version::${GITHUB_REF##*/}
- name: Package AppImage
shell: bash
env:
ENV_QT_PATH: ${{ github.workspace }}/Qt/${{ matrix.qt-ver }}
run: |
chmod +x ci/linux/package_appimage.sh
ci/linux/package_appimage.sh "Release"
- name: Upload AppImage Artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}-AppImage
path: output/appimage/*.AppImage
if-no-files-found: error
- name: Prepare AppImage for Release
if: startsWith(github.ref, 'refs/tags/')
run: |
APPIMAGE_FILE=$(find output/appimage -name "QtScrcpy-*.AppImage" -type f | head -n 1)
if [ -z "$APPIMAGE_FILE" ] || [ ! -f "$APPIMAGE_FILE" ]; then
echo "Error: AppImage file not found"
exit 1
fi
FINAL_NAME="${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}.AppImage"
cp "$APPIMAGE_FILE" "$FINAL_NAME"
- name: Upload AppImage to Releases
if: startsWith(github.ref, 'refs/tags/')
uses: svenstaro/upload-release-action@2.3.0
with:
file: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}.AppImage
asset_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}.AppImage
repo_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref }}
overwrite: true
================================================
FILE: .github/workflows/windows.yml
================================================
name: Windows
# 触发规则详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#on
on:
push:
paths:
- 'QtScrcpy/**'
- '!QtScrcpy/res/**'
- '.github/workflows/windows.yml'
- 'ci/win**'
pull_request:
paths:
- 'QtScrcpy/**'
- '!QtScrcpy/res/**'
- '.github/workflows/windows.yml'
- 'ci/win**'
jobs:
build:
name: Build
# windows-latest目前是windows server 2022
# windows server 2019安装的是vs2019,windows server 2016安装的是vs2017
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
runs-on: windows-latest
# 矩阵配置 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix
strategy:
matrix:
qt-ver: [5.15.2]
qt-arch: [win64_msvc2019_64, win32_msvc2019]
# 配置qt-arch的额外设置msvc-arch,qt-arch-install
include:
- qt-arch: win64_msvc2019_64
msvc-arch: x64
qt-arch-install: msvc2019_64
- qt-arch: win32_msvc2019
msvc-arch: x86
qt-arch-install: msvc2019
# job env,所有steps都可以访问
# 不同级别env详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#env
# 使用表达式语法${{}}访问上下文 https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions
env:
target-name: QtScrcpy
vcvarsall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat'
vcinstall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\VC'
qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
plantform-des: win
# 步骤
steps:
- name: Cache Qt
id: cache-qt
uses: actions/cache@v4
with:
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch }}
# 安装Qt
- name: Install Qt
# 使用外部action。这个action专门用来安装Qt
uses: jurplel/install-qt-action@v4.1.1
with:
# Version of Qt to install
version: ${{ matrix.qt-ver }}
# Target platform for build
target: desktop
# Architecture for Windows/Android
arch: ${{ matrix.qt-arch }}
cached: ${{ steps.cache-qt.outputs.cache-hit }}
# 拉取代码
- uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: 'true'
ssh-key: ${{ secrets.BOT_SSH_KEY }}
# 编译msvc
- name: Build MSVC
# shell介绍 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
shell: cmd
env:
ENV_VCVARSALL: ${{ env.vcvarsall-path }}
ENV_QT_PATH: ${{ env.qt-install-path }}
run: |
call python ci\generate-version.py
call "ci\win\build_for_win.bat" RelWithDebInfo ${{ matrix.msvc-arch }}
# 获取ref最后一个/后的内容
- name: Get the version
shell: bash
id: get-version
# ${ GITHUB_REF/refs\/tags\// }是linux shell ${}的变量替换语法
run: echo ::set-output name=version::${GITHUB_REF##*/}
# tag 打包
- name: Package
id: package
env:
ENV_VCVARSALL: ${{ env.vcvarsall-path }}
ENV_VCINSTALL: ${{ env.vcinstall-path }}
ENV_QT_PATH: ${{ env.qt-install-path }}
publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.msvc-arch }}-${{ steps.get-version.outputs.version }}
run: |
cmd.exe /c ci\win\publish_for_win.bat ${{ matrix.msvc-arch }} ..\build\${{ env.publish_name }}
# 打包zip
Compress-Archive -Path ci\build\${{ env.publish_name }} ci\build\${{ env.publish_name }}.zip
echo "::set-output name=package-name::${{ env.publish_name }}"
# 上传artifacts
# https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts
- uses: actions/upload-artifact@v4
with:
name: ${{ steps.package.outputs.package-name }}.zip
path: ci\build\${{ steps.package.outputs.package-name }}
# Upload to release
- name: Upload Release
if: startsWith(github.ref, 'refs/tags/')
uses: svenstaro/upload-release-action@v1-release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ci\build\${{ steps.package.outputs.package-name }}.zip
asset_name: ${{ steps.package.outputs.package-name }}.zip
tag: ${{ github.ref }}
overwrite: true
================================================
FILE: .gitignore
================================================
/output
*.user
/QtScrcpy/*.user
/server/.gradle
/server/.idea
/server/build
/server/gradle/wrapper/gradle-wrapper.jar
/server/gradle/wrapper/gradle-wrapper.properties
/server/gradlew
/server/gradlew.bat
/server/local.properties
/build/
build-*
*.DS_Store
userdata.ini
Info_Mac.plist
/ci/build_temp
================================================
FILE: .gitmodules
================================================
[submodule "QtScrcpy/QtScrcpyCore"]
path = QtScrcpy/QtScrcpyCore
url = git@github.com:barry-ran/QtScrcpyCore.git
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
project(all)
add_subdirectory(QtScrcpy)
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (C) 2019 Rankun
Copyright (C) 2019-2025 Rankun
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: QtScrcpy/CMakeLists.txt
================================================
# For VS2019 and Xcode 12+ support.
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
#
# Global config
#
# QC is "Qt CMake"
# https://www.kdab.com/wp-content/uploads/stories/QTVTC20-Using-Modern-CMake-Kevin-Funk.pdf
# QC Custom config
set(QC_PROJECT_NAME "QtScrcpy")
# Read version numbers from file
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/appversion QC_FILE_VERSION)
set(QC_PROJECT_VERSION ${QC_FILE_VERSION})
# Project declare
project(${QC_PROJECT_NAME} VERSION ${QC_PROJECT_VERSION} LANGUAGES CXX)
message(STATUS "[${PROJECT_NAME}] Project ${PROJECT_NAME} ${PROJECT_VERSION}")
# QC define
# check arch
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(QC_CPU_ARCH x64)
else()
set(QC_CPU_ARCH x86)
endif()
# MacOS
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
# mac default arch arm64
if(NOT CMAKE_OSX_ARCHITECTURES)
set(CMAKE_OSX_ARCHITECTURES arm64)
endif()
if (CMAKE_OSX_ARCHITECTURES MATCHES "arm64")
set(QC_CPU_ARCH arm64)
endif()
endif()
message(STATUS "[${PROJECT_NAME}] CPU_ARCH:${QC_CPU_ARCH}")
# CMake set
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# default RelWithDebInfo
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()
message(STATUS "[${PROJECT_NAME}] BUILD_TYPE:${CMAKE_BUILD_TYPE}")
# Log configuration
option(ENABLE_DETAILED_LOGS "Enable detailed log output with file and line info" OFF)
if(ENABLE_DETAILED_LOGS)
message(STATUS "[${PROJECT_NAME}] Detailed logs enabled")
else()
message(STATUS "[${PROJECT_NAME}] Simple logs enabled")
endif()
# Compiler set
message(STATUS "[${PROJECT_NAME}] C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}")
if (MSVC)
# FFmpeg cannot be compiled natively by MSVC version < 12.0 (2013)
if(MSVC_VERSION LESS 1800)
message(FATAL_ERROR "[${PROJECT_NAME}] ERROR: MSVC version is older than 12.0 (2013).")
endif()
message(STATUS "[${PROJECT_NAME}] Set Warnings as error")
# warning level 3 and all warnings as errors
add_compile_options(/W3 /WX /wd4566)
# avoid warning C4819
#add_compile_options(-source-charset:utf-8)
# /utf-8 will set source charset and execution charset to utf-8, so we don't need to set source-charset:utf-8
add_compile_options(/utf-8)
# ensure we use minimal "windows.h" lib without the crazy min max macros
add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
# disable SAFESEH - avoid "LNK2026: module unsafe"(Qt5.15&&vs2019)
add_link_options(/SAFESEH:NO)
endif()
if (NOT MSVC)
message(STATUS "[${PROJECT_NAME}] Set warnings as error")
# lots of warnings and all warnings as errors
add_compile_options(-Wall -Wextra -pedantic -Werror)
# disable some warning
add_compile_options(-Wno-nested-anon-types -Wno-c++17-extensions -Wno-overloaded-virtual)
endif()
#
# Qt
#
# Find Qt version
if (NOT QT_DESIRED_VERSION)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
message(" >>> Found Qt version: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}")
set(QT_DESIRED_VERSION ${QT_VERSION_MAJOR})
endif()
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(qt_required_components Widgets Network Multimedia)
if (QT_DESIRED_VERSION MATCHES 6)
# list(APPEND qt_required_components Core5Compat)
list(APPEND qt_required_components OpenGL)
list(APPEND qt_required_components OpenGLWidgets)
else()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND qt_required_components X11Extras )
endif()
endif()
find_package(Qt${QT_DESIRED_VERSION} REQUIRED COMPONENTS ${qt_required_components})
set(LINK_LIBS
Qt${QT_DESIRED_VERSION}::Widgets
Qt${QT_DESIRED_VERSION}::Network
Qt${QT_DESIRED_VERSION}::Multimedia
)
if (QT_DESIRED_VERSION MATCHES 6)
# list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::Core5Compat)
list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::GuiPrivate)
list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::OpenGL)
list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::OpenGLWidgets)
else()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::X11Extras)
endif()
endif()
message(STATUS "[${PROJECT_NAME}] Qt version is: ${QT_DESIRED_VERSION}")
#
# Sources
#
# fontawesome
set(QC_FONTAWESOME_SOURCES
fontawesome/iconhelper.h
fontawesome/iconhelper.cpp
)
source_group(fontawesome FILES ${QC_FONTAWESOME_SOURCES})
# uibase
set(QC_UIBASE_SOURCES
uibase/keepratiowidget.h
uibase/keepratiowidget.cpp
uibase/magneticwidget.h
uibase/magneticwidget.cpp
)
source_group(uibase FILES ${QC_UIBASE_SOURCES})
# audio
set(QC_AUDIO_SOURCES
audio/audiooutput.h
audio/audiooutput.cpp
)
source_group(audio FILES ${QC_AUDIO_SOURCES})
# ui
set(QC_UI_SOURCES
ui/toolform.h
ui/toolform.cpp
ui/toolform.ui
ui/videoform.h
ui/videoform.cpp
ui/videoform.ui
ui/dialog.cpp
ui/dialog.h
ui/dialog.ui
render/qyuvopenglwidget.h
render/qyuvopenglwidget.cpp
)
source_group(ui FILES ${QC_UI_SOURCES})
# group controller
set(QC_GROUP_CONTROLLER
groupcontroller/groupcontroller.h
groupcontroller/groupcontroller.cpp
)
source_group(groupcontroller FILES ${QC_GROUP_CONTROLLER})
# util
set(QC_UTIL_SOURCES
util/config.h
util/config.cpp
util/mousetap/mousetap.h
util/mousetap/mousetap.cpp
)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES}
util/mousetap/winmousetap.h
util/mousetap/winmousetap.cpp
util/winutils.h
util/winutils.cpp
)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES}
util/mousetap/xmousetap.h
util/mousetap/xmousetap.cpp
)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES}
util/mousetap/cocoamousetap.h
util/mousetap/cocoamousetap.mm
util/path.h
util/path.mm
)
endif()
source_group(util FILES ${QC_UTIL_SOURCES})
# qrc
set(QC_QRC_SOURCES "res/res.qrc")
# main
set(QC_MAIN_SOURCES
main.cpp
${QC_QRC_SOURCES}
)
# plantform file
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
# Define VERSION macros for .rc file
add_compile_definitions(
VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
VERSION_MINOR=${PROJECT_VERSION_MINOR}
VERSION_PATCH=${PROJECT_VERSION_PATCH}
VERSION_RC_STR="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}"
)
set(QC_PLANTFORM_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.rc"
)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
# Step 1. add icns to source file, for MACOSX_PACKAGE_LOCATION copy
set(QC_PLANTFORM_SOURCES
"${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.icns"
)
endif()
# 翻译相关(使用shell脚本替代cmake处理翻译)
# add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/res/i18n)
# all sources
set(QC_PROJECT_SOURCES
${QC_FONTAWESOME_SOURCES}
${QC_UIBASE_SOURCES}
${QC_UI_SOURCES}
${QC_UTIL_SOURCES}
${QC_MAIN_SOURCES}
${QC_GROUP_CONTROLLER}
${QC_PLANTFORM_SOURCES}
${QC_AUDIO_SOURCES}
)
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(QC_RUNTIME_TYPE MACOSX_BUNDLE)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
set(QC_RUNTIME_TYPE WIN32)
endif()
add_executable(${PROJECT_NAME} ${QC_RUNTIME_TYPE} ${QC_PROJECT_SOURCES})
# Log compile definitions
if(ENABLE_DETAILED_LOGS)
target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_DETAILED_LOGS)
endif()
#
# Internal include path (todo: remove this, use absolute path include)
#
target_include_directories(${PROJECT_NAME} PRIVATE fontawesome)
target_include_directories(${PROJECT_NAME} PRIVATE util)
target_include_directories(${PROJECT_NAME} PRIVATE uibase)
target_include_directories(${PROJECT_NAME} PRIVATE ui)
target_include_directories(${PROJECT_NAME} PRIVATE render)
# output dir
# https://cmake.org/cmake/help/latest/prop_gbl/GENERATOR_IS_MULTI_CONFIG.html
get_property(QC_IS_MUTIL_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
message(STATUS "multi config:" QC_IS_MUTIL_CONFIG)
# $<0:> 使用生成器表达式为每个config设置RUNTIME_OUTPUT_DIRECTORY,这样multi config就不会自动追加CMAKE_BUILD_TYPE子目录了
# 1. multi config介绍 https://cmake.org/cmake/help/latest/prop_gbl/GENERATOR_IS_MULTI_CONFIG.html
# 2. multi config在不用表达式生成器时自动追加子目录说明 https://cmake.org/cmake/help/latest/prop_tgt/RUNTIME_OUTPUT_DIRECTORY.html
# 3. 使用表达式生成器禁止multi config自动追加子目录解决方案 https://stackoverflow.com/questions/7747857/in-cmake-how-do-i-work-around-the-debug-and-release-directories-visual-studio-2
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../output/${QC_CPU_ARCH}/${CMAKE_BUILD_TYPE}/$<0:>"
)
#
# plantform deps
#
# windows
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.bat" "${QSC_BIN_OUTPUT_PATH}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}"
)
endif()
# MacOS
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
# qt6 need 10.15 or later
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15")
# copy bundle file
get_target_property(MACOS_BUNDLE_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
set(MACOS_BUNDLE_PATH ${MACOS_BUNDLE_PATH}/${PROJECT_NAME}.app/Contents)
set(QSC_DEPLOY_PATH ${MACOS_BUNDLE_PATH})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
# config file copy to Contents/MacOS/config
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../config/config.ini" "${MACOS_BUNDLE_PATH}/MacOS/config/config.ini"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${MACOS_BUNDLE_PATH}/MacOS"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${MACOS_BUNDLE_PATH}/MacOS"
)
# Step 2. ues MACOSX_PACKAGE_LOCATION copy icns to Resources
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.icns
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
)
# use MACOSX_BUNDLE_INFO_PLIST custom plist, not use MACOSX_BUNDLE_BUNDLE_NAME etc..
set(INFO_PLIST_TEMPLATE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/Info_Mac.plist.in")
set(INFO_PLIST_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/Info_Mac.plist")
file(READ "${INFO_PLIST_TEMPLATE_FILE}" plist_contents)
string(REPLACE "\${BUNDLE_VERSION}" "${PROJECT_VERSION}" plist_contents ${plist_contents})
file(WRITE ${INFO_PLIST_FILE} ${plist_contents})
set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE_INFO_PLIST "${INFO_PLIST_FILE}"
# "" disable code sign
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
)
# mac framework
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework AppKit")
endif()
# Linux
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${QSC_BIN_OUTPUT_PATH}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}"
)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE
# xcb https://doc.qt.io/qt-5/linux-requirements.html
xcb
# pthread
Threads::Threads
)
# linux set app icon: https://blog.csdn.net/MrNoboday/article/details/82870853
endif()
#
# common deps
#
add_subdirectory(QtScrcpyCore)
# Qt
target_link_libraries(${PROJECT_NAME} PRIVATE
${LINK_LIBS}
QtScrcpyCore
)
================================================
FILE: QtScrcpy/appversion
================================================
0.0.0
================================================
FILE: QtScrcpy/audio/audiooutput.cpp
================================================
#include <QAudioOutput>
#include <QCoreApplication>
#include <QElapsedTimer>
#include <QHostAddress>
#include <QTcpSocket>
#include <QTime>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QAudioSink>
#include <QAudioDevice>
#include <QMediaDevices>
#endif
#include "audiooutput.h"
AudioOutput::AudioOutput(QObject *parent)
: QObject(parent)
{
m_running = false;
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
m_audioOutput = nullptr;
#else
m_audioSink = nullptr;
#endif
connect(&m_sndcpy, &QProcess::readyReadStandardOutput, this, [this]() {
qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardOutput());
});
connect(&m_sndcpy, &QProcess::readyReadStandardError, this, [this]() {
qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardError());
});
}
AudioOutput::~AudioOutput()
{
if (QProcess::NotRunning != m_sndcpy.state()) {
m_sndcpy.kill();
}
stop();
}
bool AudioOutput::start(const QString& serial, int port)
{
if (m_running) {
stop();
}
QElapsedTimer timeConsumeCount;
timeConsumeCount.start();
bool ret = runSndcpyProcess(serial, port);
qInfo() << "AudioOutput::run sndcpy cost:" << timeConsumeCount.elapsed() << "milliseconds";
if (!ret) {
return ret;
}
startAudioOutput();
startRecvData(port);
m_running = true;
return true;
}
void AudioOutput::stop()
{
if (!m_running) {
return;
}
m_running = false;
stopRecvData();
stopAudioOutput();
}
void AudioOutput::installonly(const QString &serial, int port)
{
runSndcpyProcess(serial, port, false);
}
bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait)
{
if (QProcess::NotRunning != m_sndcpy.state()) {
m_sndcpy.kill();
}
#ifdef Q_OS_WIN32
QStringList params{serial, QString::number(port)};
m_sndcpy.start("sndcpy.bat", params);
#else
QStringList params{"sndcpy.sh", serial, QString::number(port)};
m_sndcpy.setWorkingDirectory(QCoreApplication::applicationDirPath());
m_sndcpy.start("bash", params);
#endif
if (!wait) {
return true;
}
if (!m_sndcpy.waitForStarted()) {
qWarning() << "AudioOutput::start sndcpy process failed";
return false;
}
if (!m_sndcpy.waitForFinished()) {
qWarning() << "AudioOutput::sndcpy process crashed";
return false;
}
return true;
}
void AudioOutput::startAudioOutput()
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
if (m_audioOutput) {
return;
}
QAudioFormat format;
format.setSampleRate(48000);
format.setChannelCount(2);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
if (!info.isFormatSupported(format)) {
qWarning() << "AudioOutput::audio format not supported, cannot play audio.";
return;
}
m_audioOutput = new QAudioOutput(format, this);
connect(m_audioOutput, &QAudioOutput::stateChanged, this, [](QAudio::State state) {
qInfo() << "AudioOutput::audio state changed:" << state;
});
m_audioOutput->setBufferSize(48000*2*15/1000 * 20);
m_outputDevice = m_audioOutput->start();
#else
if (m_audioSink) {
return;
}
QAudioFormat format;
format.setSampleRate(48000);
format.setChannelCount(2);
format.setSampleFormat(QAudioFormat::Int16);
QAudioDevice defaultDevice = QMediaDevices::defaultAudioOutput();
if (!defaultDevice.isFormatSupported(format)) {
qWarning() << "AudioOutput::audio format not supported, cannot play audio.";
return;
}
m_audioSink = new QAudioSink(defaultDevice, format, this);
m_outputDevice = m_audioSink->start();
if (!m_outputDevice) {
qWarning() << "AudioOutput::audio output device not available, cannot play audio.";
delete m_audioSink;
m_audioSink = nullptr;
return;
}
#endif
}
void AudioOutput::stopAudioOutput()
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
if (m_audioOutput) {
m_audioOutput->stop();
delete m_audioOutput;
m_audioOutput = nullptr;
}
#else
if (m_audioSink) {
m_audioSink->stop();
delete m_audioSink;
m_audioSink = nullptr;
}
#endif
m_outputDevice = nullptr;
}
void AudioOutput::startRecvData(int port)
{
if (m_workerThread.isRunning()) {
stopRecvData();
}
auto audioSocket = new QTcpSocket();
audioSocket->moveToThread(&m_workerThread);
connect(&m_workerThread, &QThread::finished, audioSocket, &QObject::deleteLater);
connect(this, &AudioOutput::connectTo, audioSocket, [audioSocket](int port) {
audioSocket->connectToHost(QHostAddress::LocalHost, port);
if (!audioSocket->waitForConnected(500)) {
qWarning("AudioOutput::audio socket connect failed");
return;
}
qInfo("AudioOutput::audio socket connect success");
});
connect(audioSocket, &QIODevice::readyRead, audioSocket, [this, audioSocket]() {
qint64 recv = audioSocket->bytesAvailable();
//qDebug() << "AudioOutput::recv data:" << recv;
if (!m_outputDevice) {
return;
}
if (m_buffer.capacity() < recv) {
m_buffer.reserve(recv);
}
qint64 count = audioSocket->read(m_buffer.data(), recv);
m_outputDevice->write(m_buffer.data(), count);
});
connect(audioSocket, &QTcpSocket::stateChanged, audioSocket, [](QAbstractSocket::SocketState state) {
qInfo() << "AudioOutput::audio socket state changed:" << state;
});
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) {
qInfo() << "AudioOutput::audio socket error occurred:" << error;
});
#else
connect(audioSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), audioSocket, [](QAbstractSocket::SocketError error) {
qInfo() << "AudioOutput::audio socket error occurred:" << error;
});
#endif
m_workerThread.start();
emit connectTo(port);
}
void AudioOutput::stopRecvData()
{
if (!m_workerThread.isRunning()) {
return;
}
m_workerThread.quit();
m_workerThread.wait();
}
================================================
FILE: QtScrcpy/audio/audiooutput.h
================================================
#ifndef AUDIOOUTPUT_H
#define AUDIOOUTPUT_H
#include <QThread>
#include <QProcess>
#include <QPointer>
#include <QVector>
class QAudioSink;
class QAudioOutput;
class QIODevice;
class AudioOutput : public QObject
{
Q_OBJECT
public:
explicit AudioOutput(QObject *parent = nullptr);
~AudioOutput();
bool start(const QString& serial, int port);
void stop();
void installonly(const QString& serial, int port);
private:
bool runSndcpyProcess(const QString& serial, int port, bool wait = true);
void startAudioOutput();
void stopAudioOutput();
void startRecvData(int port);
void stopRecvData();
signals:
void connectTo(int port);
private:
QPointer<QIODevice> m_outputDevice;
QThread m_workerThread;
QProcess m_sndcpy;
QVector<char> m_buffer;
bool m_running = false;
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QAudioOutput* m_audioOutput = nullptr;
#else
QAudioSink *m_audioSink = nullptr;
#endif
};
#endif // AUDIOOUTPUT_H
================================================
FILE: QtScrcpy/clang-format-all.sh
================================================
#!/bin/bash
#
# clang-format-all: a tool to run clang-format on an entire project
# Copyright (C) 2016 Evan Klitzke <evan@eklitzke.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
function usage {
echo "Usage: $0 DIR..."
exit 1
}
if [ $# -eq 0 ]; then
usage
fi
# Variable that will hold the name of the clang-format command
FMT=""
# Some distros just call it clang-format. Others (e.g. Ubuntu) are insistent
# that the version number be part of the command. We prefer clang-format if
# that's present, otherwise we work backwards from highest version to lowest
# version.
for clangfmt in clang-format{,-{4,3}.{9,8,7,6,5,4,3,2,1,0}}; do
if which "$clangfmt" &>/dev/null; then
FMT="$clangfmt"
break
fi
done
# Check if we found a working clang-format
if [ -z "$FMT" ]; then
echo "failed to find clang-format"
exit 1
fi
# Check all of the arguments first to make sure they're all directories
for dir in "$@"; do
if [ ! -d "${dir}" ]; then
echo "${dir} is not a directory"
usage
fi
done
# Find a dominating file, starting from a given directory and going up.
find-dominating-file() {
if [ -r "$1"/"$2" ]; then
return 0
fi
if [ "$1" = "/" ]; then
return 1
fi
find-dominating-file "$(realpath "$1"/..)" "$2"
return $?
}
# Run clang-format -i on all of the things
for dir in "$@"; do
pushd "${dir}" &>/dev/null
if ! find-dominating-file . .clang-format; then
echo "Failed to find dominating .clang-format starting at $PWD"
continue
fi
find . \
\( -name '*.c' \
-o -name '*.cc' \
-o -name '*.cpp' \
-o -name '*.h' \
-o -name '*.hh' \
-o -name '*.hpp' \) \
-exec "${FMT}" -i '{}' \;
popd &>/dev/null
done
================================================
FILE: QtScrcpy/fontawesome/iconhelper.cpp
================================================
#include "iconhelper.h"
IconHelper *IconHelper::_instance = 0;
IconHelper::IconHelper(QObject *) : QObject(qApp)
{
int fontId = QFontDatabase::addApplicationFont(":/font/fontawesome-webfont.ttf");
QString fontName = QFontDatabase::applicationFontFamilies(fontId).at(0);
iconFont = QFont(fontName);
}
void IconHelper::SetIcon(QLabel *lab, QChar c, int size)
{
iconFont.setPointSize(size);
lab->setFont(iconFont);
lab->setText(c);
}
void IconHelper::SetIcon(QPushButton *btn, QChar c, int size)
{
iconFont.setPointSize(size);
btn->setFont(iconFont);
btn->setText(c);
}
================================================
FILE: QtScrcpy/fontawesome/iconhelper.h
================================================
#ifndef ICONHELPER_H
#define ICONHELPER_H
#include <QApplication>
#include <QFont>
#include <QFontDatabase>
#include <QLabel>
#include <QMutex>
#include <QObject>
#include <QPushButton>
class IconHelper : public QObject
{
private:
explicit IconHelper(QObject *parent = 0);
QFont iconFont;
static IconHelper *_instance;
public:
static IconHelper *Instance()
{
static QMutex mutex;
if (!_instance) {
QMutexLocker locker(&mutex);
if (!_instance) {
_instance = new IconHelper;
}
}
return _instance;
}
void SetIcon(QLabel *lab, QChar c, int size = 10);
void SetIcon(QPushButton *btn, QChar c, int size = 10);
};
#endif // ICONHELPER_H
================================================
FILE: QtScrcpy/groupcontroller/groupcontroller.cpp
================================================
#include <QPointer>
#include "groupcontroller.h"
#include "videoform.h"
GroupController::GroupController(QObject *parent) : QObject(parent)
{
}
bool GroupController::isHost(const QString &serial)
{
auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();
if (!data) {
return true;
}
return static_cast<VideoForm*>(data)->isHost();
}
QSize GroupController::getFrameSize(const QString &serial)
{
auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();
if (!data) {
return QSize();
}
return static_cast<VideoForm*>(data)->frameSize();
}
GroupController &GroupController::instance()
{
static GroupController gc;
return gc;
}
void GroupController::updateDeviceState(const QString &serial)
{
if (!m_devices.contains(serial)) {
return;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
return;
}
if (isHost(serial)) {
device->registerDeviceObserver(this);
} else {
device->deRegisterDeviceObserver(this);
}
}
void GroupController::addDevice(const QString &serial)
{
if (m_devices.contains(serial)) {
return;
}
m_devices.append(serial);
}
void GroupController::removeDevice(const QString &serial)
{
if (!m_devices.contains(serial)) {
return;
}
m_devices.removeOne(serial);
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
return;
}
if (isHost(serial)) {
device->deRegisterDeviceObserver(this);
}
}
void GroupController::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize);
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->mouseEvent(from, getFrameSize(serial), showSize);
}
}
void GroupController::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize);
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->wheelEvent(from, getFrameSize(serial), showSize);
}
}
void GroupController::keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
{
Q_UNUSED(frameSize);
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->keyEvent(from, getFrameSize(serial), showSize);
}
}
void GroupController::postGoBack()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postGoBack();
}
}
void GroupController::postGoHome()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postGoHome();
}
}
void GroupController::postGoMenu()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postGoMenu();
}
}
void GroupController::postAppSwitch()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postAppSwitch();
}
}
void GroupController::postPower()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postPower();
}
}
void GroupController::postVolumeUp()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postVolumeUp();
}
}
void GroupController::postVolumeDown()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postVolumeDown();
}
}
void GroupController::postCopy()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postCopy();
}
}
void GroupController::postCut()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postCut();
}
}
void GroupController::setDisplayPower(bool on)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->setDisplayPower(on);
}
}
void GroupController::expandNotificationPanel()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->expandNotificationPanel();
}
}
void GroupController::collapsePanel()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->collapsePanel();
}
}
void GroupController::postBackOrScreenOn(bool down)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postBackOrScreenOn(down);
}
}
void GroupController::postTextInput(QString &text)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->postTextInput(text);
}
}
void GroupController::requestDeviceClipboard()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->requestDeviceClipboard();
}
}
void GroupController::setDeviceClipboard(bool pause)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->setDeviceClipboard(pause);
}
}
void GroupController::clipboardPaste()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->clipboardPaste();
}
}
void GroupController::pushFileRequest(const QString &file, const QString &devicePath)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->pushFileRequest(file, devicePath);
}
}
void GroupController::installApkRequest(const QString &apkFile)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->installApkRequest(apkFile);
}
}
void GroupController::screenshot()
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->screenshot();
}
}
void GroupController::showTouch(bool show)
{
for (const auto& serial : m_devices) {
if (true == isHost(serial)) {
continue;
}
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
continue;
}
device->showTouch(show);
}
}
================================================
FILE: QtScrcpy/groupcontroller/groupcontroller.h
================================================
#ifndef GROUPCONTROLLER_H
#define GROUPCONTROLLER_H
#include <QObject>
#include <QVector>
#include "QtScrcpyCore.h"
class GroupController : public QObject, public qsc::DeviceObserver
{
Q_OBJECT
public:
static GroupController& instance();
void updateDeviceState(const QString& serial);
void addDevice(const QString& serial);
void removeDevice(const QString& serial);
private:
// DeviceObserver
void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) override;
void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) override;
void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) override;
void postGoBack() override;
void postGoHome() override;
void postGoMenu() override;
void postAppSwitch() override;
void postPower() override;
void postVolumeUp() override;
void postVolumeDown() override;
void postCopy() override;
void postCut() override;
void setDisplayPower(bool on) override;
void expandNotificationPanel() override;
void collapsePanel() override;
void postBackOrScreenOn(bool down) override;
void postTextInput(QString &text) override;
void requestDeviceClipboard() override;
void setDeviceClipboard(bool pause = true) override;
void clipboardPaste() override;
void pushFileRequest(const QString &file, const QString &devicePath = "") override;
void installApkRequest(const QString &apkFile) override;
void screenshot() override;
void showTouch(bool show) override;
private:
explicit GroupController(QObject *parent = nullptr);
bool isHost(const QString& serial);
QSize getFrameSize(const QString& serial);
private:
QVector<QString> m_devices;
};
#endif // GROUPCONTROLLER_H
================================================
FILE: QtScrcpy/main.cpp
================================================
#include <QApplication>
#include <QDebug>
#include <QFile>
#ifdef Q_OS_LINUX
#include <QFileInfo>
#include <QIcon>
#endif
#include <QSurfaceFormat>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTranslator>
#include <QDateTime>
#include "config.h"
#include "dialog.h"
#include "mousetap/mousetap.h"
static Dialog *g_mainDlg = Q_NULLPTR;
static QtMessageHandler g_oldMessageHandler = Q_NULLPTR;
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
void installTranslator();
static QtMsgType g_msgType = QtInfoMsg;
QtMsgType covertLogLevel(const QString &logLevel);
int main(int argc, char *argv[])
{
// set env
#ifdef Q_OS_WIN32
qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/win/adb.exe");
qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
#endif
#ifdef Q_OS_OSX
qputenv("QTSCRCPY_ADB_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/mac/adb");
qputenv("QTSCRCPY_SERVER_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../../../keymap");
qputenv("QTSCRCPY_CONFIG_PATH", "../../../../../../config");
#endif
#ifdef Q_OS_LINUX
// Only set environment variables if they are not already set (e.g., by AppImage AppRun)
if (qgetenv("QTSCRCPY_ADB_PATH").isEmpty()) {
qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb");
}
if (qgetenv("QTSCRCPY_SERVER_PATH").isEmpty()) {
qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
}
if (qgetenv("QTSCRCPY_KEYMAP_PATH").isEmpty()) {
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
}
if (qgetenv("QTSCRCPY_CONFIG_PATH").isEmpty()) {
qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
}
#endif
g_msgType = covertLogLevel(Config::getInstance().getLogLevel());
// set on QApplication before
// bug: config path is error on mac
int opengl = Config::getInstance().getDesktopOpenGL();
if (0 == opengl) {
QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
} else if (1 == opengl) {
QApplication::setAttribute(Qt::AA_UseOpenGLES);
} else if (2 == opengl) {
QApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
}
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
#endif
QSurfaceFormat varFormat = QSurfaceFormat::defaultFormat();
varFormat.setVersion(2, 0);
varFormat.setProfile(QSurfaceFormat::NoProfile);
/*
varFormat.setSamples(4);
varFormat.setAlphaBufferSize(8);
varFormat.setBlueBufferSize(8);
varFormat.setRedBufferSize(8);
varFormat.setGreenBufferSize(8);
varFormat.setDepthBufferSize(24);
*/
QSurfaceFormat::setDefaultFormat(varFormat);
g_oldMessageHandler = qInstallMessageHandler(myMessageOutput);
QApplication a(argc, argv);
// Set application icon for Linux (taskbar icon)
#ifdef Q_OS_LINUX
// Load icon from Qt resource (logo.png is included in res.qrc)
QIcon appIcon(":/image/tray/logo.png");
if (!appIcon.isNull()) {
a.setWindowIcon(appIcon);
}
#endif
// windows下通过qmake VERSION变量或者rc设置版本号和应用名称后,这里可以直接拿到
// mac下拿到的是CFBundleVersion的值
qDebug() << a.applicationVersion();
qDebug() << a.applicationName();
//update version
QStringList versionList = QCoreApplication::applicationVersion().split(".");
if (versionList.size() >= 3) {
QString version = versionList[0] + "." + versionList[1] + "." + versionList[2];
a.setApplicationVersion(version);
}
installTranslator();
#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)
MouseTap::getInstance()->initMouseEventTap();
#endif
// load style sheet
QFile file(":/qss/psblack.css");
if (file.open(QFile::ReadOnly)) {
QString qss = QLatin1String(file.readAll());
QString paletteColor = qss.mid(20, 7);
qApp->setPalette(QPalette(QColor(paletteColor)));
qApp->setStyleSheet(qss);
file.close();
}
qsc::AdbProcess::setAdbPath(Config::getInstance().getAdbPath());
g_mainDlg = new Dialog {};
g_mainDlg->show();
qInfo() << QObject::tr("This software is completely open source and free. Use it at your own risk. You can download it at the "
"following address:");
qInfo() << QString("QtScrcpy %1 <https://github.com/barry-ran/QtScrcpy>").arg(QCoreApplication::applicationVersion());
qInfo() << QObject::tr("If you need more professional batch control mirror software, you can try the following software:");
qInfo() << QString(QObject::tr("QuickMirror") + " <https://lrbnfell4p.feishu.cn/drive/folder/KviYfz5uFlpUT8dXgdjccmfUnse>");
qInfo() << QObject::tr("If you need more professional game keymap mirror software, you can try the following software:");
qInfo() << QString(QObject::tr("QuickAssistant") + " <https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh>");
qInfo() << QObject::tr("If you need more professional PC remote software, you can try the following software:");
qInfo() << QString(QObject::tr("QuickDesk") + " <https://github.com/barry-ran/QuickDesk>");
qInfo() << QObject::tr("You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1>");
int ret = a.exec();
delete g_mainDlg;
#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)
MouseTap::getInstance()->quitMouseEventTap();
#endif
return ret;
}
void installTranslator()
{
static QTranslator translator;
QLocale locale;
QLocale::Language language = locale.language();
if (Config::getInstance().getLanguage() == "zh_CN") {
language = QLocale::Chinese;
} else if (Config::getInstance().getLanguage() == "en_US") {
language = QLocale::English;
} else if (Config::getInstance().getLanguage() == "ja_JP") {
language = QLocale::Japanese;
}
QString languagePath = ":/i18n/";
switch (language) {
case QLocale::Chinese:
languagePath += "zh_CN.qm";
break;
case QLocale::Japanese:
languagePath += "ja_JP.qm";
break;
case QLocale::English:
default:
languagePath += "en_US.qm";
break;
}
auto loaded = translator.load(languagePath);
if (!loaded) {
qWarning() << "Failed to load translation file:" << languagePath;
}
qApp->installTranslator(&translator);
}
QtMsgType covertLogLevel(const QString &logLevel)
{
if ("debug" == logLevel) {
return QtDebugMsg;
}
if ("info" == logLevel) {
return QtInfoMsg;
}
if ("warn" == logLevel) {
return QtWarningMsg;
}
if ("error" == logLevel) {
return QtCriticalMsg;
}
#ifdef QT_NO_DEBUG
return QtInfoMsg;
#else
return QtDebugMsg;
#endif
}
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QString outputMsg;
#ifdef ENABLE_DETAILED_LOGS
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
if (context.file && context.line > 0) {
QString fileName = QString::fromUtf8(context.file);
int lastSlash = fileName.lastIndexOf('/');
if (lastSlash >= 0) {
fileName = fileName.mid(lastSlash + 1);
}
lastSlash = fileName.lastIndexOf('\\');
if (lastSlash >= 0) {
fileName = fileName.mid(lastSlash + 1);
}
outputMsg = QString("[ %1 %2: %3 ] %4").arg(timestamp).arg(fileName).arg(context.line).arg(msg);
} else {
outputMsg = QString("[%1] %2").arg(timestamp).arg(msg);
}
switch (type) {
case QtDebugMsg:
outputMsg.prepend("[debug] ");
break;
case QtInfoMsg:
outputMsg.prepend("[info] ");
break;
case QtWarningMsg:
outputMsg.prepend("[warring] ");
break;
case QtCriticalMsg:
outputMsg.prepend("[critical] ");
break;
case QtFatalMsg:
outputMsg.prepend("[fatal] ");
break;
}
fprintf(stderr, "%s\n", outputMsg.toUtf8().constData());
#else
outputMsg = msg;
if (g_oldMessageHandler) {
g_oldMessageHandler(type, context, outputMsg);
}
#endif
// Is Qt log level higher than warning?
float fLogLevel = g_msgType;
if (QtInfoMsg == g_msgType) {
fLogLevel = QtDebugMsg + 0.5f;
}
float fLogLevel2 = type;
if (QtInfoMsg == type) {
fLogLevel2 = QtDebugMsg + 0.5f;
}
if (fLogLevel <= fLogLevel2) {
if (g_mainDlg && g_mainDlg->isVisible() && !g_mainDlg->filterLog(outputMsg)) {
g_mainDlg->outLog(outputMsg);
}
}
if (QtFatalMsg == type) {
//abort();
}
}
================================================
FILE: QtScrcpy/render/qyuvopenglwidget.cpp
================================================
#include <QCoreApplication>
#include <QOpenGLTexture>
#include <QSurfaceFormat>
#include "qyuvopenglwidget.h"
// 存储顶点坐标和纹理坐标
// 存在一起缓存在vbo
// 使用glVertexAttribPointer指定访问方式即可
static const GLfloat coordinate[] = {
// 顶点坐标,存储4个xyz坐标
// 坐标范围为[-1,1],中心点为 0,0
// 二维图像z始终为0
// GL_TRIANGLE_STRIP的绘制方式:
// 使用前3个坐标绘制一个三角形,使用后三个坐标绘制一个三角形,正好为一个矩形
// x y z
-1.0f,
-1.0f,
0.0f,
1.0f,
-1.0f,
0.0f,
-1.0f,
1.0f,
0.0f,
1.0f,
1.0f,
0.0f,
// 纹理坐标,存储4个xy坐标
// 坐标范围为[0,1],左下角为 0,0
0.0f,
1.0f,
1.0f,
1.0f,
0.0f,
0.0f,
1.0f,
0.0f
};
// 顶点着色器
static const QString s_vertShader = R"(
attribute vec3 vertexIn; // xyz顶点坐标
attribute vec2 textureIn; // xy纹理坐标
varying vec2 textureOut; // 传递给片段着色器的纹理坐标
void main(void)
{
gl_Position = vec4(vertexIn, 1.0); // 1.0表示vertexIn是一个顶点位置
textureOut = textureIn; // 纹理坐标直接传递给片段着色器
}
)";
// 片段着色器
static QString s_fragShader = R"(
varying vec2 textureOut; // 由顶点着色器传递过来的纹理坐标
uniform sampler2D textureY; // uniform 纹理单元,利用纹理单元可以使用多个纹理
uniform sampler2D textureU; // sampler2D是2D采样器
uniform sampler2D textureV; // 声明yuv三个纹理单元
void main(void)
{
vec3 yuv;
vec3 rgb;
// SDL2 BT709_SHADER_CONSTANTS
// https://github.com/spurious/SDL-mirror/blob/4ddd4c445aa059bb127e101b74a8c5b59257fbe2/src/render/opengl/SDL_shaders_gl.c#L102
const vec3 Rcoeff = vec3(1.1644, 0.000, 1.7927);
const vec3 Gcoeff = vec3(1.1644, -0.2132, -0.5329);
const vec3 Bcoeff = vec3(1.1644, 2.1124, 0.000);
// 根据指定的纹理textureY和坐标textureOut来采样
yuv.x = texture2D(textureY, textureOut).r;
yuv.y = texture2D(textureU, textureOut).r - 0.5;
yuv.z = texture2D(textureV, textureOut).r - 0.5;
// 采样完转为rgb
// 减少一些亮度
yuv.x = yuv.x - 0.0625;
rgb.r = dot(yuv, Rcoeff);
rgb.g = dot(yuv, Gcoeff);
rgb.b = dot(yuv, Bcoeff);
// 输出颜色值
gl_FragColor = vec4(rgb, 1.0);
}
)";
QYUVOpenGLWidget::QYUVOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
/*
QSurfaceFormat format = QSurfaceFormat::defaultFormat();
format.setColorSpace(QSurfaceFormat::sRGBColorSpace);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setMajorVersion(3);
format.setMinorVersion(2);
QSurfaceFormat::setDefaultFormat(format);
*/
}
QYUVOpenGLWidget::~QYUVOpenGLWidget()
{
makeCurrent();
m_vbo.destroy();
deInitTextures();
doneCurrent();
}
QSize QYUVOpenGLWidget::minimumSizeHint() const
{
return QSize(50, 50);
}
QSize QYUVOpenGLWidget::sizeHint() const
{
return size();
}
void QYUVOpenGLWidget::setFrameSize(const QSize &frameSize)
{
if (m_frameSize != frameSize) {
m_frameSize = frameSize;
m_needUpdate = true;
// inittexture immediately
repaint();
}
}
const QSize &QYUVOpenGLWidget::frameSize()
{
return m_frameSize;
}
void QYUVOpenGLWidget::updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV)
{
if (m_textureInited) {
updateTexture(m_texture[0], 0, dataY, linesizeY);
updateTexture(m_texture[1], 1, dataU, linesizeU);
updateTexture(m_texture[2], 2, dataV, linesizeV);
update();
}
}
void QYUVOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glDisable(GL_DEPTH_TEST);
// 顶点缓冲对象初始化
m_vbo.create();
m_vbo.bind();
m_vbo.allocate(coordinate, sizeof(coordinate));
initShader();
// 设置背景清理色为黑色
glClearColor(0.0, 0.0, 0.0, 0.0);
// 清理颜色背景
glClear(GL_COLOR_BUFFER_BIT);
}
void QYUVOpenGLWidget::paintGL()
{
m_shaderProgram.bind();
if (m_needUpdate) {
deInitTextures();
initTextures();
m_needUpdate = false;
}
if (m_textureInited) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_texture[0]);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_texture[1]);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, m_texture[2]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
m_shaderProgram.release();
}
void QYUVOpenGLWidget::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
repaint();
}
void QYUVOpenGLWidget::initShader()
{
// opengles的float、int等要手动指定精度
if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES)) {
s_fragShader.prepend(R"(
precision mediump int;
precision mediump float;
)");
}
m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, s_vertShader);
m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, s_fragShader);
m_shaderProgram.link();
m_shaderProgram.bind();
// 指定顶点坐标在vbo中的访问方式
// 参数解释:顶点坐标在shader中的参数名称,顶点坐标为float,起始偏移为0,顶点坐标类型为vec3,步幅为3个float
m_shaderProgram.setAttributeBuffer("vertexIn", GL_FLOAT, 0, 3, 3 * sizeof(float));
// 启用顶点属性
m_shaderProgram.enableAttributeArray("vertexIn");
// 指定纹理坐标在vbo中的访问方式
// 参数解释:纹理坐标在shader中的参数名称,纹理坐标为float,起始偏移为12个float(跳过前面存储的12个顶点坐标),纹理坐标类型为vec2,步幅为2个float
m_shaderProgram.setAttributeBuffer("textureIn", GL_FLOAT, 12 * sizeof(float), 2, 2 * sizeof(float));
m_shaderProgram.enableAttributeArray("textureIn");
// 关联片段着色器中的纹理单元和opengl中的纹理单元(opengl一般提供16个纹理单元)
m_shaderProgram.setUniformValue("textureY", 0);
m_shaderProgram.setUniformValue("textureU", 1);
m_shaderProgram.setUniformValue("textureV", 2);
}
void QYUVOpenGLWidget::initTextures()
{
// 创建纹理
glGenTextures(1, &m_texture[0]);
glBindTexture(GL_TEXTURE_2D, m_texture[0]);
// 设置纹理缩放时的策略
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 设置st方向上纹理超出坐标时的显示策略
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width(), m_frameSize.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
glGenTextures(1, &m_texture[1]);
glBindTexture(GL_TEXTURE_2D, m_texture[1]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width() / 2, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
glGenTextures(1, &m_texture[2]);
glBindTexture(GL_TEXTURE_2D, m_texture[2]);
// 设置纹理缩放时的策略
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 设置st方向上纹理超出坐标时的显示策略
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width() / 2, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
m_textureInited = true;
}
void QYUVOpenGLWidget::deInitTextures()
{
if (QOpenGLFunctions::isInitialized(QOpenGLFunctions::d_ptr)) {
glDeleteTextures(3, m_texture);
}
memset(m_texture, 0, sizeof(m_texture));
m_textureInited = false;
}
void QYUVOpenGLWidget::updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride)
{
if (!pixels)
return;
QSize size = 0 == textureType ? m_frameSize : m_frameSize / 2;
makeCurrent();
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.width(), size.height(), GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels);
doneCurrent();
}
================================================
FILE: QtScrcpy/render/qyuvopenglwidget.h
================================================
#ifndef QYUVOPENGLWIDGET_H
#define QYUVOPENGLWIDGET_H
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLWidget>
class QYUVOpenGLWidget
: public QOpenGLWidget
, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit QYUVOpenGLWidget(QWidget *parent = nullptr);
virtual ~QYUVOpenGLWidget() override;
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
void setFrameSize(const QSize &frameSize);
const QSize &frameSize();
void updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV);
protected:
void initializeGL() override;
void paintGL() override;
void resizeGL(int width, int height) override;
private:
void initShader();
void initTextures();
void deInitTextures();
void updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride);
private:
// 视频帧尺寸
QSize m_frameSize = { -1, -1 };
bool m_needUpdate = false;
bool m_textureInited = false;
// 顶点缓冲对象(Vertex Buffer Objects, VBO):默认即为VertexBuffer(GL_ARRAY_BUFFER)类型
QOpenGLBuffer m_vbo;
// 着色器程序:编译链接着色器
QOpenGLShaderProgram m_shaderProgram;
// YUV纹理,用于生成纹理贴图
GLuint m_texture[3] = { 0 };
};
#endif // QYUVOPENGLWIDGET_H
================================================
FILE: QtScrcpy/res/Info_Mac.plist.in
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>zh-Hans</string>
<key>CFBundleExecutable</key>
<string>QtScrcpy</string>
<key>CFBundleGetInfoString</key>
<string>Created by rankun</string>
<key>CFBundleIconFile</key>
<string>QtScrcpy</string>
<key>CFBundleIdentifier</key>
<string>rankun.QtScrcpy</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>QtScrcpy</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${BUNDLE_VERSION}</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>${BUNDLE_VERSION}</string>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
<key>NSAppleEventsUsageDescription</key>
<string></string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018-2038 rankun. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>
================================================
FILE: QtScrcpy/res/QtScrcpy.rc
================================================
#include "winres.h"
IDI_ICON1 ICON "QtScrcpy.ico"
// GB2312编码的话,在中文系统上打包FileDescription可以显示中文
// 在github action(英文系统)打包后FileDescription是乱码,utf8编码也不行。。
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH
PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "080404b0"
BEGIN
VALUE "CompanyName", "RanKun"
VALUE "FileDescription", "Android real-time display control software"
VALUE "FileVersion", VERSION_RC_STR
VALUE "LegalCopyright", "Copyright (C) RanKun 2018-2038. All rights reserved."
VALUE "ProductName", "QtScrcpy"
VALUE "ProductVersion", VERSION_RC_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x804, 1200
END
END
================================================
FILE: QtScrcpy/res/i18n/CMakeLists.txt
================================================
# 声明ts文件
set(QC_TS_FILES
${CMAKE_CURRENT_SOURCE_DIR}/zh_CN.ts
${CMAKE_CURRENT_SOURCE_DIR}/en_US.ts
${CMAKE_CURRENT_SOURCE_DIR}/ja_JP.ts
)
# 设置qm文件生成目录
set_source_files_properties(${QC_TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}")
# 引入LinguistTools
find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools REQUIRED)
# qt5_create_translation会依次执行 lupdate更新ts、lrelease更新qm
qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../.. ${QC_TS_FILES})
# 自定义目标依赖QM_FILES,否则不会生成qm文件
add_custom_target(QC_QM_GENERATOR DEPENDS ${QM_FILES})
# qt5_create_translation的bug:cmake clean的时候会删除翻译好的ts文件,导致翻译丢失
# (qt官方说qt6没问题,只用qt6的可以考虑qt5_create_translation)
# 网上查到的CLEAN_NO_CUSTOM办法只能在makefile生成器下生效,解决不了问题
# https://cmake.org/cmake/help/latest/prop_dir/CLEAN_NO_CUSTOM.html
# set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM true)
# 目前唯一的解决办法是每次clean后,都手动在git中恢复一下ts文件
#[[
总结:
cmake qt项目下,利用cmake脚本有三种方式处理翻译:
1. 完全使用qt自带的cmake LinguistTools脚本:qt5_create_translation&qt5_add_translation
这两个脚本都满足不了需求:
qt5_add_translation只能根据已有ts文件生成qm文件(lrelease),不能更新ts文件(lupdate)
qt5_create_translation在cmake clean的时候会删除翻译好的ts文件,导致翻译丢失
2. cmake add_custom_command + cmake LinguistTools脚本(其实qt5_create_translation内部使用的也是add_custom_command)
例如add_custom_command执行lupdate,配合qt5_add_translation更新qm,
参考:https://github.com/maratnek/QtFirstProgrammCMake/blob/2c93b59e2ba85ff6ee0e727487e14003381687d3/CMakeLists.txt
3. 完全使用cmake命令来执行lupdate和lrelease
例如add_custom_command/add_custom_target/execute_process都可以实现执行lupdate和lrelease命令
上面3个方案都有一个共同问题:就是翻译文件处理都是和编译绑定在一起的,每次编译都会检测执行,实际的翻译工作是所有
编程工作都完成以后,统一执行一次lupdate、翻译、lrelease就可以了,不应该和编译绑定在一起
所以写两个shell脚本lupdate.sh和lrelease.sh来处理比较合适,其实非常简单:
1. 更新ts:lupdate -no-obsolete ./QtScrcpy -ts ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts
2. 手动翻译ts
3. 发布:lrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts
参考文档
1. qt知道qt5_create_translation的bug,但是不肯解决,只确定了qt6没问题 https://bugreports.qt.io/browse/QTBUG-96549
2. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-add-translation.html
3. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-create-translation.html
4. execute_process 参考:https://blog.csdn.net/u010255072/article/details/120326833
5. add_custom_target 参考:https://www.cnblogs.com/apocelipes/p/14355460.html
================================================
FILE: QtScrcpy/res/i18n/en_US.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>Dialog</name>
<message>
<source>show</source>
<translation>show</translation>
</message>
<message>
<source>quit</source>
<translation>quit</translation>
</message>
<message>
<source>original</source>
<translation>original</translation>
</message>
<message>
<source>no lock</source>
<translation>no lock</translation>
</message>
<message>
<source>Notice</source>
<translation>Notice</translation>
</message>
<message>
<source>Hidden here!</source>
<translation>Hidden here!</translation>
</message>
<message>
<source>select path</source>
<translation>select path</translation>
</message>
<message>
<source>Clear History</source>
<translation>Clear History</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</translation>
</message>
<message>
<source>QuickMirror</source>
<translation>QuickMirror</translation>
</message>
<message>
<source>If you need more professional batch control mirror software, you can try the following software:</source>
<translation>If you need more professional batch control mirror software, you can try the following software:</translation>
</message>
<message>
<source>If you need more professional game keymap mirror software, you can try the following software:</source>
<translation>If you need more professional game keymap mirror software, you can try the following software:</translation>
</message>
<message>
<source>QuickAssistant</source>
<translation>QuickAssistant</translation>
</message>
<message>
<source>You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1></source>
<translation>You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1></translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation>Tool</translation>
</message>
<message>
<source>full screen</source>
<translation>full screen</translation>
</message>
<message>
<source>expand notify</source>
<translation>expand notify</translation>
</message>
<message>
<source>touch switch</source>
<translation>touch switch</translation>
</message>
<message>
<source>close screen</source>
<translation>close screen</translation>
</message>
<message>
<source>power</source>
<translation>power</translation>
</message>
<message>
<source>volume up</source>
<translation>volume up</translation>
</message>
<message>
<source>volume down</source>
<translation>volume down</translation>
</message>
<message>
<source>app switch</source>
<translation>app switch</translation>
</message>
<message>
<source>menu</source>
<translation>menu</translation>
</message>
<message>
<source>home</source>
<translation>home</translation>
</message>
<message>
<source>return</source>
<translation>return</translation>
</message>
<message>
<source>screen shot</source>
<translation>screen shot</translation>
</message>
<message>
<source>open screen</source>
<translation>open screen</translation>
</message>
<message>
<source>group control</source>
<translation>group control</translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation>file does not exist</translation>
</message>
</context>
<context>
<name>Widget</name>
<message>
<source>Wireless</source>
<translation>Wireless</translation>
</message>
<message>
<source>wireless connect</source>
<translation>wireless connect</translation>
</message>
<message>
<source>wireless disconnect</source>
<translation>wireless disconnect</translation>
</message>
<message>
<source>Start Config</source>
<translation>Start Config</translation>
</message>
<message>
<source>select path</source>
<translation>select path</translation>
</message>
<message>
<source>record format:</source>
<translation>record format:</translation>
</message>
<message>
<source>record screen</source>
<translation>record screen</translation>
</message>
<message>
<source>frameless</source>
<translation>frameless</translation>
</message>
<message>
<source>Use Simple Mode</source>
<translatorcomment>Use Simple Mode</translatorcomment>
<translation>Use Simple Mode</translation>
</message>
<message>
<source>Simple Mode</source>
<translatorcomment>Simple Mode</translatorcomment>
<translation>Simple Mode</translation>
</message>
<message>
<source>WIFI Connect</source>
<translatorcomment>WIFI Connect</translatorcomment>
<translation>WIFI Connect</translation>
</message>
<message>
<source>USB Connect</source>
<translatorcomment>USB Connect</translatorcomment>
<translation>USB Connect</translation>
</message>
<message>
<source>Double click to connect:</source>
<translation>Double click to connect:</translation>
</message>
<message>
<source>lock orientation:</source>
<translation>lock orientation:</translation>
</message>
<message>
<source>show fps</source>
<translation>show fps</translation>
</message>
<message>
<source>stay awake</source>
<translation>stay awake</translation>
</message>
<message>
<source>device name:</source>
<translatorcomment>device name:</translatorcomment>
<translation>device name:</translation>
</message>
<message>
<source>update name</source>
<translatorcomment>update name</translatorcomment>
<translation>update name</translation>
</message>
<message>
<source>stop all server</source>
<translation>stop all server</translation>
</message>
<message>
<source>adb command:</source>
<translation>adb command:</translation>
</message>
<message>
<source>terminate</source>
<translation>terminate</translation>
</message>
<message>
<source>execute</source>
<translation>execute</translation>
</message>
<message>
<source>clear</source>
<translation>clear</translation>
</message>
<message>
<source>reverse connection</source>
<translation>reverse connection</translation>
</message>
<message>
<source>background record</source>
<translation>background record</translation>
</message>
<message>
<source>screen-off</source>
<translation>screen-off</translation>
</message>
<message>
<source>apply</source>
<translation>apply</translation>
</message>
<message>
<source>max size:</source>
<translation>max size:</translation>
</message>
<message>
<source>always on top</source>
<translation>always on top</translation>
</message>
<message>
<source>refresh script</source>
<translation>refresh script</translation>
</message>
<message>
<source>get device IP</source>
<translation>get device IP</translation>
</message>
<message>
<source>USB line</source>
<translation>USB line</translation>
</message>
<message>
<source>stop server</source>
<translation>stop server</translation>
</message>
<message>
<source>start server</source>
<translation>start server</translation>
</message>
<message>
<source>device serial:</source>
<translation>device serial:</translation>
</message>
<message>
<source>bit rate:</source>
<translation>bit rate:</translation>
</message>
<message>
<source>start adbd</source>
<translation>start adbd</translation>
</message>
<message>
<source>refresh devices</source>
<translation>refresh devices</translation>
</message>
<message>
<source>install sndcpy</source>
<translation>install sndcpy</translation>
</message>
<message>
<source>start audio</source>
<translation>start audio</translation>
</message>
<message>
<source>stop audio</source>
<translation>stop audio</translation>
</message>
<message>
<source>auto update</source>
<translation>auto update</translation>
</message>
<message>
<source>show toolbar</source>
<translation>show toolbar</translation>
</message>
<message>
<source>record save path:</source>
<translation>record save path:</translation>
</message>
</context>
</TS>
================================================
FILE: QtScrcpy/res/i18n/ja_JP.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS []>
<TS version="2.1" language="ja_JP">
<context>
<name>Dialog</name>
<message>
<source>show</source>
<translation>表示</translation>
</message>
<message>
<source>quit</source>
<translation>終了</translation>
</message>
<message>
<source>original</source>
<translation>オリジナル</translation>
</message>
<message>
<source>no lock</source>
<translation>ロックなし</translation>
</message>
<message>
<source>Notice</source>
<translation>お知らせ</translation>
</message>
<message>
<source>Hidden here!</source>
<translation>ここに隠れています!</translation>
</message>
<message>
<source>select path</source>
<translation>パスを選択</translation>
</message>
<message>
<source>Clear History</source>
<translation>履歴を消去</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>このソフトウェアはオープンソースで完全無料です。自己責任でご利用ください。以下のアドレスからダウンロードできます:</translation>
</message>
<message>
<source>QuickMirror</source>
<translation>クイックミラー</translation>
</message>
<message>
<source>If you need more professional batch control mirror software, you can try the following software:</source>
<translation>より高度なバッチ制御が可能なミラーソフトウェアが必要な場合は、次のソフトウェアをお試しください:</translation>
</message>
<message>
<source>If you need more professional game keymap mirror software, you can try the following software:</source>
<translation>より高度なゲームキーマップが可能なミラーソフトウェアが必要な場合は、次のソフトウェアをお試しください:</translation>
</message>
<message>
<source>QuickAssistant</source>
<translation>クイックアシスタント</translation>
</message>
<message>
<source>You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1></source>
<translation>Telegram で連絡ができます <https://t.me/+Ylf_5V_rDCMyODQ1></translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation>ツール</translation>
</message>
<message>
<source>full screen</source>
<translation>フルスクリーン</translation>
</message>
<message>
<source>expand notify</source>
<translation>通知を展開</translation>
</message>
<message>
<source>touch switch</source>
<translation>タッチ切り替え</translation>
</message>
<message>
<source>close screen</source>
<translation>画面を閉じる</translation>
</message>
<message>
<source>power</source>
<translation>電源</translation>
</message>
<message>
<source>volume up</source>
<translation>音量を上げる</translation>
</message>
<message>
<source>volume down</source>
<translation>音量を下げる</translation>
</message>
<message>
<source>app switch</source>
<translation>アプリを切り替え</translation>
</message>
<message>
<source>menu</source>
<translation>メニュー</translation>
</message>
<message>
<source>home</source>
<translation>ホーム</translation>
</message>
<message>
<source>return</source>
<translation>戻る</translation>
</message>
<message>
<source>screen shot</source>
<translation>スクリーンショット</translation>
</message>
<message>
<source>open screen</source>
<translation>画面を開く</translation>
</message>
<message>
<source>group control</source>
<translation>グループコントロール</translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation>ファイルが存在しません</translation>
</message>
</context>
<context>
<name>Widget</name>
<message>
<source>Wireless</source>
<translation>ワイヤレス</translation>
</message>
<message>
<source>wireless connect</source>
<translation>ワイヤレスで接続</translation>
</message>
<message>
<source>wireless disconnect</source>
<translation>ワイヤレスを切断</translation>
</message>
<message>
<source>Start Config</source>
<translation>構成を開始</translation>
</message>
<message>
<source>select path</source>
<translation>パスを選択</translation>
</message>
<message>
<source>record format:</source>
<translation>録画の形式:</translation>
</message>
<message>
<source>record screen</source>
<translation>画面を録画</translation>
</message>
<message>
<source>frameless</source>
<translation>フレームレス</translation>
</message>
<message>
<source>Use Simple Mode</source>
<translatorcomment>シンプルモードを使用する</translatorcomment>
<translation>シンプルモードを使用する</translation>
</message>
<message>
<source>Simple Mode</source>
<translatorcomment>シンプルモード</translatorcomment>
<translation>シンプルモード</translation>
</message>
<message>
<source>WIFI Connect</source>
<translatorcomment>Wi-Fi 接続</translatorcomment>
<translation>Wi-Fi 接続</translation>
</message>
<message>
<source>USB Connect</source>
<translatorcomment>USB 接続</translatorcomment>
<translation>USB 接続</translation>
</message>
<message>
<source>Double click to connect:</source>
<translation>ダブルクリックで接続:</translation>
</message>
<message>
<source>lock orientation:</source>
<translation>画面方向をロック:</translation>
</message>
<message>
<source>show fps</source>
<translation>FPS を表示</translation>
</message>
<message>
<source>stay awake</source>
<translation>画面を常時点灯</translation>
</message>
<message>
<source>device name:</source>
<translatorcomment>デバイス名:</translatorcomment>
<translation>デバイス名:</translation>
</message>
<message>
<source>update name</source>
<translatorcomment>更新名</translatorcomment>
<translation>更新名</translation>
</message>
<message>
<source>stop all server</source>
<translation>すべてのサーバーを停止</translation>
</message>
<message>
<source>adb command:</source>
<translation>adb コマンド:</translation>
</message>
<message>
<source>terminate</source>
<translation>停止</translation>
</message>
<message>
<source>execute</source>
<translation>実行</translation>
</message>
<message>
<source>clear</source>
<translation>消去</translation>
</message>
<message>
<source>reverse connection</source>
<translation>リバース接続</translation>
</message>
<message>
<source>background record</source>
<translation>バックグラウンド録画</translation>
</message>
<message>
<source>screen-off</source>
<translation>画面を OFF</translation>
</message>
<message>
<source>apply</source>
<translation>適用</translation>
</message>
<message>
<source>max size:</source>
<translation>最大サイズ:</translation>
</message>
<message>
<source>always on top</source>
<translation>常に手前に表示</translation>
</message>
<message>
<source>refresh script</source>
<translation>スクリプトを更新</translation>
</message>
<message>
<source>get device IP</source>
<translation>デバイス IP を取得</translation>
</message>
<message>
<source>USB line</source>
<translation>USB ライン</translation>
</message>
<message>
<source>stop server</source>
<translation>サーバーを停止</translation>
</message>
<message>
<source>start server</source>
<translation>サーバーを開始</translation>
</message>
<message>
<source>device serial:</source>
<translation>デバイスシリアル:</translation>
</message>
<message>
<source>bit rate:</source>
<translation>ビットレート:</translation>
</message>
<message>
<source>start adbd</source>
<translation>adbd を開始</translation>
</message>
<message>
<source>refresh devices</source>
<translation>デバイスを更新</translation>
</message>
<message>
<source>install sndcpy</source>
<translation>Sndcpy をインストール</translation>
</message>
<message>
<source>start audio</source>
<translation>オーディオを開始</translation>
</message>
<message>
<source>stop audio</source>
<translation>オーディオを停止</translation>
</message>
<message>
<source>auto update</source>
<translation>自動更新</translation>
</message>
<message>
<source>show toolbar</source>
<translation>ツールバーを表示</translation>
</message>
<message>
<source>record save path:</source>
<translation>録画の保存先:</translation>
</message>
</context>
</TS>
================================================
FILE: QtScrcpy/res/i18n/ko_KR.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ko">
<context>
<name>Dialog</name>
<message>
<source>show</source>
<translation>표시</translation>
</message>
<message>
<source>quit</source>
<translation>종료</translation>
</message>
<message>
<source>original</source>
<translation>원본</translation>
</message>
<message>
<source>no lock</source>
<translation>잠금 없음</translation>
</message>
<message>
<source>Notice</source>
<translation>알림</translation>
</message>
<message>
<source>Hidden here!</source>
<translation>여기에 숨겨져 있어요!</translation>
</message>
<message>
<source>select path</source>
<translation>경로 선택</translation>
</message>
<message>
<source>Clear History</source>
<translation>기록 지우기</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>이 소프트웨어는 완전히 오픈 소스이며 무료입니다. 본인의 위험을 감수하고 사용하세요. 다음 주소로 다운로드할 수 있습니다:</translation>
</message>
<message>
<source>QuickMirror</source>
<translation>빠른미러</translation>
</message>
<message>
<source>If you need more professional batch control mirror software, you can try the following software:</source>
<translation>더 전문적인 일괄 제어 미러 소프트웨어가 필요하다면 다음 소프트웨어를 사용해 볼 수 있습니다:</translation>
</message>
<message>
<source>If you need more professional game keymap mirror software, you can try the following software:</source>
<translation>더 전문적인 게임 키맵 미러 소프트웨어가 필요하다면 다음 소프트웨어를 사용해 보세요:</translation>
</message>
<message>
<source>QuickAssistant</source>
<translation>빠른 도우미</translation>
</message>
<message>
<source>You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1></source>
<translation>텔레그램으로 연락주세요 <https://t.me/+Ylf_5V_rDCMyODQ1></translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation>도구</translation>
</message>
<message>
<source>full screen</source>
<translation>전체 화면</translation>
</message>
<message>
<source>expand notify</source>
<translation>확장 알림</translation>
</message>
<message>
<source>touch switch</source>
<translation>터치 스위치</translation>
</message>
<message>
<source>close screen</source>
<translation>화면 닫기</translation>
</message>
<message>
<source>power</source>
<translation>전ㅇ</translation>
</message>
<message>
<source>volume up</source>
<translation>볼륨 높이기</translation>
</message>
<message>
<source>volume down</source>
<translation>볼륨 낮추기</translation>
</message>
<message>
<source>app switch</source>
<translation>앱 스위치</translation>
</message>
<message>
<source>menu</source>
<translation>메뉴</translation>
</message>
<message>
<source>home</source>
<translation>home</translation>
</message>
<message>
<source>return</source>
<translation>돌ㅇ가기</translation>
</message>
<message>
<source>screen shot</source>
<translation>스크린샷</translation>
</message>
<message>
<source>open screen</source>
<translation>화면 열기</translation>
</message>
<message>
<source>group control</source>
<translation>그룹 제어</translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation>파일이 존재하지 않습니다</translation>
</message>
</context>
<context>
<name>Widget</name>
<message>
<source>Wireless</source>
<translation>무선</translation>
</message>
<message>
<source>wireless connect</source>
<translation>무선 연결</translation>
</message>
<message>
<source>wireless disconnect</source>
<translation>무선 연결 끊기</translation>
</message>
<message>
<source>Start Config</source>
<translation>시작 구성</translation>
</message>
<message>
<source>select path</source>
<translation>경로 선택</translation>
</message>
<message>
<source>record format:</source>
<translation>기록 형식:</translation>
</message>
<message>
<source>record screen</source>
<translation>화면 녹화</translation>
</message>
<message>
<source>frameless</source>
<translation>프레임 없음</translation>
</message>
<message>
<source>Use Simple Mode</source>
<translatorcomment>Use Simple Mode</translatorcomment>
<translation>간단한 모드 사용</translation>
</message>
<message>
<source>Simple Mode</source>
<translatorcomment>Simple Mode</translatorcomment>
<translation>간단한 모드</translation>
</message>
<message>
<source>WIFI Connect</source>
<translatorcomment>WIFI Connect</translatorcomment>
<translation>WIFI 연결</translation>
</message>
<message>
<source>USB Connect</source>
<translatorcomment>USB Connect</translatorcomment>
<translation>USB 연결</translation>
</message>
<message>
<source>Double click to connect:</source>
<translation>더블 클릭하여 연결:</translation>
</message>
<message>
<source>lock orientation:</source>
<translation>잠금 방향:</translation>
</message>
<message>
<source>show fps</source>
<translation>fps 표시</translation>
</message>
<message>
<source>stay awake</source>
<translation>깨어 있기</translation>
</message>
<message>
<source>device name:</source>
<translatorcomment>device name:</translatorcomment>
<translation>장치 이름:</translation>
</message>
<message>
<source>update name</source>
<translatorcomment>update name</translatorcomment>
<translation>업데이트 이름</translation>
</message>
<message>
<source>stop all server</source>
<translation>모든 서버 중지</translation>
</message>
<message>
<source>adb command:</source>
<translation>adb 명령:</translation>
</message>
<message>
<source>terminate</source>
<translation>끝내기</translation>
</message>
<message>
<source>execute</source>
<translation>실행</translation>
</message>
<message>
<source>clear</source>
<translation>지우기</translation>
</message>
<message>
<source>reverse connection</source>
<translation>역연결</translation>
</message>
<message>
<source>background record</source>
<translation>배경 기록</translation>
</message>
<message>
<source>screen-off</source>
<translation>화면 끄기</translation>
</message>
<message>
<source>apply</source>
<translation>적용</translation>
</message>
<message>
<source>max size:</source>
<translation>최대 크기:</translation>
</message>
<message>
<source>always on top</source>
<translation>항상 위에</translation>
</message>
<message>
<source>refresh script</source>
<translation>스크립트 새로 고침</translation>
</message>
<message>
<source>get device IP</source>
<translation>장치 IP 가져오기</translation>
</message>
<message>
<source>USB line</source>
<translation>USB 선</translation>
</message>
<message>
<source>stop server</source>
<translation>서버 중지</translation>
</message>
<message>
<source>start server</source>
<translation>서버 시작</translation>
</message>
<message>
<source>device serial:</source>
<translation>장치 직렬:</translation>
</message>
<message>
<source>bit rate:</source>
<translation>비트 전송률:</translation>
</message>
<message>
<source>start adbd</source>
<translation>adbd 시작</translation>
</message>
<message>
<source>refresh devices</source>
<translation>장치 새로 고침</translation>
</message>
<message>
<source>install sndcpy</source>
<translation>sndcpy 설치</translation>
</message>
<message>
<source>start audio</source>
<translation>오디오 시작</translation>
</message>
<message>
<source>stop audio</source>
<translation>오디오 중지</translation>
</message>
<message>
<source>auto update</source>
<translation>자동 업데이트</translation>
</message>
<message>
<source>show toolbar</source>
<translation>도구 모음 표시</translation>
</message>
<message>
<source>record save path:</source>
<translation>기록 저장 경로:</translation>
</message>
</context>
</TS>
================================================
FILE: QtScrcpy/res/i18n/zh_CN.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>Dialog</name>
<message>
<source>show</source>
<translation>显示</translation>
</message>
<message>
<source>quit</source>
<translation>退出</translation>
</message>
<message>
<source>original</source>
<translation>原始</translation>
</message>
<message>
<source>no lock</source>
<translation>不锁定</translation>
</message>
<message>
<source>Notice</source>
<translation>提示</translation>
</message>
<message>
<source>Hidden here!</source>
<translation>安卓录屏程序隐藏在这!</translation>
</message>
<message>
<source>select path</source>
<translation>选择路径</translation>
</message>
<message>
<source>Clear History</source>
<translation>清理历史</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
<translation>本软件完全开源免费,作者不对使用该软件产生的一切后果负责。你可以在以下地址下载:</translation>
</message>
<message>
<source>QuickMirror</source>
<translation>极限投屏</translation>
</message>
<message>
<source>If you need more professional batch control mirror software, you can try the following software:</source>
<translation>如果你需要更专业的批量控制投屏软件,你可以尝试下面软件:</translation>
</message>
<message>
<source>If you need more professional game keymap mirror software, you can try the following software:</source>
<translation>如果你需要更专业的游戏映射投屏软件,你可以尝试下面软件:</translation>
</message>
<message>
<source>QuickAssistant</source>
<translation>极限手游助手</translation>
</message>
<message>
<source>You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1></source>
<translation>你可以通过QQ群联系我 <901736468></translation>
</message>
</context>
<context>
<name>ToolForm</name>
<message>
<source>Tool</source>
<translation>工具</translation>
</message>
<message>
<source>full screen</source>
<translation>全屏</translation>
</message>
<message>
<source>expand notify</source>
<translation>下拉通知</translation>
</message>
<message>
<source>touch switch</source>
<translation>触摸显示开关</translation>
</message>
<message>
<source>close screen</source>
<translation>关闭屏幕</translation>
</message>
<message>
<source>power</source>
<translation>电源</translation>
</message>
<message>
<source>volume up</source>
<translation>音量加</translation>
</message>
<message>
<source>volume down</source>
<translation>音量减</translation>
</message>
<message>
<source>app switch</source>
<translation>切换应用</translation>
</message>
<message>
<source>menu</source>
<translation>菜单</translation>
</message>
<message>
<source>home</source>
<translation>主界面</translation>
</message>
<message>
<source>return</source>
<translation>返回</translation>
</message>
<message>
<source>screen shot</source>
<translation>截图</translation>
</message>
<message>
<source>open screen</source>
<translation>打开屏幕</translation>
</message>
<message>
<source>group control</source>
<translation>群控</translation>
</message>
</context>
<context>
<name>VideoForm</name>
<message>
<source>file does not exist</source>
<translation>文件不存在</translation>
</message>
</context>
<context>
<name>Widget</name>
<message>
<source>Wireless</source>
<translation>无线</translation>
</message>
<message>
<source>wireless connect</source>
<translation>无线连接</translation>
</message>
<message>
<source>wireless disconnect</source>
<translation>无线断开</translation>
</message>
<message>
<source>Start Config</source>
<translation>启动配置</translation>
</message>
<message>
<source>select path</source>
<translation>选择路径</translation>
</message>
<message>
<source>record format:</source>
<translation>录制格式:</translation>
</message>
<message>
<source>record screen</source>
<translation>录制屏幕</translation>
</message>
<message>
<source>frameless</source>
<translation>无边框</translation>
</message>
<message>
<source>Use Simple Mode</source>
<translatorcomment>启用精简模式</translatorcomment>
<translation>启用精简模式</translation>
</message>
<message>
<source>Simple Mode</source>
<translatorcomment>精简模式</translatorcomment>
<translation>精简模式</translation>
</message>
<message>
<source>WIFI Connect</source>
<translatorcomment>一键WIFI连接</translatorcomment>
<translation>一键WIFI连接</translation>
</message>
<message>
<source>USB Connect</source>
<translatorcomment>一键USB连接</translatorcomment>
<translation>一键USB连接</translation>
</message>
<message>
<source>Double click to connect:</source>
<translation>双击连接:</translation>
</message>
<message>
<source>lock orientation:</source>
<translation>锁定方向:</translation>
</message>
<message>
<source>show fps</source>
<translation>显示fps</translation>
</message>
<message>
<source>stay awake</source>
<translation>保持唤醒</translation>
</message>
<message>
<source>device name:</source>
<translatorcomment>设备名称:</translatorcomment>
<translation>设备名称:</translation>
</message>
<message>
<source>update name</source>
<translatorcomment>更新设置名称</translatorcomment>
<translation>更新设置名称</translation>
</message>
<message>
<source>stop all server</source>
<translation>停止所有服务</translation>
</message>
<message>
<source>adb command:</source>
<translation>adb命令:</translation>
</message>
<message>
<source>terminate</source>
<translation>终止</translation>
</message>
<message>
<source>execute</source>
<translation>执行</translation>
</message>
<message>
<source>clear</source>
<translation>清理</translation>
</message>
<message>
<source>reverse connection</source>
<translation>反向连接</translation>
</message>
<message>
<source>background record</source>
<translation>后台录制</translation>
</message>
<message>
<source>screen-off</source>
<translation>自动息屏</translation>
</message>
<message>
<source>apply</source>
<translation>应用脚本</translation>
</message>
<message>
<source>max size:</source>
<translation>最大尺寸:</translation>
</message>
<message>
<source>always on top</source>
<translation>窗口置顶</translation>
</message>
<message>
<source>refresh script</source>
<translation>刷新脚本</translation>
</message>
<message>
<source>get device IP</source>
<translation>获取设备IP</translation>
</message>
<message>
<source>USB line</source>
<translation>USB线</translation>
</message>
<message>
<source>stop server</source>
<translation>停止服务</translation>
</message>
<message>
<source>start server</source>
<translation>启动服务</translation>
</message>
<message>
<source>device serial:</source>
<translation>设备序列号:</translation>
</message>
<message>
<source>bit rate:</source>
<translation>比特率:</translation>
</message>
<message>
<source>start adbd</source>
<translation>启动adbd</translation>
</message>
<message>
<source>refresh devices</source>
<translation>刷新设备列表</translation>
</message>
<message>
<source>install sndcpy</source>
<translation>安装sndcpy</translation>
</message>
<message>
<source>start audio</source>
<translation>开始音频</translation>
</message>
<message>
<source>stop audio</source>
<translation>停止音频</translation>
</message>
<message>
<source>auto update</source>
<translation>自动刷新</translation>
</message>
<message>
<source>show toolbar</source>
<translation>显示工具栏</translation>
</message>
<message>
<source>record save path:</source>
<translation>录像保存路径</translation>
</message>
</context>
</TS>
================================================
FILE: QtScrcpy/res/qss/psblack.css
================================================
QPalette{background:#444444;}*{outline:0px;color:#DCDCDC;}
QWidget[form="true"],QLabel[frameShape="1"]{
border:1px solid #242424;
border-radius:0px;
}
QWidget[form="bottom"]{
background:#484848;
}
QWidget[form="bottom"] .QFrame{
border:1px solid #DCDCDC;
}
QWidget[form="bottom"] QLabel,QWidget[form="title"] QLabel{
border-radius:0px;
color:#DCDCDC;
background:none;
border-style:none;
}
QWidget[form="title"],QWidget[nav="left"],QWidget[nav="top"] QAbstractButton{
border-style:none;
border-radius:0px;
padding:5px;
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}
QWidget[nav="top"] QAbstractButton:hover,QWidget[nav="top"] QAbstractButton:pressed,QWidget[nav="top"] QAbstractButton:checked{
border-style:solid;
border-width:0px 0px 2px 0px;
padding:4px 4px 2px 4px;
border-color:#00BB9E;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}
QWidget[nav="left"] QAbstractButton{
border-radius:0px;
color:#DCDCDC;
background:none;
border-style:none;
}
QWidget[nav="left"] QAbstractButton:hover{
color:#FFFFFF;
background-color:#00BB9E;
}
QWidget[nav="left"] QAbstractButton:checked,QWidget[nav="left"] QAbstractButton:pressed{
color:#DCDCDC;
border-style:solid;
border-width:0px 0px 0px 2px;
padding:4px 4px 4px 2px;
border-color:#00BB9E;
background-color:#444444;
}
QWidget[video="true"] QLabel{
color:#DCDCDC;
border:1px solid #242424;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}
QWidget[video="true"] QLabel:focus{
border:1px solid #00BB9E;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}
QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{
border:1px solid #242424;
border-radius:3px;
padding:2px;
background:none;
selection-background-color:#264F78;
selection-color:#DCDCDC;
}
QLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{
border:1px solid #242424;
}
QLineEdit[echoMode="2"]{
lineedit-password-character:9679;
}
.QFrame{
border:1px solid #242424;
border-radius:3px;
}
.QGroupBox{
border:1px solid #242424;
border-radius:5px;
margin-top:3ex;
}
.QGroupBox::title{
subcontrol-origin:margin;
position:relative;
left:10px;
}
.QPushButton,.QToolButton{
border-style:none;
border:1px solid #242424;
color:#DCDCDC;
padding:5px;
min-height:15px;
border-radius:5px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}
.QPushButton:hover,.QToolButton:hover{
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}
.QPushButton:pressed,.QToolButton:pressed{
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}
.QToolButton::menu-indicator{
image:None;
}
QToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{
border-radius:3px;
color:#DCDCDC;
padding:3px;
margin:0px;
background:none;
border-style:none;
}
QToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{
color:#FFFFFF;
margin:1px 1px 2px 1px;
background-color:rgba(51,127,209,230);
}
QPushButton#btnMenu_Close:hover{
color:#FFFFFF;
margin:1px 1px 2px 1px;
background-color:rgba(238,0,0,128);
}
QRadioButton::indicator{
width:15px;
height:15px;
}
QRadioButton::indicator::unchecked{
image:url(:/qss/psblack/radiobutton_unchecked.png);
}
QRadioButton::indicator::unchecked:disabled{
image:url(:/qss/psblack/radiobutton_unchecked_disable.png);
}
QRadioButton::indicator::checked{
image:url(:/qss/psblack/radiobutton_checked.png);
}
QRadioButton::indicator::checked:disabled{
image:url(:/qss/psblack/radiobutton_checked_disable.png);
}
QGroupBox::indicator,QTreeWidget::indicator,QListWidget::indicator{
padding:0px -3px 0px 3px;
}
QCheckBox::indicator,QGroupBox::indicator,QTreeWidget::indicator,QListWidget::indicator{
width:13px;
height:13px;
}
QCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeWidget::indicator:unchecked,QListWidget::indicator:unchecked{
image:url(:/qss/psblack/checkbox_unchecked.png);
}
QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeWidget::indicator:unchecked:disabled,QListWidget::indicator:disabled{
image:url(:/qss/psblack/checkbox_unchecked_disable.png);
}
QCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeWidget::indicator:checked,QListWidget::indicator:checked{
image:url(:/qss/psblack/checkbox_checked.png);
}
QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeWidget::indicator:checked:disabled,QListWidget::indicator:checked:disabled{
image:url(:/qss/psblack/checkbox_checked_disable.png);
}
QCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeWidget::indicator:indeterminate,QListWidget::indicator:indeterminate{
image:url(:/qss/psblack/checkbox_parcial.png);
}
QCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeWidget::indicator:indeterminate:disabled,QListWidget::indicator:indeterminate:disabled{
image:url(:/qss/psblack/checkbox_parcial_disable.png);
}
QTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{
image:url(:/qss/psblack/add_top.png);
width:10px;
height:10px;
padding:2px 5px 0px 0px;
}
QTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{
image:url(:/qss/psblack/add_bottom.png);
width:10px;
height:10px;
padding:0px 5px 2px 0px;
}
QTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{
top:-2px;
}
QTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{
bottom:-2px;
}
QComboBox::down-arrow,QDateEdit[calendarPopup="true"]::down-arrow,QTimeEdit[calendarPopup="true"]::down-arrow,QDateTimeEdit[calendarPopup="true"]::down-arrow{
image:url(:/qss/psblack/add_bottom.png);
width:10px;
height:10px;
right:2px;
}
QComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{
subcontrol-origin:padding;
subcontrol-position:top right;
width:15px;
border-left-width:0px;
border-left-style:solid;
border-top-right-radius:3px;
border-bottom-right-radius:3px;
border-left-color:#242424;
}
QComboBox::drop-down:on{
top:1px;
}
QMenuBar::item{
color:#DCDCDC;
background-color:#484848;
margin:0px;
padding:3px 10px;
}
QMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{
color:#DCDCDC;
background-color:#484848;
border:1px solid #242424;
margin:0px;
}
QMenu::item{
padding:3px 20px;
}
QMenu::indicator{
width:13px;
height:13px;
}
QMenu::item:selected,QMenuBar::item:selected{
color:#DCDCDC;
border:0px solid #242424;
background:#646464;
}
QMenu::separator{
height:1px;
background:#242424;
}
QProgressBar{
min-height:10px;
background:#484848;
border-radius:5px;
text-align:center;
border:1px solid #484848;
}
QProgressBar:chunk{
border-radius:5px;
background-color:#242424;
}
QSlider::groove:horizontal{
background:#484848;
height:8px;
border-radius:4px;
}
QSlider::add-page:horizontal{
background:#484848;
height:8px;
border-radius:4px;
}
QSlider::sub-page:horizontal{
background:#242424;
height:8px;
border-radius:4px;
}
QSlider::handle:horizontal{
width:13px;
margin-top:-3px;
margin-bottom:-3px;
border-radius:6px;
background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424);
}
QSlider::groove:vertical{
width:8px;
border-radius:4px;
background:#484848;
}
QSlider::add-page:vertical{
width:8px;
border-radius:4px;
background:#484848;
}
QSlider::sub-page:vertical{
width:8px;
border-radius:4px;
background:#242424;
}
QSlider::handle:vertical{
height:14px;
margin-left:-3px;
margin-right:-3px;
border-radius:6px;
background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424);
}
QScrollBar:horizontal{
background:#484848;
padding:0px;
border-radius:6px;
max-height:12px;
}
QScrollBar::handle:horizontal{
background:#525252;
min-width:50px;
border-radius:6px;
}
QScrollBar::handle:horizontal:hover{
background:#242424;
}
QScrollBar::handle:horizontal:pressed{
background:#242424;
}
QScrollBar::add-page:horizontal{
background:none;
}
QScrollBar::sub-page:horizontal{
background:none;
}
QScrollBar::add-line:horizontal{
background:none;
}
QScrollBar::sub-line:horizontal{
background:none;
}
QScrollBar:vertical{
background:#484848;
padding:0px;
border-radius:6px;
max-width:12px;
}
QScrollBar::handle:vertical{
background:#525252;
min-height:50px;
border-radius:6px;
}
QScrollBar::handle:vertical:hover{
background:#242424;
}
QScrollBar::handle:vertical:pressed{
background:#242424;
}
QScrollBar::add-page:vertical{
background:none;
}
QScrollBar::sub-page:vertical{
background:none;
}
QScrollBar::add-line:vertical{
background:none;
}
QScrollBar::sub-line:vertical{
background:none;
}
QScrollArea{
border:0px;
}
QTreeView,QListView,QTableView,QTabWidget::pane{
border:1px solid #242424;
selection-background-color:#646464;
selection-color:#DCDCDC;
alternate-background-color:#525252;
gridline-color:#242424;
}
QTreeView::branch:closed:has-children{
margin:4px;
border-image:url(:/qss/psblack/branch_open.png);
}
QTreeView::branch:open:has-children{
margin:4px;
border-image:url(:/qss/psblack/branch_close.png);
}
QTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{
background:#444444;
}
QTableView::item:selected,QListView::item:selected,QTreeView::item:selected{
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}
QTableView::item:hover,QListView::item:hover,QTreeView::item:hover{
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}
QTableView::item,QListView::item,QTreeView::item{
padding:1px;
margin:0px;
}
QHeaderView::section,QTableCornerButton:section{
padding:3px;
margin:0px;
color:#DCDCDC;
border:1px solid #242424;
border-left-width:0px;
border-right-width:1px;
border-top-width:0px;
border-bottom-width:1px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}
QTabBar::tab{
border:1px solid #242424;
color:#DCDCDC;
margin:0px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}
QTabBar::tab:selected,QTabBar::tab:hover{
border-style:solid;
border-color:#00BB9E;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}
QTabBar::tab:top,QTabBar::tab:bottom{
padding:3px 8px 3px 8px;
}
QTabBar::tab:left,QTabBar::tab:right{
padding:8px 3px 8px 3px;
}
QTabBar::tab:top:selected,QTabBar::tab:top:hover{
border-width:2px 0px 0px 0px;
}
QTabBar::tab:right:selected,QTabBar::tab:right:hover{
border-width:0px 0px 0px 2px;
}
QTabBar::tab:bottom:selected,QTabBar::tab:bottom:hover{
border-width:0px 0px 2px 0px;
}
QTabBar::tab:left:selected,QTabBar::tab:left:hover{
border-width:0px 2px 0px 0px;
}
QTabBar::tab:first:top:selected,QTabBar::tab:first:top:hover,QTabBar::tab:first:bottom:selected,QTabBar::tab:first:bottom:hover{
border-left-width:1px;
border-left-color:#242424;
}
QTabBar::tab:first:left:selected,QTabBar::tab:first:left:hover,QTabBar::tab:first:right:selected,QTabBar::tab:first:right:hover{
border-top-width:1px;
border-top-color:#242424;
}
QTabBar::tab:last:top:selected,QTabBar::tab:last:top:hover,QTabBar::tab:last:bottom:selected,QTabBar::tab:last:bottom:hover{
border-right-width:1px;
border-right-color:#242424;
}
QTabBar::tab:last:left:selected,QTabBar::tab:last:left:hover,QTabBar::tab:last:right:selected,QTabBar::tab:last:right:hover{
border-bottom-width:1px;
border-bottom-color:#242424;
}
QStatusBar::item{
border:0px solid #484848;
border-radius:3px;
}
QToolBox::tab,QGroupBox#gboxDevicePanel,QGroupBox#gboxDeviceTitle,QFrame#gboxDevicePanel,QFrame#gboxDeviceTitle{
padding:3px;
border-radius:5px;
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}
QToolTip{
border:0px solid #DCDCDC;
padding:1px;
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}
QToolBox::tab:selected{
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}
QPrintPreviewDialog QToolButton{
border:0px solid #DCDCDC;
border-radius:0px;
margin:0px;
padding:3px;
background:none;
}
QColorDialog QPushButton,QFileDialog QPushButton{
min-width:80px;
}
QToolButton#qt_calendar_prevmonth{
icon-size:0px;
min-width:20px;
image:url(:/qss/psblack/calendar_prevmonth.png);
}
QToolButton#qt_calendar_nextmonth{
icon-size:0px;
min-width:20px;
image:url(:/qss/psblack/calendar_nextmonth.png);
}
QToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{
border:0px solid #DCDCDC;
border-radius:3px;
margin:3px 3px 3px 3px;
padding:3px;
background:none;
}
QToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{
border:1px solid #242424;
}
QCalendarWidget QSpinBox#qt_calendar_yearedit{
margin:2px;
}
QCalendarWidget QToolButton::menu-indicator{
image:None;
}
QCalendarWidget QTableView{
border-width:0px;
}
QCalendarWidget QWidget#qt_calendar_navigationbar{
border:1px solid #242424;
border-width:1px 1px 0px 1px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}
QComboBox QAbstractItemView::item{
min-height:20px;
min-width:10px;
}
QTableView[model="true"]::item{
padding:0px;
margin:0px;
}
QTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{
border-width:0px;
border-radius:0px;
}
QTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{
border-width:0px;
border-radius:0px;
}
QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{
background:#444444;
}
*:disabled{
background:#444444;
border-color:#484848;
}
QMessageBox {
background-color:#444444;
color:#DCDCDC;
}
/*TextColor:#DCDCDC*/
/*PanelColor:#444444*/
/*BorderColor:#242424*/
/*NormalColorStart:#484848*/
/*NormalColorEnd:#383838*/
/*DarkColorStart:#646464*/
/*DarkColorEnd:#525252*/
/*HighColor:#00BB9E*/
================================================
FILE: QtScrcpy/res/res.qrc
================================================
<RCC>
<qresource prefix="/">
<file>font/fontawesome-webfont.ttf</file>
<file>image/videoform/phone-h.png</file>
<file>image/videoform/phone-v.png</file>
<file>qss/psblack.css</file>
<file>qss/psblack/add_bottom.png</file>
<file>qss/psblack/add_left.png</file>
<file>qss/psblack/add_right.png</file>
<file>qss/psblack/add_top.png</file>
<file>qss/psblack/branch_close.png</file>
<file>qss/psblack/branch_open.png</file>
<file>qss/psblack/calendar_nextmonth.png</file>
<file>qss/psblack/calendar_prevmonth.png</file>
<file>qss/psblack/checkbox_checked.png</file>
<file>qss/psblack/checkbox_checked_disable.png</file>
<file>qss/psblack/checkbox_parcial.png</file>
<file>qss/psblack/checkbox_parcial_disable.png</file>
<file>qss/psblack/checkbox_unchecked.png</file>
<file>qss/psblack/checkbox_unchecked_disable.png</file>
<file>qss/psblack/radiobutton_checked.png</file>
<file>qss/psblack/radiobutton_checked_disable.png</file>
<file>qss/psblack/radiobutton_unchecked.png</file>
<file>qss/psblack/radiobutton_unchecked_disable.png</file>
<file>i18n/en_US.qm</file>
<file>i18n/zh_CN.qm</file>
<file>i18n/ja_JP.qm</file>
<file>image/tray/logo.png</file>
</qresource>
</RCC>
================================================
FILE: QtScrcpy/sndcpy/sndcpy.bat
================================================
@echo off
echo Begin Runing...
set SNDCPY_PORT=28200
set SNDCPY_APK=sndcpy.apk
set ADB=adb.exe
if not "%1"=="" (
set serial=-s %1
)
if not "%2"=="" (
set SNDCPY_PORT=%2
)
echo Waiting for device %1...
%ADB% %serial% wait-for-device || goto :error
echo Find device %1
for /f "delims=" %%i in ('%ADB% %serial% shell pm path com.rom1v.sndcpy') do set sndcpy_installed=%%i
if "%sndcpy_installed%"=="" (
echo Install %SNDCPY_APK%...
%ADB% %serial% uninstall com.rom1v.sndcpy || echo uninstall failed
%ADB% %serial% install -t -r -g %SNDCPY_APK% || goto :error
echo Install %SNDCPY_APK% success
)
echo Request PROJECT_MEDIA permission...
%ADB% %serial% shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow
echo Forward port %SNDCPY_PORT%...
%ADB% %serial% forward tcp:%SNDCPY_PORT% localabstract:sndcpy || goto :error
echo Start %SNDCPY_APK%...
%ADB% %serial% shell am start com.rom1v.sndcpy/.MainActivity || goto :error
:check_start
echo Waiting %SNDCPY_APK% start...
::timeout /T 1 /NOBREAK > nul
%ADB% %serial% shell sleep 0.1
for /f "delims=" %%i in ("%ADB% shell 'ps | grep com.rom1v.sndcpy'") do set sndcpy_started=%%i
if "%sndcpy_started%"=="" (
goto :check_start
)
echo %SNDCPY_APK% started...
echo Ready playing...
::vlc.exe -Idummy --demux rawaud --network-caching=0 --play-and-exit tcp://localhost:%SNDCPY_PORT%
::ffplay.exe -nodisp -autoexit -probesize 32 -sync ext -f s16le -ar 48k -ac 2 tcp://localhost:%SNDCPY_PORT%
goto :EOF
:error
echo Failed with error #%errorlevel%.
exit /b %errorlevel%
================================================
FILE: QtScrcpy/sndcpy/sndcpy.sh
================================================
#!/bin/bash
echo Begin Runing...
SNDCPY_PORT=28200
SNDCPY_APK=sndcpy.apk
ADB=./adb
serial=
if [[ $# -ge 2 ]]
then
serial="-s $1"
SNDCPY_PORT=$2
fi
echo "Waiting for device $1..."
$ADB $serial wait-for-device
echo "Find device $1"
sndcpy_installed=$($ADB $serial shell pm path com.rom1v.sndcpy)
if [[ $sndcpy_installed == "" ]]; then
echo Install $SNDCPY_APK...
$ADB $serial uninstall com.rom1v.sndcpy || echo uninstall failed
$ADB $serial install -t -r -g $SNDCPY_APK
echo Install $SNDCPY_APK success
fi
echo Request PROJECT_MEDIA permission...
$ADB $serial shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow
echo Forward port $SNDCPY_PORT...
$ADB $serial forward tcp:$SNDCPY_PORT localabstract:sndcpy
echo Start $SNDCPY_APK...
$ADB $serial shell am start com.rom1v.sndcpy/.MainActivity
while ((1))
do
echo Waiting $SNDCPY_APK start...
sleep 0.1
sndcpy_started=$($ADB shell 'ps | grep com.rom1v.sndcpy')
if [[ $sndcpy_started != "" ]]; then
break
fi
done
echo Ready playing...
================================================
FILE: QtScrcpy/ui/dialog.cpp
================================================
#include <QDebug>
#include <QFile>
#include <QFileDialog>
#include <QKeyEvent>
#include <QRandomGenerator>
#include <QTime>
#include <QTimer>
#include "config.h"
#include "dialog.h"
#include "ui_dialog.h"
#include "videoform.h"
#include "../groupcontroller/groupcontroller.h"
#ifdef Q_OS_WIN32
#include "../util/winutils.h"
#endif
QString s_keyMapPath = "";
const QString &getKeyMapPath()
{
if (s_keyMapPath.isEmpty()) {
s_keyMapPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_KEYMAP_PATH"));
QFileInfo fileInfo(s_keyMapPath);
if (s_keyMapPath.isEmpty() || !fileInfo.isDir()) {
s_keyMapPath = QCoreApplication::applicationDirPath() + "/keymap";
}
}
return s_keyMapPath;
}
Dialog::Dialog(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
initUI();
updateBootConfig(true);
on_useSingleModeCheck_clicked();
on_updateDevice_clicked();
connect(&m_autoUpdatetimer, &QTimer::timeout, this, &Dialog::on_updateDevice_clicked);
if (ui->autoUpdatecheckBox->isChecked()) {
m_autoUpdatetimer.start(5000);
}
connect(&m_adb, &qsc::AdbProcess::adbProcessResult, this, [this](qsc::AdbProcess::ADB_EXEC_RESULT processResult) {
QString log = "";
bool newLine = true;
QStringList args = m_adb.arguments();
switch (processResult) {
case qsc::AdbProcess::AER_ERROR_START:
break;
case qsc::AdbProcess::AER_SUCCESS_START:
log = "adb run";
newLine = false;
break;
case qsc::AdbProcess::AER_ERROR_EXEC:
//log = m_adb.getErrorOut();
if (args.contains("ifconfig") && args.contains("wlan0")) {
getIPbyIp();
}
break;
case qsc::AdbProcess::AER_ERROR_MISSING_BINARY:
log = "adb not found";
break;
case qsc::AdbProcess::AER_SUCCESS_EXEC:
//log = m_adb.getStdOut();
if (args.contains("devices")) {
QStringList devices = m_adb.getDevicesSerialFromStdOut();
ui->serialBox->clear();
ui->connectedPhoneList->clear();
for (auto &item : devices) {
ui->serialBox->addItem(item);
ui->connectedPhoneList->addItem(Config::getInstance().getNickName(item) + "-" + item);
}
} else if (args.contains("show") && args.contains("wlan0")) {
QString ip = m_adb.getDeviceIPFromStdOut();
if (ip.isEmpty()) {
log = "ip not find, connect to wifi?";
break;
}
ui->deviceIpEdt->setEditText(ip);
} else if (args.contains("ifconfig") && args.contains("wlan0")) {
QString ip = m_adb.getDeviceIPFromStdOut();
if (ip.isEmpty()) {
log = "ip not find, connect to wifi?";
break;
}
ui->deviceIpEdt->setEditText(ip);
} else if (args.contains("ip -o a")) {
QString ip = m_adb.getDeviceIPByIpFromStdOut();
if (ip.isEmpty()) {
log = "ip not find, connect to wifi?";
break;
}
ui->deviceIpEdt->setEditText(ip);
}
break;
}
if (!log.isEmpty()) {
outLog(log, newLine);
}
});
m_hideIcon = new QSystemTrayIcon(this);
m_hideIcon->setIcon(QIcon(":/image/tray/logo.png"));
m_menu = new QMenu(this);
m_quit = new QAction(this);
m_showWindow = new QAction(this);
m_showWindow->setText(tr("show"));
m_quit->setText(tr("quit"));
m_menu->addAction(m_showWindow);
m_menu->addAction(m_quit);
m_hideIcon->setContextMenu(m_menu);
m_hideIcon->show();
connect(m_showWindow, &QAction::triggered, this, &Dialog::show);
connect(m_quit, &QAction::triggered, this, [this]() {
m_hideIcon->hide();
qApp->quit();
});
connect(m_hideIcon, &QSystemTrayIcon::activated, this, &Dialog::slotActivated);
connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceConnected, this, &Dialog::onDeviceConnected);
connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceDisconnected, this, &Dialog::onDeviceDisconnected);
}
Dialog::~Dialog()
{
qDebug() << "~Dialog()";
updateBootConfig(false);
qsc::IDeviceManage::getInstance().disconnectAllDevice();
delete ui;
}
void Dialog::initUI()
{
setAttribute(Qt::WA_DeleteOnClose);
//setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint);
setWindowTitle(Config::getInstance().getTitle());
#ifdef Q_OS_LINUX
// Set window icon (inherits from application icon set in main.cpp)
// If application icon was set, this will use it automatically
if (!qApp->windowIcon().isNull()) {
setWindowIcon(qApp->windowIcon());
}
#endif
#ifdef Q_OS_WIN32
WinUtils::setDarkBorderToWindow((HWND)this->winId(), true);
#endif
ui->bitRateEdit->setValidator(new QIntValidator(1, 99999, this));
ui->maxSizeBox->addItem("640");
ui->maxSizeBox->addItem("720");
ui->maxSizeBox->addItem("1080");
ui->maxSizeBox->addItem("1280");
ui->maxSizeBox->addItem("1920");
ui->maxSizeBox->addItem(tr("original"));
ui->formatBox->addItem("mp4");
ui->formatBox->addItem("mkv");
ui->lockOrientationBox->addItem(tr("no lock"));
ui->lockOrientationBox->addItem("0");
ui->lockOrientationBox->addItem("90");
ui->lockOrientationBox->addItem("180");
ui->lockOrientationBox->addItem("270");
ui->lockOrientationBox->setCurrentIndex(0);
// 加载IP历史记录
loadIpHistory();
// 加载端口历史记录
loadPortHistory();
// 为deviceIpEdt添加右键菜单
if (ui->deviceIpEdt->lineEdit()) {
ui->deviceIpEdt->lineEdit()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->deviceIpEdt->lineEdit(), &QWidget::customContextMenuRequested,
this, &Dialog::showIpEditMenu);
}
// 为devicePortEdt添加右键菜单
if (ui->devicePortEdt->lineEdit()) {
ui->devicePortEdt->lineEdit()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->devicePortEdt->lineEdit(), &QWidget::customContextMenuRequested,
this, &Dialog::showPortEditMenu);
}
}
void Dialog::updateBootConfig(bool toView)
{
if (toView) {
UserBootConfig config = Config::getInstance().getUserBootConfig();
if (config.bitRate == 0) {
ui->bitRateBox->setCurrentText("Mbps");
} else if (config.bitRate % 1000000 == 0) {
ui->bitRateEdit->setText(QString::number(config.bitRate / 1000000));
ui->bitRateBox->setCurrentText("Mbps");
} else {
ui->bitRateEdit->setText(QString::number(config.bitRate / 1000));
ui->bitRateBox->setCurrentText("Kbps");
}
ui->maxSizeBox->setCurrentIndex(config.maxSizeIndex);
ui->formatBox->setCurrentIndex(config.recordFormatIndex);
ui->recordPathEdt->setText(config.recordPath);
ui->lockOrientationBox->setCurrentIndex(config.lockOrientationIndex);
ui->framelessCheck->setChecked(config.framelessWindow);
ui->recordScreenCheck->setChecked(config.recordScreen);
ui->notDisplayCheck->setChecked(config.recordBackground);
ui->useReverseCheck->setChecked(config.reverseConnect);
ui->fpsCheck->setChecked(config.showFPS);
ui->alwaysTopCheck->setChecked(config.windowOnTop);
ui->closeScreenCheck->setChecked(config.autoOffScreen);
ui->stayAwakeCheck->setChecked(config.keepAlive);
ui->useSingleModeCheck->setChecked(config.simpleMode);
ui->autoUpdatecheckBox->setChecked(config.autoUpdateDevice);
ui->showToolbar->setChecked(config.showToolbar);
} else {
UserBootConfig config;
config.bitRate = getBitRate();
config.maxSizeIndex = ui->maxSizeBox->currentIndex();
config.recordFormatIndex = ui->formatBox->currentIndex();
config.recordPath = ui->recordPathEdt->text();
config.lockOrientationIndex = ui->lockOrientationBox->currentIndex();
config.recordScreen = ui->recordScreenCheck->isChecked();
config.recordBackground = ui->notDisplayCheck->isChecked();
config.reverseConnect = ui->useReverseCheck->isChecked();
config.showFPS = ui->fpsCheck->isChecked();
config.windowOnTop = ui->alwaysTopCheck->isChecked();
config.autoOffScreen = ui->closeScreenCheck->isChecked();
config.framelessWindow = ui->framelessCheck->isChecked();
config.keepAlive = ui->stayAwakeCheck->isChecked();
config.simpleMode = ui->useSingleModeCheck->isChecked();
config.autoUpdateDevice = ui->autoUpdatecheckBox->isChecked();
config.showToolbar = ui->showToolbar->isChecked();
// 保存当前IP到历史记录
QString currentIp = ui->deviceIpEdt->currentText().trimmed();
if (!currentIp.isEmpty()) {
saveIpHistory(currentIp);
}
Config::getInstance().setUserBootConfig(config);
}
}
void Dialog::execAdbCmd()
{
if (checkAdbRun()) {
return;
}
QString cmd = ui->adbCommandEdt->text().trimmed();
outLog("adb " + cmd, false);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(" ", Qt::SkipEmptyParts));
#else
m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(" ", QString::SkipEmptyParts));
#endif
}
void Dialog::delayMs(int ms)
{
QTime dieTime = QTime::currentTime().addMSecs(ms);
while (QTime::currentTime() < dieTime) {
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}
}
QString Dialog::getGameScript(const QString &fileName)
{
if (fileName.isEmpty()) {
return "";
}
QFile loadFile(getKeyMapPath() + "/" + fileName);
if (!loadFile.open(QIODevice::ReadOnly)) {
outLog("open file failed:" + fileName, true);
return "";
}
QString ret = loadFile.readAll();
loadFile.close();
return ret;
}
void Dialog::slotActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason) {
case QSystemTrayIcon::Trigger:
#ifdef Q_OS_WIN32
this->show();
#endif
break;
default:
break;
}
}
void Dialog::closeEvent(QCloseEvent *event)
{
this->hide();
if (!Config::getInstance().getTrayMessageShown()) {
Config::getInstance().setTrayMessageShown(true);
m_hideIcon->showMessage(tr("Notice"),
tr("Hidden here!"),
QSystemTrayIcon::Information,
3000);
}
event->ignore();
}
void Dialog::on_updateDevice_clicked()
{
if (checkAdbRun()) {
return;
}
outLog("update devices...", false);
m_adb.execute("", QStringList() << "devices");
}
void Dialog::on_startServerBtn_clicked()
{
outLog("start server...", false);
// this is ok that "original" toUshort is 0
quint16 videoSize = ui->maxSizeBox->currentText().trimmed().toUShort();
qsc::DeviceParams params;
params.serial = ui->serialBox->currentText().trimmed();
params.maxSize = videoSize;
params.bitRate = getBitRate();
// on devices with Android >= 10, the capture frame rate can be limited
params.maxFps = static_cast<quint32>(Config::getInstance().getMaxFps());
params.closeScreen = ui->closeScreenCheck->isChecked();
params.useReverse = ui->useReverseCheck->isChecked();
params.display = !ui->notDisplayCheck->isChecked();
params.renderExpiredFrames = Config::getInstance().getRenderExpiredFrames();
if (ui->lockOrientationBox->currentIndex() > 0) {
params.captureOrientationLock = 1;
params.captureOrientation = (ui->lockOrientationBox->currentIndex() - 1) * 90;
}
params.stayAwake = ui->stayAwakeCheck->isChecked();
params.recordFile = ui->recordScreenCheck->isChecked();
params.recordPath = ui->recordPathEdt->text().trimmed();
params.recordFileFormat = ui->formatBox->currentText().trimmed();
params.serverLocalPath = getServerPath();
params.serverRemotePath = Config::getInstance().getServerPath();
params.pushFilePath = Config::getInstance().getPushFilePath();
params.gameScript = getGameScript(ui->gameBox->currentText());
params.logLevel = Config::getInstance().getLogLevel();
params.codecOptions = Config::getInstance().getCodecOptions();
params.codecName = Config::getInstance().getCodecName();
params.scid = QRandomGenerator::global()->bounded(1, 10000) & 0x7FFFFFFF;
qsc::IDeviceManage::getInstance().connectDevice(params);
}
void Dialog::on_stopServerBtn_clicked()
{
if (qsc::IDeviceManage::getInstance().disconnectDevice(ui->serialBox->currentText().trimmed())) {
outLog("stop server");
}
}
void Dialog::on_wirelessConnectBtn_clicked()
{
if (checkAdbRun()) {
return;
}
QString addr = ui->deviceIpEdt->currentText().trimmed();
if (addr.isEmpty()) {
outLog("error: device ip is null", false);
return;
}
if (!ui->devicePortEdt->currentText().isEmpty()) {
addr += ":";
addr += ui->devicePortEdt->currentText().trimmed();
} else if (!ui->devicePortEdt->lineEdit()->placeholderText().isEmpty()) {
addr += ":";
addr += ui->devicePortEdt->lineEdit()->placeholderText().trimmed();
} else {
outLog("error: device port is null", false);
return;
}
// 保存IP历史记录 - 只保存IP部分,不包含端口
QString ip = addr.split(":").first();
if (!ip.isEmpty()) {
saveIpHistory(ip);
}
// 保存端口历史记录
QString port = addr.split(":").last();
if (!port.isEmpty() && port != ip) {
savePortHistory(port);
}
outLog("wireless connect...", false);
QStringList adbArgs;
adbArgs << "connect";
adbArgs << addr;
m_adb.execute("", adbArgs);
}
void Dialog::on_startAdbdBtn_clicked()
{
if (checkAdbRun()) {
return;
}
outLog("start devices adbd...", false);
// adb tcpip 5555
QStringList adbArgs;
adbArgs << "tcpip";
adbArgs << "5555";
m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);
}
void Dialog::outLog(const QString &log, bool newLine)
{
// avoid sub thread update ui
QString backLog = log;
QTimer::singleShot(0, this, [this, backLog, newLine]() {
ui->outEdit->append(backLog);
if (newLine) {
ui->outEdit->append("<br/>");
}
});
}
bool Dialog::filterLog(const QString &log)
{
if (log.contains("app_proces")) {
return true;
}
if (log.contains("Unable to set geometry")) {
return true;
}
return false;
}
bool Dialog::checkAdbRun()
{
if (m_adb.isRuning()) {
outLog("wait for the end of the current command to run");
}
return m_adb.isRuning();
}
void Dialog::on_getIPBtn_clicked()
{
if (checkAdbRun()) {
return;
}
outLog("get ip...", false);
// adb -s P7C0218510000537 shell ifconfig wlan0
// or
// adb -s P7C0218510000537 shell ip -f inet addr show wlan0
QStringList adbArgs;
#if 0
adbArgs << "shell";
adbArgs << "ip";
adbArgs << "-f";
adbArgs << "inet";
adbArgs << "addr";
adbArgs << "show";
adbArgs << "wlan0";
#else
adbArgs << "shell";
adbArgs << "ifconfig";
adbArgs << "wlan0";
#endif
m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);
}
void Dialog::getIPbyIp()
{
if (checkAdbRun()) {
return;
}
QStringList adbArgs;
adbArgs << "shell";
adbArgs << "ip -o a";
m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);
}
void Dialog::onDeviceConnected(bool success, const QString &serial, const QString &deviceName, const QSize &size)
{
Q_UNUSED(deviceName);
if (!success) {
return;
}
auto videoForm = new VideoForm(ui->framelessCheck->isChecked(), Config::getInstance().getSkin(), ui->showToolbar->isChecked());
videoForm->setSerial(serial);
qsc::IDeviceManage::getInstance().getDevice(serial)->setUserData(static_cast<void*>(videoForm));
qsc::IDeviceManage::getInstance().getDevice(serial)->registerDeviceObserver(videoForm);
videoForm->showFPS(ui->fpsCheck->isChecked());
if (ui->alwaysTopCheck->isChecked()) {
videoForm->staysOnTop();
}
#ifndef Q_OS_WIN32
// must be show before updateShowSize
videoForm->show();
#endif
QString name = Config::getInstance().getNickName(serial);
if (name.isEmpty()) {
name = Config::getInstance().getTitle();
}
videoForm->setWindowTitle(name + "-" + serial);
videoForm->updateShowSize(size);
bool deviceVer = size.height() > size.width();
QRect rc = Config::getInstance().getRect(serial);
bool rcVer = rc.height() > rc.width();
// same width/height rate
if (rc.isValid() && (deviceVer == rcVer)) {
// mark: resize is for fix setGeometry magneticwidget bug
videoForm->resize(rc.size());
videoForm->setGeometry(rc);
}
#ifdef Q_OS_WIN32
// windows是show太早可以看到resize的过程
QTimer::singleShot(200, videoForm, [videoForm](){videoForm->show();});
#endif
GroupController::instance().addDevice(serial);
}
void Dialog::onDeviceDisconnected(QString serial)
{
GroupController::instance().removeDevice(serial);
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
if (!device) {
return;
}
auto data = device->getUserData();
if (data) {
VideoForm* vf = static_cast<VideoForm*>(data);
qsc::IDeviceManage::getInstance().getDevice(serial)->deRegisterDeviceObserver(vf);
vf->close();
vf->deleteLater();
}
}
void Dialog::on_wirelessDisConnectBtn_clicked()
{
if (checkAdbRun()) {
return;
}
QString addr = ui->deviceIpEdt->currentText().trimmed();
outLog("wireless disconnect...", false);
QStringList adbArgs;
adbArgs << "disconnect";
adbArgs << addr;
m_adb.execute("", adbArgs);
}
void Dialog::on_selectRecordPathBtn_clicked()
{
QFileDialog::Options options = QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly;
QString directory = QFileDialog::getExistingDirectory(this, tr("select path"), "", options);
ui->recordPathEdt->setText(directory);
}
void Dialog::on_recordPathEdt_textChanged(const QString &arg1)
{
ui->recordPathEdt->setToolTip(arg1.trimmed());
ui->notDisplayCheck->setCheckable(!arg1.trimmed().isEmpty());
}
void Dialog::on_adbCommandBtn_clicked()
{
execAdbCmd();
}
void Dialog::on_stopAdbBtn_clicked()
{
m_adb.kill();
}
void Dialog::on_clearOut_clicked()
{
ui->outEdit->clear();
}
void Dialog::on_stopAllServerBtn_clicked()
{
qsc::IDeviceManage::getInstance().disconnectAllDevice();
}
void Dialog::on_refreshGameScriptBtn_clicked()
{
ui->gameBox->clear();
QDir dir(getKeyMapPath());
if (!dir.exists()) {
outLog("keymap directory not find", true);
return;
}
dir.setFilter(QDir::Files | QDir::NoSymLinks);
QFileInfoList list = dir.entryInfoList();
QFileInfo fileInfo;
int size = list.size();
for (int i = 0; i < size; ++i) {
fileInfo = list.at(i);
ui->gameBox->addItem(fileInfo.fileName());
}
}
void Dialog::on_applyScriptBtn_clicked()
{
auto curSerial = ui->serialBox->currentText().trimmed();
auto device = qsc::IDeviceManage::getInstance().getDevice(curSerial);
if (!device) {
return;
}
device->updateScript(getGameScript(ui->gameBox->currentText()));
}
void Dialog::on_recordScreenCheck_clicked(bool checked)
{
if (!checked) {
return;
}
QString fileDir(ui->recordPathEdt->text().trimmed());
if (fileDir.isEmpty()) {
qWarning() << "please select record save path!!!";
ui->recordScreenCheck->setChecked(false);
}
}
void Dialog::on_usbConnectBtn_clicked()
{
on_stopAllServerBtn_clicked();
delayMs(200);
on_updateDevice_clicked();
delayMs(200);
int firstUsbDevice = findDeviceFromeSerialBox(false);
if (-1 == firstUsbDevice) {
qWarning() << "No use device is found!";
return;
}
ui->serialBox->setCurrentIndex(firstUsbDevice);
on_startServerBtn_clicked();
}
int Dialog::findDeviceFromeSerialBox(bool wifi)
{
QString regStr = "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\:([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])\\b";
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
QRegExp regIP(regStr);
#else
QRegularExpression regIP(regStr);
#endif
for (int i = 0; i < ui->serialBox->count(); ++i) {
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
bool isWifi = regIP.exactMatch(ui->serialBox->itemText(i));
#else
bool isWifi = regIP.match(ui->serialBox->itemText(i)).hasMatch();
#endif
bool found = wifi ? isWifi : !isWifi;
if (found) {
return i;
}
}
return -1;
}
void Dialog::on_wifiConnectBtn_clicked()
{
on_stopAllServerBtn_clicked();
delayMs(200);
on_updateDevice_clicked();
delayMs(200);
int firstUsbDevice = findDeviceFromeSerialBox(false);
if (-1 == firstUsbDevice) {
qWarning() << "No use device is found!";
return;
}
ui->serialBox->setCurrentIndex(firstUsbDevice);
on_getIPBtn_clicked();
delayMs(200);
on_startAdbdBtn_clicked();
delayMs(1000);
on_wirelessConnectBtn_clicked();
delayMs(2000);
on_updateDevice_clicked();
delayMs(200);
int firstWifiDevice = findDeviceFromeSerialBox(true);
if (-1 == firstWifiDevice) {
qWarning() << "No wifi device is found!";
return;
}
ui->serialBox->setCurrentIndex(firstWifiDevice);
on_startServerBtn_clicked();
}
void Dialog::on_connectedPhoneList_itemDoubleClicked(QListWidgetItem *item)
{
Q_UNUSED(item);
ui->serialBox->setCurrentIndex(ui->connectedPhoneList->currentRow());
on_startServerBtn_clicked();
}
void Dialog::on_updateNameBtn_clicked()
{
if (ui->serialBox->count() != 0) {
if (ui->userNameEdt->text().isEmpty()) {
Config::getInstance().setNickName(ui->serialBox->currentText(), "Phone");
} else {
Config::getInstance().setNickName(ui->serialBox->currentText(), ui->userNameEdt->text());
}
on_updateDevice_clicked();
qDebug() << "Update OK!";
} else {
qWarning() << "No device is connected!";
}
}
void Dialog::on_useSingleModeCheck_clicked()
{
if (ui->useSingleModeCheck->isChecked()) {
ui->rightWidget->hide();
} else {
ui->rightWidget->show();
}
adjustSize();
}
void Dialog::on_serialBox_currentIndexChanged(const QString &arg1)
{
ui->userNameEdt->setText(Config::getInstance().getNickName(arg1));
}
quint32 Dialog::getBitRate()
{
return ui->bitRateEdit->text().trimmed().toUInt() *
(ui->bitRateBox->currentText() == QString("Mbps") ? 1000000 : 1000);
}
const QString &Dialog::getServerPath()
{
static QString serverPath;
if (serverPath.isEmpty()) {
serverPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_SERVER_PATH"));
QFileInfo fileInfo(serverPath);
if (serverPath.isEmpty() || !fileInfo.isFile()) {
serverPath = QCoreApplication::applicationDirPath() + "/scrcpy-server";
}
}
return serverPath;
}
void Dialog::on_startAudioBtn_clicked()
{
if (ui->serialBox->count() == 0) {
qWarning() << "No device is connected!";
return;
}
m_audioOutput.start(ui->serialBox->currentText(), 28200);
}
void Dialog::on_stopAudioBtn_clicked()
{
m_audioOutput.stop();
}
void Dialog::on_installSndcpyBtn_clicked()
{
if (ui->serialBox->count() == 0) {
qWarning() << "No device is connected!";
return;
}
m_audioOutput.installonly(ui->serialBox->currentText(), 28200);
}
void Dialog::on_autoUpdatecheckBox_toggled(bool checked)
{
if (checked) {
m_autoUpdatetimer.start(5000);
} else {
m_autoUpdatetimer.stop();
}
}
void Dialog::loadIpHistory()
{
QStringList ipList = Config::getInstance().getIpHistory();
ui->deviceIpEdt->clear();
ui->deviceIpEdt->addItems(ipList);
ui->deviceIpEdt->setContentsMargins(0, 0, 0, 0);
if (ui->deviceIpEdt->lineEdit()) {
ui->deviceIpEdt->lineEdit()->setMaxLength(128);
ui->deviceIpEdt->lineEdit()->setPlaceholderText("192.168.0.1");
}
}
void Dialog::saveIpHistory(const QString &ip)
{
if (ip.isEmpty()) {
return;
}
Config::getInstance().saveIpHistory(ip);
// 更新ComboBox
loadIpHistory();
ui->deviceIpEdt->setCurrentText(ip);
}
void Dialog::showIpEditMenu(const QPoint &pos)
{
QMenu *menu = ui->deviceIpEdt->lineEdit()->createStandardContextMenu();
menu->addSeparator();
QAction *clearHistoryAction = new QAction(tr("Clear History"), menu);
connect(clearHistoryAction, &QAction::triggered, this, [this]() {
Config::getInstance().clearIpHistory();
loadIpHistory();
});
menu->addAction(clearHistoryAction);
menu->exec(ui->deviceIpEdt->lineEdit()->mapToGlobal(pos));
delete menu;
}
void Dialog::loadPortHistory()
{
QStringList portList = Config::getInstance().getPortHistory();
ui->devicePortEdt->clear();
ui->devicePortEdt->addItems(portList);
ui->devicePortEdt->setContentsMargins(0, 0, 0, 0);
if (ui->devicePortEdt->lineEdit()) {
ui->devicePortEdt->lineEdit()->setMaxLength(6);
ui->devicePortEdt->lineEdit()->setPlaceholderText("5555");
}
}
void Dialog::savePortHistory(const QString &port)
{
if (port.isEmpty()) {
return;
}
Config::getInstance().savePortHistory(port);
// 更新ComboBox
loadPortHistory();
ui->devicePortEdt->setCurrentText(port);
}
void Dialog::showPortEditMenu(const QPoint &pos)
{
QMenu *menu = ui->devicePortEdt->lineEdit()->createStandardContextMenu();
menu->addSeparator();
QAction *clearHistoryAction = new QAction(tr("Clear History"), menu);
connect(clearHistoryAction, &QAction::triggered, this, [this]() {
Config::getInstance().clearPortHistory();
loadPortHistory();
});
menu->addAction(clearHistoryAction);
menu->exec(ui->devicePortEdt->lineEdit()->mapToGlobal(pos));
delete menu;
}
================================================
FILE: QtScrcpy/ui/dialog.h
================================================
#ifndef DIALOG_H
#define DIALOG_H
#include <QWidget>
#include <QPointer>
#include <QMessageBox>
#include <QMenu>
#include <QSystemTrayIcon>
#include <QListWidget>
#include <QTimer>
#include "adbprocess.h"
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
#include "audio/audiooutput.h"
namespace Ui
{
class Widget;
}
class QYUVOpenGLWidget;
class Dialog : public QWidget
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = 0);
~Dialog();
void outLog(const QString &log, bool newLine = true);
bool filterLog(const QString &log);
void getIPbyIp();
private slots:
void onDeviceConnected(bool success, const QString& serial, const QString& deviceName, const QSize& size);
void onDeviceDisconnected(QString serial);
void on_updateDevice_clicked();
void on_startServerBtn_clicked();
void on_stopServerBtn_clicked();
void on_wirelessConnectBtn_clicked();
void on_startAdbdBtn_clicked();
void on_getIPBtn_clicked();
void on_wirelessDisConnectBtn_clicked();
void on_selectRecordPathBtn_clicked();
void on_recordPathEdt_textChanged(const QString &arg1);
void on_adbCommandBtn_clicked();
void on_stopAdbBtn_clicked();
void on_clearOut_clicked();
void on_stopAllServerBtn_clicked();
void on_refreshGameScriptBtn_clicked();
void on_applyScriptBtn_clicked();
void on_recordScreenCheck_clicked(bool checked);
void on_usbConnectBtn_clicked();
void on_wifiConnectBtn_clicked();
void on_connectedPhoneList_itemDoubleClicked(QListWidgetItem *item);
void on_updateNameBtn_clicked();
void on_useSingleModeCheck_clicked();
void on_serialBox_currentIndexChanged(const QString &arg1);
void on_startAudioBtn_clicked();
void on_stopAudioBtn_clicked();
void on_installSndcpyBtn_clicked();
void on_autoUpdatecheckBox_toggled(bool checked);
void showIpEditMenu(const QPoint &pos);
private:
bool checkAdbRun();
void initUI();
void updateBootConfig(bool toView = true);
void execAdbCmd();
void delayMs(int ms);
QString getGameScript(const QString &fileName);
void slotActivated(QSystemTrayIcon::ActivationReason reason);
int findDeviceFromeSerialBox(bool wifi);
quint32 getBitRate();
const QString &getServerPath();
void loadIpHistory();
void saveIpHistory(const QString &ip);
void loadPortHistory();
void savePortHistory(const QString &port);
void showPortEditMenu(const QPoint &pos);
protected:
void closeEvent(QCloseEvent *event);
private:
Ui::Widget *ui;
qsc::AdbProcess m_adb;
QSystemTrayIcon *m_hideIcon;
QMenu *m_menu;
QAction *m_showWindow;
QAction *m_quit;
AudioOutput m_audioOutput;
QTimer m_autoUpdatetimer;
};
#endif // DIALOG_H
================================================
FILE: QtScrcpy/ui/dialog.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1293</width>
<height>502</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">QtScrcpy</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="leftWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QCheckBox" name="useSingleModeCheck">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Use Simple Mode</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="simpleGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Simple Mode</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QPushButton" name="wifiConnectBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>WIFI Connect</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="usbConnectBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>USB Connect</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_10">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Double click to connect:</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="autoUpdatecheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>auto update</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="connectedPhoneList">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="adbGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string notr="true">adb</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="label_7">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>adb command:</string>
</property>
<property name="buddy">
<cstring>adbCommandEdt</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="adbCommandEdt">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">devices</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="adbCommandBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>execute</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopAdbBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>terminate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearOut">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>clear</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTextEdit" name="outEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="documentTitle">
<string/>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="rightWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QGroupBox" name="configGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Start Config</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QWidget" name="configWidget1" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>bit rate:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="bitRateEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">2</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="bitRateBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string/>
</property>
<property name="currentText">
<string notr="true">Mbps</string>
</property>
<item>
<property name="text">
<string notr="true">Mbps</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">Kbps</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>max size:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="maxSizeBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="configWidget5" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>record format:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="formatBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_8">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>lock orientation:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="lockOrientationBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="configWidget2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>record save path:</string>
</property>
<property name="buddy">
<cstring>recordPathEdt</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="recordPathEdt">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="selectRecordPathBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>select path</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="configWidget4" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="gameBox">
<property name="sizePolicy">
<size
gitextract_v8zjkv6h/
├── .clang-format
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── macos.yml
│ ├── ubuntu.yml
│ └── windows.yml
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── LICENSE
├── QtScrcpy/
│ ├── CMakeLists.txt
│ ├── appversion
│ ├── audio/
│ │ ├── audiooutput.cpp
│ │ └── audiooutput.h
│ ├── clang-format-all.sh
│ ├── fontawesome/
│ │ ├── iconhelper.cpp
│ │ └── iconhelper.h
│ ├── groupcontroller/
│ │ ├── groupcontroller.cpp
│ │ └── groupcontroller.h
│ ├── main.cpp
│ ├── render/
│ │ ├── qyuvopenglwidget.cpp
│ │ └── qyuvopenglwidget.h
│ ├── res/
│ │ ├── Info_Mac.plist.in
│ │ ├── QtScrcpy.icns
│ │ ├── QtScrcpy.rc
│ │ ├── i18n/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── en_US.qm
│ │ │ ├── en_US.ts
│ │ │ ├── ja_JP.qm
│ │ │ ├── ja_JP.ts
│ │ │ ├── ko_KR.qm
│ │ │ ├── ko_KR.ts
│ │ │ ├── zh_CN.qm
│ │ │ └── zh_CN.ts
│ │ ├── qss/
│ │ │ └── psblack.css
│ │ └── res.qrc
│ ├── sndcpy/
│ │ ├── sndcpy.apk
│ │ ├── sndcpy.bat
│ │ └── sndcpy.sh
│ ├── ui/
│ │ ├── dialog.cpp
│ │ ├── dialog.h
│ │ ├── dialog.ui
│ │ ├── toolform.cpp
│ │ ├── toolform.h
│ │ ├── toolform.ui
│ │ ├── videoform.cpp
│ │ ├── videoform.h
│ │ └── videoform.ui
│ ├── uibase/
│ │ ├── keepratiowidget.cpp
│ │ ├── keepratiowidget.h
│ │ ├── magneticwidget.cpp
│ │ └── magneticwidget.h
│ └── util/
│ ├── config.cpp
│ ├── config.h
│ ├── mousetap/
│ │ ├── cocoamousetap.h
│ │ ├── cocoamousetap.mm
│ │ ├── mousetap.cpp
│ │ ├── mousetap.h
│ │ ├── winmousetap.cpp
│ │ ├── winmousetap.h
│ │ ├── xmousetap.cpp
│ │ └── xmousetap.h
│ ├── path.h
│ ├── path.mm
│ ├── winutils.cpp
│ └── winutils.h
├── README.md
├── README_zh.md
├── backup/
│ └── myconfig.sh
├── ci/
│ ├── generate-version.py
│ ├── linux/
│ │ ├── build_for_linux.sh
│ │ ├── package_appimage.sh
│ │ └── publish_for_ubuntu.sh.todo
│ ├── lrelease.sh
│ ├── lupdate.sh
│ ├── mac/
│ │ ├── build_for_mac.sh
│ │ ├── package/
│ │ │ ├── dmg-settings.json
│ │ │ ├── package.py
│ │ │ └── requirements.txt
│ │ ├── package_for_mac.sh
│ │ └── publish_for_mac.sh
│ └── win/
│ ├── build_for_win.bat
│ └── publish_for_win.bat
├── config/
│ └── config.ini
├── docs/
│ ├── DEVELOP.md
│ ├── FAQ.md
│ ├── KeyMapDes.md
│ ├── KeyMapDes_zh.md
│ └── TODO.md
└── keymap/
├── FRAG.json
├── gameforpeace.json
├── identityv.json
├── test.json
└── tiktok.json
SYMBOL INDEX (56 symbols across 25 files)
FILE: QtScrcpy/audio/audiooutput.h
function class (line 12) | class AudioOutput : public QObject
FILE: QtScrcpy/fontawesome/iconhelper.h
function class (line 12) | class IconHelper : public QObject
FILE: QtScrcpy/groupcontroller/groupcontroller.cpp
function QSize (line 21) | QSize GroupController::getFrameSize(const QString &serial)
function GroupController (line 31) | GroupController &GroupController::instance()
FILE: QtScrcpy/main.cpp
function main (line 26) | int main(int argc, char *argv[])
function installTranslator (line 161) | void installTranslator()
function QtMsgType (line 196) | QtMsgType covertLogLevel(const QString &logLevel)
function myMessageOutput (line 221) | void myMessageOutput(QtMsgType type, const QMessageLogContext &context, ...
FILE: QtScrcpy/render/qyuvopenglwidget.cpp
function QSize (line 107) | QSize QYUVOpenGLWidget::minimumSizeHint() const
function QSize (line 112) | QSize QYUVOpenGLWidget::sizeHint() const
function QSize (line 127) | const QSize &QYUVOpenGLWidget::frameSize()
FILE: QtScrcpy/ui/dialog.cpp
function QString (line 21) | const QString &getKeyMapPath()
function QString (line 279) | QString Dialog::getGameScript(const QString &fileName)
function quint32 (line 768) | quint32 Dialog::getBitRate()
function QString (line 774) | const QString &Dialog::getServerPath()
FILE: QtScrcpy/ui/dialog.h
function namespace (line 17) | namespace Ui
function class (line 23) | class Dialog : public QWidget
FILE: QtScrcpy/ui/toolform.h
function namespace (line 10) | namespace Ui
function class (line 16) | class ToolForm : public MagneticWidget
FILE: QtScrcpy/ui/videoform.cpp
function QRect (line 86) | QRect VideoForm::getGrabCursorRect()
function QSize (line 122) | const QSize &VideoForm::frameSize()
function QRect (line 367) | QRect VideoForm::getScreenRect()
function QMargins (line 405) | QMargins VideoForm::getMargins(bool vertical)
FILE: QtScrcpy/ui/videoform.h
function namespace (line 9) | namespace Ui
FILE: QtScrcpy/uibase/keepratiowidget.cpp
function QSize (line 28) | const QSize KeepRatioWidget::goodSize()
FILE: QtScrcpy/uibase/keepratiowidget.h
function class (line 7) | class KeepRatioWidget : public QWidget
FILE: QtScrcpy/uibase/magneticwidget.h
function Q_OBJECT (line 14) | Q_OBJECT
FILE: QtScrcpy/util/config.cpp
function Config (line 135) | Config &Config::getInstance()
function QString (line 141) | const QString &Config::getConfigPath()
function UserBootConfig (line 185) | UserBootConfig Config::getUserBootConfig()
function QRect (line 237) | QRect Config::getRect(const QString &serial)
function QString (line 257) | QString Config::getNickName(const QString &serial)
function QString (line 304) | QString Config::getPushFilePath()
function QString (line 313) | QString Config::getServerPath()
function QString (line 322) | QString Config::getAdbPath()
function QString (line 331) | QString Config::getLogLevel()
function QString (line 340) | QString Config::getCodecOptions()
function QString (line 349) | QString Config::getCodecName()
function QStringList (line 358) | QStringList Config::getConnectedGroups()
function QString (line 368) | QString Config::getLanguage()
function QString (line 377) | QString Config::getTitle()
function QStringList (line 405) | QStringList Config::getIpHistory()
function QStringList (line 437) | QStringList Config::getPortHistory()
FILE: QtScrcpy/util/config.h
type UserBootConfig (line 8) | struct UserBootConfig
function class (line 29) | class Config : public QObject
FILE: QtScrcpy/util/mousetap/cocoamousetap.h
type MouseEventTapData (line 8) | struct MouseEventTapData
FILE: QtScrcpy/util/mousetap/mousetap.cpp
function MouseTap (line 15) | MouseTap *MouseTap::getInstance()
FILE: QtScrcpy/util/mousetap/mousetap.h
function class (line 6) | class MouseTap
FILE: QtScrcpy/util/mousetap/winmousetap.h
function class (line 6) | class WinMouseTap : public MouseTap
FILE: QtScrcpy/util/mousetap/xmousetap.cpp
function find_grab_window_recursive (line 23) | static void find_grab_window_recursive(xcb_connection_t *dpy, xcb_window...
FILE: QtScrcpy/util/mousetap/xmousetap.h
function class (line 6) | class XMouseTap : public MouseTap
FILE: QtScrcpy/util/path.h
function class (line 3) | class Path {
FILE: QtScrcpy/util/winutils.cpp
type WORD (line 8) | enum : WORD
FILE: QtScrcpy/util/winutils.h
function class (line 7) | class WinUtils
FILE: ci/mac/package/package.py
function console_print (line 13) | def console_print(msg):
function generate_dmg_info (line 17) | def generate_dmg_info():
Condensed preview — 93 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (424K chars).
[
{
"path": ".clang-format",
"chars": 5843,
"preview": "---\n# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto\nLanguage: Cpp\n# BasedOnStyle: WebKit\n# 访"
},
{
"path": ".github/FUNDING.yml",
"chars": 634,
"preview": "\n# These are supported funding model platforms\n\ngithub: barry-ran\npatreon: # Replace with a single Patreon username\nopen"
},
{
"path": ".github/workflows/macos.yml",
"chars": 3568,
"preview": "name: MacOS\non: \n push:\n paths:\n - 'QtScrcpy/**'\n - '!QtScrcpy/res/**'\n - '.github/workflows/macos.ym"
},
{
"path": ".github/workflows/ubuntu.yml",
"chars": 4870,
"preview": "name: Ubuntu\non: \n push:\n paths:\n - 'QtScrcpy/**'\n - '!QtScrcpy/res/**'\n - '.github/workflows/ubuntu."
},
{
"path": ".github/workflows/windows.yml",
"chars": 4649,
"preview": "name: Windows\n# 触发规则详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#on\non: \n push:\n "
},
{
"path": ".gitignore",
"chars": 297,
"preview": "/output\n*.user\n/QtScrcpy/*.user\n/server/.gradle\n/server/.idea\n/server/build\n/server/gradle/wrapper/gradle-wrapper.jar\n/s"
},
{
"path": ".gitmodules",
"chars": 115,
"preview": "[submodule \"QtScrcpy/QtScrcpyCore\"]\n\tpath = QtScrcpy/QtScrcpyCore\n\turl = git@github.com:barry-ran/QtScrcpyCore.git\n"
},
{
"path": "CMakeLists.txt",
"chars": 91,
"preview": "cmake_minimum_required(VERSION 3.19 FATAL_ERROR)\nproject(all)\n\nadd_subdirectory(QtScrcpy)\n"
},
{
"path": "LICENSE",
"chars": 11374,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "QtScrcpy/CMakeLists.txt",
"chars": 12107,
"preview": "# For VS2019 and Xcode 12+ support.\ncmake_minimum_required(VERSION 3.19 FATAL_ERROR)\n\n#\n# Global config\n#\n\n# QC is \"Qt C"
},
{
"path": "QtScrcpy/appversion",
"chars": 6,
"preview": "0.0.0\n"
},
{
"path": "QtScrcpy/audio/audiooutput.cpp",
"chars": 6499,
"preview": "#include <QAudioOutput>\n#include <QCoreApplication>\n#include <QElapsedTimer>\n#include <QHostAddress>\n#include <QTcpSocke"
},
{
"path": "QtScrcpy/audio/audiooutput.h",
"chars": 1001,
"preview": "#ifndef AUDIOOUTPUT_H\n#define AUDIOOUTPUT_H\n\n#include <QThread>\n#include <QProcess>\n#include <QPointer>\n#include <QVecto"
},
{
"path": "QtScrcpy/clang-format-all.sh",
"chars": 2402,
"preview": "#!/bin/bash\n#\n# clang-format-all: a tool to run clang-format on an entire project\n# Copyright (C) 2016 Evan Klitzke <eva"
},
{
"path": "QtScrcpy/fontawesome/iconhelper.cpp",
"chars": 629,
"preview": "#include \"iconhelper.h\"\r\n\r\nIconHelper *IconHelper::_instance = 0;\r\nIconHelper::IconHelper(QObject *) : QObject(qApp)\r\n{\r"
},
{
"path": "QtScrcpy/fontawesome/iconhelper.h",
"chars": 787,
"preview": "#ifndef ICONHELPER_H\r\n#define ICONHELPER_H\r\n\r\n#include <QApplication>\r\n#include <QFont>\r\n#include <QFontDatabase>\r\n#incl"
},
{
"path": "QtScrcpy/groupcontroller/groupcontroller.cpp",
"chars": 9982,
"preview": "#include <QPointer>\n\n#include \"groupcontroller.h\"\n#include \"videoform.h\"\n\nGroupController::GroupController(QObject *pare"
},
{
"path": "QtScrcpy/groupcontroller/groupcontroller.h",
"chars": 1823,
"preview": "#ifndef GROUPCONTROLLER_H\n#define GROUPCONTROLLER_H\n\n#include <QObject>\n#include <QVector>\n\n#include \"QtScrcpyCore.h\"\n\nc"
},
{
"path": "QtScrcpy/main.cpp",
"chars": 9168,
"preview": "#include <QApplication>\n#include <QDebug>\n#include <QFile>\n#ifdef Q_OS_LINUX\n#include <QFileInfo>\n#include <QIcon>\n#end"
},
{
"path": "QtScrcpy/render/qyuvopenglwidget.cpp",
"chars": 8140,
"preview": "#include <QCoreApplication>\n#include <QOpenGLTexture>\n#include <QSurfaceFormat>\n\n#include \"qyuvopenglwidget.h\"\n\n// 存储顶点坐"
},
{
"path": "QtScrcpy/render/qyuvopenglwidget.h",
"chars": 1343,
"preview": "#ifndef QYUVOPENGLWIDGET_H\n#define QYUVOPENGLWIDGET_H\n#include <QOpenGLBuffer>\n#include <QOpenGLFunctions>\n#include <QOp"
},
{
"path": "QtScrcpy/res/Info_Mac.plist.in",
"chars": 1296,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "QtScrcpy/res/QtScrcpy.rc",
"chars": 1016,
"preview": "#include \"winres.h\"\r\n\r\nIDI_ICON1 ICON \"QtScrcpy.ico\"\r\n// GB2312编码的话,在中文系统上打包FileDescription可以显示中文\r\n// 在github"
},
{
"path": "QtScrcpy/res/i18n/CMakeLists.txt",
"chars": 2352,
"preview": "# 声明ts文件\nset(QC_TS_FILES \n ${CMAKE_CURRENT_SOURCE_DIR}/zh_CN.ts \n ${CMAKE_CURRENT_SOURCE_DIR}/en_US.ts\n ${CMAK"
},
{
"path": "QtScrcpy/res/i18n/en_US.ts",
"chars": 9740,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"en_US\">\n<context>\n <name>Dialog</nam"
},
{
"path": "QtScrcpy/res/i18n/ja_JP.ts",
"chars": 9254,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<!DOCTYPE TS []>\r\n<TS version=\"2.1\" language=\"ja_JP\">\r\n <context>\r\n <name>D"
},
{
"path": "QtScrcpy/res/i18n/ko_KR.ts",
"chars": 9188,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"ko\">\n<context>\n <name>Dialog</name>\n"
},
{
"path": "QtScrcpy/res/i18n/zh_CN.ts",
"chars": 8962,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_CN\">\n<context>\n <name>Dialog</nam"
},
{
"path": "QtScrcpy/res/qss/psblack.css",
"chars": 15207,
"preview": "QPalette{background:#444444;}*{outline:0px;color:#DCDCDC;}\n\nQWidget[form=\"true\"],QLabel[frameShape=\"1\"]{\nborder:1px soli"
},
{
"path": "QtScrcpy/res/res.qrc",
"chars": 1385,
"preview": "<RCC>\n <qresource prefix=\"/\">\n <file>font/fontawesome-webfont.ttf</file>\n <file>image/videoform/phone-h"
},
{
"path": "QtScrcpy/sndcpy/sndcpy.bat",
"chars": 1541,
"preview": "@echo off\n\necho Begin Runing...\nset SNDCPY_PORT=28200\nset SNDCPY_APK=sndcpy.apk\nset ADB=adb.exe\n\nif not \"%1\"==\"\" (\n s"
},
{
"path": "QtScrcpy/sndcpy/sndcpy.sh",
"chars": 1041,
"preview": "#!/bin/bash\n\necho Begin Runing...\nSNDCPY_PORT=28200\nSNDCPY_APK=sndcpy.apk\nADB=./adb\n\nserial=\nif [[ $# -ge 2 ]]\nthen\n "
},
{
"path": "QtScrcpy/ui/dialog.cpp",
"chars": 26731,
"preview": "#include <QDebug>\n#include <QFile>\n#include <QFileDialog>\n#include <QKeyEvent>\n#include <QRandomGenerator>\n#include <QT"
},
{
"path": "QtScrcpy/ui/dialog.h",
"chars": 2771,
"preview": "#ifndef DIALOG_H\n#define DIALOG_H\n\n#include <QWidget>\n#include <QPointer>\n#include <QMessageBox>\n#include <QMenu>\n#incl"
},
{
"path": "QtScrcpy/ui/dialog.ui",
"chars": 41361,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>Widget</class>\n <widget class=\"QWidget\" name=\"Widget\">"
},
{
"path": "QtScrcpy/ui/toolform.cpp",
"chars": 5950,
"preview": "#include <QDebug>\n#include <QHideEvent>\n#include <QMouseEvent>\n#include <QShowEvent>\n\n#include \"iconhelper.h\"\n#include \""
},
{
"path": "QtScrcpy/ui/toolform.h",
"chars": 1428,
"preview": "#ifndef TOOLFORM_H\n#define TOOLFORM_H\n\n#include <QPointer>\n#include <QWidget>\n\n#include \"../QtScrcpyCore/include/QtScrcp"
},
{
"path": "QtScrcpy/ui/toolform.ui",
"chars": 4335,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ToolForm</class>\n <widget class=\"QWidget\" name=\"ToolFo"
},
{
"path": "QtScrcpy/ui/videoform.cpp",
"chars": 25228,
"preview": "// #include <QDesktopWidget>\n#include <QFileInfo>\n#include <QLabel>\n#include <QMessageBox>\n#include <QMimeData>\n#include"
},
{
"path": "QtScrcpy/ui/videoform.h",
"chars": 2736,
"preview": "#ifndef VIDEOFORM_H\n#define VIDEOFORM_H\n\n#include <QPointer>\n#include <QWidget>\n\n#include \"../QtScrcpyCore/include/QtScr"
},
{
"path": "QtScrcpy/ui/videoform.ui",
"chars": 1359,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>videoForm</class>\n <widget class=\"QWidget\" name=\"video"
},
{
"path": "QtScrcpy/uibase/keepratiowidget.cpp",
"chars": 1560,
"preview": "#include <QResizeEvent>\n#include <cmath>\n\n#include \"keepratiowidget.h\"\n\nKeepRatioWidget::KeepRatioWidget(QWidget *parent"
},
{
"path": "QtScrcpy/uibase/keepratiowidget.h",
"chars": 571,
"preview": "#ifndef KEEPRATIOWIDGET_H\n#define KEEPRATIOWIDGET_H\n\n#include <QPointer>\n#include <QWidget>\n\nclass KeepRatioWidget : pub"
},
{
"path": "QtScrcpy/uibase/magneticwidget.cpp",
"chars": 5992,
"preview": "#include <QDebug>\n#include <QMoveEvent>\n#include <QStyle>\n\n#include \"magneticwidget.h\"\n\nMagneticWidget::MagneticWidget(Q"
},
{
"path": "QtScrcpy/uibase/magneticwidget.h",
"chars": 1500,
"preview": "#ifndef MAGNETICWIDGET_H\n#define MAGNETICWIDGET_H\n\n#include <QPointer>\n#include <QWidget>\n\n/*\n * a magnetic widget\n * wi"
},
{
"path": "QtScrcpy/util/config.cpp",
"chars": 13940,
"preview": "#include <QCoreApplication>\n#include <QFileInfo>\n#include <QSettings>\n#include <QDebug>\n\n#include \"config.h\"\n#ifdef Q_O"
},
{
"path": "QtScrcpy/util/config.h",
"chars": 2121,
"preview": "#ifndef CONFIG_H\n#define CONFIG_H\n\n#include <QObject>\n#include <QPointer>\n#include <QRect>\n\nstruct UserBootConfig\n{\n "
},
{
"path": "QtScrcpy/util/mousetap/cocoamousetap.h",
"chars": 612,
"preview": "#ifndef COCOAMOUSETAP_H\n#define COCOAMOUSETAP_H\n#include <QSemaphore>\n#include <QThread>\n\n#include \"mousetap.h\"\n\nstruct "
},
{
"path": "QtScrcpy/util/mousetap/cocoamousetap.mm",
"chars": 7369,
"preview": "#import <Cocoa/Cocoa.h>\n#include <QDebug>\n\n#include \"cocoamousetap.h\"\n\nstatic const CGEventMask movementEventsMask =\n "
},
{
"path": "QtScrcpy/util/mousetap/mousetap.cpp",
"chars": 531,
"preview": "#include <QtGlobal>\n\n#include \"mousetap.h\"\n#ifdef Q_OS_WIN32\n#include \"winmousetap.h\"\n#endif\n#ifdef Q_OS_OSX\n#include \"c"
},
{
"path": "QtScrcpy/util/mousetap/mousetap.h",
"chars": 425,
"preview": "#ifndef MOUSETAP_H\n#define MOUSETAP_H\n#include <QRect>\n\nclass QWidget;\nclass MouseTap\n{\npublic:\n static MouseTap *get"
},
{
"path": "QtScrcpy/util/mousetap/winmousetap.cpp",
"chars": 646,
"preview": "#include <QDebug>\n#include <QWidget>\n#include <Windows.h>\n\n#include \"winmousetap.h\"\n\nWinMouseTap::WinMouseTap() {}\n\nWinM"
},
{
"path": "QtScrcpy/util/mousetap/winmousetap.h",
"chars": 331,
"preview": "#ifndef WINMOUSETAP_H\n#define WINMOUSETAP_H\n\n#include \"mousetap.h\"\n\nclass WinMouseTap : public MouseTap\n{\npublic:\n Wi"
},
{
"path": "QtScrcpy/util/mousetap/xmousetap.cpp",
"chars": 2877,
"preview": "#include <QtGlobal>\n\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n#include <QtX11Extras/QX11Info>\n#else\n#include <QtGui/"
},
{
"path": "QtScrcpy/util/mousetap/xmousetap.h",
"chars": 319,
"preview": "#ifndef XMOUSETAP_H\n#define XMOUSETAP_H\n\n#include \"mousetap.h\"\n\nclass XMouseTap : public MouseTap\n{\npublic:\n XMouseTa"
},
{
"path": "QtScrcpy/util/path.h",
"chars": 79,
"preview": "#pragma once\n\nclass Path {\npublic:\n static const char* GetCurrentPath();\n};\n"
},
{
"path": "QtScrcpy/util/path.mm",
"chars": 143,
"preview": "#include \"path.h\"\n\n#import <Cocoa/Cocoa.h>\n\nconst char* Path::GetCurrentPath() {\n return [[[NSBundle mainBundle] bund"
},
{
"path": "QtScrcpy/util/winutils.cpp",
"chars": 848,
"preview": "#include <QDebug>\n#include <Windows.h>\n#include <dwmapi.h>\n#pragma comment(lib, \"dwmapi\")\n\n#include \"winutils.h\"\n\nenum :"
},
{
"path": "QtScrcpy/util/winutils.h",
"chars": 241,
"preview": "#ifndef WINUTILS_H\n#define WINUTILS_H\n\n#include <QApplication>\n#include <Windows.h>\n\nclass WinUtils\n{\npublic:\n WinUti"
},
{
"path": "README.md",
"chars": 18425,
"preview": "# QtScrcpy \n\n[\n\n commit = p.re"
},
{
"path": "ci/linux/build_for_linux.sh",
"chars": 1916,
"preview": "echo ---------------------------------------------------------------\necho Check \\& Set Environment Variables\necho ------"
},
{
"path": "ci/linux/package_appimage.sh",
"chars": 14442,
"preview": "#!/bin/bash\n\necho \"Package AppImage\"\n\nbuild_mode=\"$1\"\nif [[ $build_mode != \"Release\" && $build_mode != \"Debug\" && $build"
},
{
"path": "ci/linux/publish_for_ubuntu.sh.todo",
"chars": 2688,
"preview": "echo\necho\necho ---------------------------------------------------------------\necho check ENV\necho ---------------------"
},
{
"path": "ci/lrelease.sh",
"chars": 169,
"preview": "# https://doc.qt.io/qt-5/linguist-manager.html#lrelease\n# lrelease -help\nlrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcp"
},
{
"path": "ci/lupdate.sh",
"chars": 240,
"preview": "# https://doc.qt.io/qt-5/linguist-manager.html#lupdate\n# lupdate -help\n# export PATH=/D/Qt/5.15.2/msvc2019/bin:$PATH\nlup"
},
{
"path": "ci/mac/build_for_mac.sh",
"chars": 2409,
"preview": "\necho\necho\necho ---------------------------------------------------------------\necho check ENV\necho --------------------"
},
{
"path": "ci/mac/package/dmg-settings.json",
"chars": 447,
"preview": "{\"icon-size\": 120, \"format\": \"UDZO\", \"title\": \"QtScrcpy\", \"compression-level\": 9, \"window\": {\"position\": {\"y\": 200, \"x\":"
},
{
"path": "ci/mac/package/package.py",
"chars": 1612,
"preview": "import dmgbuild\nimport os\nimport json\nimport sys\n\ncurrent_file_path = os.path.dirname(os.path.realpath(__file__))\ndmg_se"
},
{
"path": "ci/mac/package/requirements.txt",
"chars": 15,
"preview": "dmgbuild==1.4.2"
},
{
"path": "ci/mac/package_for_mac.sh",
"chars": 782,
"preview": "# 获取绝对路径,保证其他目录执行此脚本依然正确\n{\ncd $(dirname \"$0\")\nscript_path=$(pwd)\ncd -\n} &> /dev/null # disable output\n# 设置当前目录,cd的目录影响接下"
},
{
"path": "ci/mac/publish_for_mac.sh",
"chars": 3163,
"preview": "echo\necho\necho ---------------------------------------------------------------\necho check ENV\necho ---------------------"
},
{
"path": "ci/win/build_for_win.bat",
"chars": 2721,
"preview": "@echo off\r\n\r\necho=\r\necho=\r\necho ---------------------------------------------------------------\r\necho check ENV\r\necho --"
},
{
"path": "ci/win/publish_for_win.bat",
"chars": 4346,
"preview": "@echo off\n\necho=\necho=\necho ---------------------------------------------------------------\necho check ENV\necho --------"
},
{
"path": "config/config.ini",
"chars": 696,
"preview": "[common]\n# 语言 Auto=自动,zh_CN=简体中文,en_US=English\nLanguage=Auto\n# 窗口标题\nWindowTitle=QtScrcpy\n# 推送到安卓设备的文件保存路径(必须以/结尾)\nPushF"
},
{
"path": "docs/DEVELOP.md",
"chars": 12880,
"preview": "# scrcpy for developers\n\n## Overview\n\nThis application is composed of two parts:\n - the server (`scrcpy-server`), to be "
},
{
"path": "docs/FAQ.md",
"chars": 1198,
"preview": "# Frequently Asked Questions\n一些经常问的问题\n\n如果在此文档没有解决你的问题,描述你的问题,截图软件控制台中打印的日志,一起发到QQ群里提问。\n\n# adb问题\n## ADB版本之间的冲突\n```\nadb se"
},
{
"path": "docs/KeyMapDes.md",
"chars": 6001,
"preview": "# Custom key mapping instructions\n\nThe key map file is in json format, and the new key map file needs to be placed in th"
},
{
"path": "docs/KeyMapDes_zh.md",
"chars": 2666,
"preview": "# 自定义按键映射说明\n\n按键映射文件为json格式,新增自己的按键映射文件需要放在keymap目录中才可以被QtScrcpy识别。\n\n按键映射文件的具体编写格式下面会介绍,也可以参考自带的按键映射文件。\n\n## 按键映射脚本格式说明\n\n#"
},
{
"path": "docs/TODO.md",
"chars": 1457,
"preview": "# TODO\n## 低优先级\n- text转换 https://github.com/Genymobile/scrcpy/commit/c916af0984f72a60301d13fa8ef9a85112f54202?tdsourcetag"
},
{
"path": "keymap/FRAG.json",
"chars": 1969,
"preview": "{\n\t\"old-switchKey\": \"Key_QuoteLeft\",\n\t\"switchKey\": \"RightButton\",\n\t\"mouseMoveMap\": {\n\t\t\"startPos\": {\n\t\t\t\"x\": 0.5,\n\t\t\t\"y\""
},
{
"path": "keymap/gameforpeace.json",
"chars": 4724,
"preview": "{\n\t\"switchKey\": \"Key_QuoteLeft\",\n\t\"mouseMoveMap\": {\n\t\t\"startPos\": {\n\t\t\t\"x\": 0.57,\n\t\t\t\"y\": 0.26\n\t\t},\n\t\t\"speedRatioX\": 3.2"
},
{
"path": "keymap/identityv.json",
"chars": 3521,
"preview": "{\n\t\"comment\":\"https://doc.qt.io/qt-5/qt.html#Key-enum\",\n\t\"old-switchKey\": \"Key_QuoteLeft\",\n\t\"switchKey\": \"RightButton\",\n"
},
{
"path": "keymap/test.json",
"chars": 502,
"preview": "{\n\t\"switchKey\": \"Key_QuoteLeft\",\n\t\"keyMapNodes\": [\n\t\t{\n\t\t\t\"comment\": \"测试一键多点\",\n\t\t\t\"type\": \"KMT_CLICK_MULTI\",\n\t\t\t\"key\": \""
},
{
"path": "keymap/tiktok.json",
"chars": 883,
"preview": "{\n\t\"switchKey\": \"Key_QuoteLeft\",\n\t\"keyMapNodes\": [\n\t\t{\n\t\t\t\"comment\": \"暂停/继续\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Spa"
}
]
// ... and 6 more files (download for full content)
About this extraction
This page contains the full source code of the barry-ran/QtScrcpy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 93 files (388.8 KB), approximately 113.3k tokens, and a symbol index with 56 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.