├── .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.ico │ ├── QtScrcpy.rc │ ├── font │ │ ├── fontawesome-webfont.pdf │ │ └── fontawesome-webfont.ttf │ ├── i18n │ │ ├── CMakeLists.txt │ │ ├── en_US.qm │ │ ├── en_US.ts │ │ ├── zh_CN.qm │ │ └── zh_CN.ts │ ├── image │ │ ├── tray │ │ │ └── logo.png │ │ └── videoform │ │ │ ├── phone-h.png │ │ │ └── phone-v.png │ ├── qss │ │ ├── psblack.css │ │ └── psblack │ │ │ ├── add_bottom.png │ │ │ ├── add_left.png │ │ │ ├── add_right.png │ │ │ ├── add_top.png │ │ │ ├── branch_close.png │ │ │ ├── branch_open.png │ │ │ ├── calendar_nextmonth.png │ │ │ ├── calendar_prevmonth.png │ │ │ ├── checkbox_checked.png │ │ │ ├── checkbox_checked_disable.png │ │ │ ├── checkbox_parcial.png │ │ │ ├── checkbox_parcial_disable.png │ │ │ ├── checkbox_unchecked.png │ │ │ ├── checkbox_unchecked_disable.png │ │ │ ├── radiobutton_checked.png │ │ │ ├── radiobutton_checked_disable.png │ │ │ ├── radiobutton_unchecked.png │ │ │ └── radiobutton_unchecked_disable.png │ └── 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 ├── logo.png └── myconfig.sh ├── ci ├── generate-version.py ├── linux │ ├── build_for_linux.sh │ └── publish_for_ubuntu.sh.todo ├── lrelease.sh ├── lupdate.sh ├── mac │ ├── build_for_mac.sh │ ├── package │ │ ├── dmg-background.jpg │ │ ├── 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 └── image │ ├── USB调试(安全设置).jpg │ ├── debug-keymap-pos.png │ ├── group-control.gif │ ├── quickmirror.png │ └── 显示指针位置.jpg ├── keymap ├── FRAG.json ├── gameforpeace.json ├── identityv.json ├── test.json └── tiktok.json └── screenshot ├── game.png ├── linux-en.png ├── linux-zh.png ├── mac-en.png ├── mac-zh.png ├── win-en.png └── win-zh.png /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto 3 | Language: Cpp 4 | # BasedOnStyle: WebKit 5 | # 访问说明符(public、private等)的偏移 6 | AccessModifierOffset: -4 7 | # 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行) 8 | AlignAfterOpenBracket: AlwaysBreak 9 | # 连续赋值时,对齐所有等号 10 | AlignConsecutiveAssignments: false 11 | # 连续声明时,对齐所有声明的变量名 12 | AlignConsecutiveDeclarations: false 13 | # 左对齐逃脱换行(使用反斜杠换行)的反斜杠 14 | AlignEscapedNewlines: Right 15 | # 水平对齐二元和三元表达式的操作数 16 | AlignOperands: true 17 | # 对齐连续的尾随的注释 18 | AlignTrailingComments: true 19 | # 允许函数声明的所有参数在放在下一行 20 | AllowAllParametersOfDeclarationOnNextLine: false 21 | # 允许短的块放在同一行 22 | AllowShortBlocksOnASingleLine: false 23 | # 允许短的case标签放在同一行 24 | AllowShortCaseLabelsOnASingleLine: false 25 | # 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All 26 | AllowShortFunctionsOnASingleLine: Empty 27 | # 允许短的if语句保持在同一行 28 | AllowShortIfStatementsOnASingleLine: false 29 | # 允许短的循环保持在同一行 30 | AllowShortLoopsOnASingleLine: false 31 | # 总是在定义返回类型后换行(deprecated) 32 | AlwaysBreakAfterDefinitionReturnType: None 33 | # 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数), 34 | # AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义) 35 | AlwaysBreakAfterReturnType: None 36 | # 总是在多行string字面量前换行 37 | AlwaysBreakBeforeMultilineStrings: false 38 | # 总是在template声明后换行 39 | AlwaysBreakTemplateDeclarations: true 40 | # false表示函数实参要么都在同一行,要么都各自一行 41 | BinPackArguments: false 42 | # false表示所有形参要么都在同一行,要么都各自一行 43 | BinPackParameters: false 44 | 45 | # 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似), 46 | # Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似), 47 | # Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom 48 | # 注:这里认为语句块也属于函数 49 | BreakBeforeBraces: Custom 50 | # 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效 51 | BraceWrapping: 52 | # class定义后面 53 | AfterClass: true 54 | # 控制语句后面 55 | AfterControlStatement: false 56 | # enum定义后面 57 | AfterEnum: true 58 | # 函数定义后面 59 | AfterFunction: true 60 | # 命名空间定义后面 61 | AfterNamespace: true 62 | # ObjC定义后面 63 | AfterObjCDeclaration: false 64 | # struct定义后面 65 | AfterStruct: true 66 | # union定义后面 67 | AfterUnion: true 68 | # extern 定义后面 69 | AfterExternBlock: true 70 | # catch之前 71 | BeforeCatch: false 72 | # else 之前 73 | BeforeElse: false 74 | # 缩进大括号 75 | IndentBraces: false 76 | 77 | # 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行) 78 | BreakBeforeBinaryOperators: All 79 | 80 | # 继承列表的逗号前换行 81 | BreakBeforeInheritanceComma: true 82 | # 继承列表换行 83 | #BreakInheritanceList: BeforeColon 84 | # 在三元运算符前换行 85 | BreakBeforeTernaryOperators: true 86 | # 在构造函数的初始化列表的逗号前换行 87 | BreakConstructorInitializersBeforeComma: true 88 | # 初始化列表前换行 89 | BreakConstructorInitializers: BeforeComma 90 | # Java注解后换行 91 | BreakAfterJavaFieldAnnotations: false 92 | 93 | BreakStringLiterals: true 94 | # 每行字符的限制,0表示没有限制 95 | ColumnLimit: 160 96 | # 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变 97 | CommentPragmas: '^ IWYU pragma:' 98 | # 紧凑 命名空间 99 | CompactNamespaces: false 100 | # 构造函数的初始化列表要么都在同一行,要么都各自一行 101 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 102 | # 构造函数的初始化列表的缩进宽度 103 | ConstructorInitializerIndentWidth: 4 104 | # 延续的行的缩进宽度 105 | ContinuationIndentWidth: 4 106 | # 去除C++11的列表初始化的大括号{后和}前的空格 107 | Cpp11BracedListStyle: false 108 | # 继承最常用的指针和引用的对齐方式 109 | DerivePointerAlignment: false 110 | # 关闭格式化 111 | DisableFormat: false 112 | # 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental) 113 | ExperimentalAutoDetectBinPacking: false 114 | # 固定命名空间注释 115 | FixNamespaceComments: true 116 | # 需要被解读为foreach循环而不是函数调用的宏 117 | ForEachMacros: 118 | - foreach 119 | - Q_FOREACH 120 | - BOOST_FOREACH 121 | 122 | IncludeBlocks: Preserve 123 | # 对#include进行排序,匹配了某正则表达式的#include拥有对应的优先级,匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前), 124 | # 可以定义负数优先级从而保证某些#include永远在最前面 125 | IncludeCategories: 126 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 127 | Priority: 2 128 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 129 | Priority: 3 130 | - Regex: 'stdafx\.' 131 | Priority: 1 132 | - Regex: '.*' 133 | Priority: 1 134 | 135 | IncludeIsMainRegex: '(Test)?$' 136 | # 缩进case标签 137 | IndentCaseLabels: false 138 | 139 | IndentPPDirectives: None 140 | # 缩进宽度 141 | IndentWidth: 4 142 | # 函数返回类型换行时,缩进函数声明或函数定义的函数名 143 | IndentWrappedFunctionNames: true 144 | 145 | JavaScriptQuotes: Leave 146 | 147 | JavaScriptWrapImports: true 148 | # 保留在块开始处的空行 149 | KeepEmptyLinesAtTheStartOfBlocks: true 150 | # 开始一个块的宏的正则表达式 151 | MacroBlockBegin: '' 152 | # 结束一个块的宏的正则表达式 153 | MacroBlockEnd: '' 154 | # 连续空行的最大数量 155 | MaxEmptyLinesToKeep: 1 156 | 157 | # 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All 158 | NamespaceIndentation: All 159 | ObjCBinPackProtocolList: Auto 160 | # 使用ObjC块时缩进宽度 161 | ObjCBlockIndentWidth: 4 162 | # 在ObjC的@property后添加一个空格 163 | ObjCSpaceAfterProperty: true 164 | # 在ObjC的protocol列表前添加一个空格 165 | ObjCSpaceBeforeProtocolList: true 166 | 167 | PenaltyBreakAssignment: 2 168 | 169 | PenaltyBreakBeforeFirstCallParameter: 19 170 | # 在一个注释中引入换行的penalty 171 | PenaltyBreakComment: 300 172 | # 第一次在<<前换行的penalty 173 | PenaltyBreakFirstLessLess: 120 174 | # 在一个字符串字面量中引入换行的penalty 175 | PenaltyBreakString: 1000 176 | PenaltyBreakTemplateDeclaration: 10 177 | # 对于每个在行字符数限制之外的字符的penalty 178 | PenaltyExcessCharacter: 1000000 179 | # 将函数的返回类型放到它自己的行的penalty 180 | PenaltyReturnTypeOnItsOwnLine: 60 181 | # 指针和引用的对齐: Left, Right, Middle 182 | PointerAlignment: Right 183 | 184 | #RawStringFormats: 185 | # - Delimiter: pb 186 | # Language: TextProto 187 | # BasedOnStyle: google 188 | # 允许重新排版注释 189 | ReflowComments: false 190 | # 允许排序#include 191 | SortIncludes: true 192 | 193 | SortUsingDeclarations: true 194 | # 在C风格类型转换后添加空格 195 | SpaceAfterCStyleCast: false 196 | # 模板关键字后面添加空格 197 | SpaceAfterTemplateKeyword: true 198 | # 在赋值运算符之前添加空格 199 | SpaceBeforeAssignmentOperators: true 200 | # 开圆括号之前添加一个空格: Never, ControlStatements, Always 201 | SpaceBeforeCpp11BracedList: false 202 | SpaceBeforeCtorInitializerColon: true 203 | SpaceBeforeInheritanceColon: true 204 | SpaceBeforeParens: ControlStatements 205 | SpaceBeforeRangeBasedForLoopColon: true 206 | # 在空的圆括号中添加空格 207 | SpaceInEmptyParentheses: false 208 | # 在尾随的评论前添加的空格数(只适用于//) 209 | SpacesBeforeTrailingComments: 1 210 | # 在尖括号的<后和>前添加空格 211 | SpacesInAngles: false 212 | # 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格 213 | SpacesInContainerLiterals: true 214 | # 在C风格类型转换的括号中添加空格 215 | SpacesInCStyleCastParentheses: false 216 | # 在圆括号的(后和)前添加空格 217 | SpacesInParentheses: false 218 | # 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响 219 | SpacesInSquareBrackets: false 220 | # 标准: Cpp03, Cpp11, Auto 221 | Standard: Cpp11 222 | # tab宽度 223 | TabWidth: 4 224 | # 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always 225 | UseTab: Never 226 | ... 227 | 228 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | # These are supported funding model platforms 3 | 4 | github: barry-ran 5 | patreon: # Replace with a single Patreon username 6 | open_collective: QtScrcpy 7 | ko_fi: # Replace with a single Ko-fi username 8 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 10 | liberapay: # Replace with a single Liberapay username 11 | issuehunt: # Replace with a single IssueHunt username 12 | otechie: # Replace with a single Otechie username 13 | custom: ["https://paypal.me/QtScrcpy", "https://gitee.com/Barryda/MyPictureBed/blob/master/QtScrcpy/payme.md"] -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | on: 3 | push: 4 | paths: 5 | - 'QtScrcpy/**' 6 | - '!QtScrcpy/res/**' 7 | - '.github/workflows/macos.yml' 8 | pull_request: 9 | paths: 10 | - 'QtScrcpy/**' 11 | - '!QtScrcpy/res/**' 12 | - '.github/workflows/macos.yml' 13 | jobs: 14 | build: 15 | name: Build 16 | # install-qt-action在arm上执行macdeployqt会报parse otool错误,所以在intel mac上执行: 17 | # 用qt6时在arm mac上编译arm和intel都没有问题 18 | # qt5+intel mac编译intel没问题 19 | # qt5+arm mac编译intel会报错 20 | # https://github.com/actions/runner-images?tab=readme-ov-file#available-images 21 | runs-on: macos-13 22 | strategy: 23 | matrix: 24 | qt-ver: [5.15.2, 6.5.3] 25 | # 配置qt-ver的额外设置qt-arch-install,build-arch 26 | include: 27 | - qt-ver: 5.15.2 28 | qt-arch-install: clang_64 29 | build-arch: x64 30 | - qt-ver: 6.5.3 31 | qt-arch-install: arm64 32 | build-arch: arm64 33 | env: 34 | target-name: QtScrcpy 35 | qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }} 36 | plantform-des: mac 37 | steps: 38 | - name: Cache Qt 39 | id: cache-qt 40 | uses: actions/cache@v4 41 | with: 42 | path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }} 43 | key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }} 44 | - name: Install Qt5 45 | if: startsWith(matrix.qt-ver, '5.') 46 | uses: jurplel/install-qt-action@v4.1.1 47 | with: 48 | version: ${{ matrix.qt-ver }} 49 | cached: ${{ steps.cache-qt.outputs.cache-hit }} 50 | - name: Install Qt6 51 | if: startsWith(matrix.qt-ver, '6.') 52 | uses: jurplel/install-qt-action@v4.1.1 53 | with: 54 | version: ${{ matrix.qt-ver }} 55 | modules: qtmultimedia 56 | cached: ${{ steps.cache-qt.outputs.cache-hit }} 57 | - uses: actions/checkout@v2 58 | with: 59 | fetch-depth: 0 60 | submodules: 'true' 61 | ssh-key: ${{ secrets.BOT_SSH_KEY }} 62 | # 编译 63 | - name: Build MacOS 64 | env: 65 | ENV_QT_PATH: ${{ env.qt-install-path }} 66 | run: | 67 | python ci/generate-version.py 68 | ci/mac/build_for_mac.sh RelWithDebInfo ${{ matrix.build-arch }} 69 | # 获取ref最后一个/后的内容 70 | - name: Get the version 71 | shell: bash 72 | id: get-version 73 | # ${ GITHUB_REF/refs\/tags\// }是linux shell ${}的变量替换语法 74 | run: echo ::set-output name=version::${GITHUB_REF##*/} 75 | # 打包 76 | - name: Package 77 | id: package 78 | env: 79 | ENV_QT_PATH: ${{ env.qt-install-path }} 80 | publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.build-arch }}-Qt${{matrix.qt-ver}}-${{ steps.get-version.outputs.version }} 81 | run: | 82 | ci/mac/publish_for_mac.sh ../build ${{ matrix.build-arch }} 83 | ci/mac/package_for_mac.sh 84 | mv ci/build/QtScrcpy.app ci/build/${{ env.publish_name }}.app 85 | mv ci/build/QtScrcpy.dmg ci/build/${{ env.publish_name }}.dmg 86 | echo "::set-output name=package-name::${{ env.publish_name }}" 87 | - uses: actions/upload-artifact@v4 88 | with: 89 | name: ${{ steps.package.outputs.package-name }}.zip 90 | path: ci/build/${{ steps.package.outputs.package-name }}.dmg 91 | # Upload to release 92 | - name: Upload Release 93 | if: startsWith(github.ref, 'refs/tags/') 94 | uses: svenstaro/upload-release-action@v1-release 95 | with: 96 | repo_token: ${{ secrets.GITHUB_TOKEN }} 97 | file: ci/build/${{ steps.package.outputs.package-name }}.dmg 98 | asset_name: ${{ steps.package.outputs.package-name }}.dmg 99 | tag: ${{ github.ref }} 100 | overwrite: true -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | on: 3 | push: 4 | paths: 5 | - 'QtScrcpy/**' 6 | - '!QtScrcpy/res/**' 7 | - '.github/workflows/ubuntu.yml' 8 | - 'ci/linux/**' 9 | pull_request: 10 | paths: 11 | - 'QtScrcpy/**' 12 | - '!QtScrcpy/res/**' 13 | - '.github/workflows/ubuntu.yml' 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ubuntu-22.04] 21 | qt-ver: [5.15.2] 22 | qt-arch-install: [gcc_64] 23 | gcc-arch: [x64] 24 | env: 25 | target-name: QtScrcpy 26 | qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }} 27 | plantform-des: ubuntu 28 | steps: 29 | - name: Install Qt 30 | uses: jurplel/install-qt-action@v4.1.1 31 | with: 32 | version: ${{ matrix.qt-ver }} 33 | cached: ${{ steps.cache-qt.outputs.cache-hit }} 34 | - name: Cache Qt 35 | id: cache-qt 36 | uses: actions/cache@v4 37 | with: 38 | path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }} 39 | key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }} 40 | - name: Install GL library 41 | run: sudo apt-get install -y libglew-dev libglfw3-dev 42 | - uses: actions/checkout@v2 43 | with: 44 | fetch-depth: 0 45 | submodules: 'true' 46 | ssh-key: ${{ secrets.BOT_SSH_KEY }} 47 | - name: Build RelWithDebInfo 48 | env: 49 | ENV_QT_PATH: ${{ env.qt-install-path }} 50 | run: | 51 | ci/linux/build_for_linux.sh "RelWithDebInfo" 52 | - name: Upload RelWithDebInfo 53 | uses: actions/upload-artifact@v4 54 | with: 55 | name: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}-RelWithDebInfo 56 | path: output/x64/RelWithDebInfo/* 57 | - name: Build Release 58 | env: 59 | ENV_QT_PATH: ${{ env.qt-install-path }} 60 | run: | 61 | ci/linux/build_for_linux.sh "Release" 62 | - name: Upload Release 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}-Release 66 | path: output/x64/Release/* 67 | - name: Install the zip utility 68 | run: | 69 | sudo apt install zip -y 70 | - name: Zip the Artifacts 71 | run: | 72 | zip -r QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}.zip output/x64/Release 73 | - name: Upload to Releases 74 | if: startsWith(github.ref, 'refs/tags/') 75 | uses: svenstaro/upload-release-action@2.3.0 76 | with: 77 | file: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}.zip 78 | asset_name: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}.zip 79 | repo_token: ${{ secrets.GITHUB_TOKEN }} 80 | tag: ${{ github.ref }} 81 | overwrite: true 82 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | # 触发规则详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#on 3 | on: 4 | push: 5 | paths: 6 | - 'QtScrcpy/**' 7 | - '!QtScrcpy/res/**' 8 | - '.github/workflows/windows.yml' 9 | - 'ci/win**' 10 | pull_request: 11 | paths: 12 | - 'QtScrcpy/**' 13 | - '!QtScrcpy/res/**' 14 | - '.github/workflows/windows.yml' 15 | - 'ci/win**' 16 | jobs: 17 | build: 18 | name: Build 19 | # windows-latest目前是windows server 2019 20 | # windows server 2019安装的是vs2019,windows server 2016安装的是vs2017 21 | # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on 22 | runs-on: windows-2019 23 | 24 | # 矩阵配置 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix 25 | strategy: 26 | matrix: 27 | qt-ver: [5.15.2] 28 | qt-arch: [win64_msvc2019_64, win32_msvc2019] 29 | # 配置qt-arch的额外设置msvc-arch,qt-arch-install 30 | include: 31 | - qt-arch: win64_msvc2019_64 32 | msvc-arch: x64 33 | qt-arch-install: msvc2019_64 34 | - qt-arch: win32_msvc2019 35 | msvc-arch: x86 36 | qt-arch-install: msvc2019 37 | # job env,所有steps都可以访问 38 | # 不同级别env详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#env 39 | # 使用表达式语法${{}}访问上下文 https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions 40 | env: 41 | target-name: QtScrcpy 42 | vcvarsall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat' 43 | vcinstall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC' 44 | qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }} 45 | plantform-des: win 46 | # 步骤 47 | steps: 48 | - name: Cache Qt 49 | id: cache-qt 50 | uses: actions/cache@v4 51 | with: 52 | path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }} 53 | key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch }} 54 | # 安装Qt 55 | - name: Install Qt 56 | # 使用外部action。这个action专门用来安装Qt 57 | uses: jurplel/install-qt-action@v4.1.1 58 | with: 59 | # Version of Qt to install 60 | version: ${{ matrix.qt-ver }} 61 | # Target platform for build 62 | target: desktop 63 | # Architecture for Windows/Android 64 | arch: ${{ matrix.qt-arch }} 65 | cached: ${{ steps.cache-qt.outputs.cache-hit }} 66 | # 拉取代码 67 | - uses: actions/checkout@v2 68 | with: 69 | fetch-depth: 0 70 | submodules: 'true' 71 | ssh-key: ${{ secrets.BOT_SSH_KEY }} 72 | # 编译msvc 73 | - name: Build MSVC 74 | # shell介绍 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell 75 | shell: cmd 76 | env: 77 | ENV_VCVARSALL: ${{ env.vcvarsall-path }} 78 | ENV_QT_PATH: ${{ env.qt-install-path }} 79 | run: | 80 | call python ci\generate-version.py 81 | call "ci\win\build_for_win.bat" RelWithDebInfo ${{ matrix.msvc-arch }} 82 | # 获取ref最后一个/后的内容 83 | - name: Get the version 84 | shell: bash 85 | id: get-version 86 | # ${ GITHUB_REF/refs\/tags\// }是linux shell ${}的变量替换语法 87 | run: echo ::set-output name=version::${GITHUB_REF##*/} 88 | # tag 打包 89 | - name: Package 90 | id: package 91 | env: 92 | ENV_VCVARSALL: ${{ env.vcvarsall-path }} 93 | ENV_VCINSTALL: ${{ env.vcinstall-path }} 94 | ENV_QT_PATH: ${{ env.qt-install-path }} 95 | publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.msvc-arch }}-${{ steps.get-version.outputs.version }} 96 | run: | 97 | cmd.exe /c ci\win\publish_for_win.bat ${{ matrix.msvc-arch }} ..\build\${{ env.publish_name }} 98 | # 打包zip 99 | Compress-Archive -Path ci\build\${{ env.publish_name }} ci\build\${{ env.publish_name }}.zip 100 | echo "::set-output name=package-name::${{ env.publish_name }}" 101 | # 上传artifacts 102 | # https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts 103 | - uses: actions/upload-artifact@v4 104 | with: 105 | name: ${{ steps.package.outputs.package-name }}.zip 106 | path: ci\build\${{ steps.package.outputs.package-name }} 107 | # Upload to release 108 | - name: Upload Release 109 | if: startsWith(github.ref, 'refs/tags/') 110 | uses: svenstaro/upload-release-action@v1-release 111 | with: 112 | repo_token: ${{ secrets.GITHUB_TOKEN }} 113 | file: ci\build\${{ steps.package.outputs.package-name }}.zip 114 | asset_name: ${{ steps.package.outputs.package-name }}.zip 115 | tag: ${{ github.ref }} 116 | overwrite: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /output 2 | *.user 3 | /QtScrcpy/*.user 4 | /server/.gradle 5 | /server/.idea 6 | /server/build 7 | /server/gradle/wrapper/gradle-wrapper.jar 8 | /server/gradle/wrapper/gradle-wrapper.properties 9 | /server/gradlew 10 | /server/gradlew.bat 11 | /server/local.properties 12 | /build/ 13 | build-* 14 | *.DS_Store 15 | userdata.ini 16 | Info_Mac.plist 17 | /ci/build_temp -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "QtScrcpy/QtScrcpyCore"] 2 | path = QtScrcpy/QtScrcpyCore 3 | url = git@github.com:barry-ran/QtScrcpyCore.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.19 FATAL_ERROR) 2 | project(all) 3 | 4 | add_subdirectory(QtScrcpy) 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (C) 2019 Rankun 190 | Copyright (C) 2019-2025 Rankun 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /QtScrcpy/appversion: -------------------------------------------------------------------------------- 1 | 0.0.0 2 | -------------------------------------------------------------------------------- /QtScrcpy/audio/audiooutput.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 8 | #include 9 | #include 10 | #include 11 | #endif 12 | 13 | #include "audiooutput.h" 14 | 15 | AudioOutput::AudioOutput(QObject *parent) 16 | : QObject(parent) 17 | { 18 | m_running = false; 19 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 20 | m_audioOutput = nullptr; 21 | #else 22 | m_audioSink = nullptr; 23 | #endif 24 | connect(&m_sndcpy, &QProcess::readyReadStandardOutput, this, [this]() { 25 | qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardOutput()); 26 | }); 27 | connect(&m_sndcpy, &QProcess::readyReadStandardError, this, [this]() { 28 | qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardError()); 29 | }); 30 | } 31 | 32 | AudioOutput::~AudioOutput() 33 | { 34 | if (QProcess::NotRunning != m_sndcpy.state()) { 35 | m_sndcpy.kill(); 36 | } 37 | stop(); 38 | } 39 | 40 | bool AudioOutput::start(const QString& serial, int port) 41 | { 42 | if (m_running) { 43 | stop(); 44 | } 45 | 46 | QElapsedTimer timeConsumeCount; 47 | timeConsumeCount.start(); 48 | bool ret = runSndcpyProcess(serial, port); 49 | qInfo() << "AudioOutput::run sndcpy cost:" << timeConsumeCount.elapsed() << "milliseconds"; 50 | if (!ret) { 51 | return ret; 52 | } 53 | 54 | startAudioOutput(); 55 | startRecvData(port); 56 | 57 | m_running = true; 58 | return true; 59 | } 60 | 61 | void AudioOutput::stop() 62 | { 63 | if (!m_running) { 64 | return; 65 | } 66 | m_running = false; 67 | 68 | stopRecvData(); 69 | stopAudioOutput(); 70 | } 71 | 72 | void AudioOutput::installonly(const QString &serial, int port) 73 | { 74 | runSndcpyProcess(serial, port, false); 75 | } 76 | 77 | bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait) 78 | { 79 | if (QProcess::NotRunning != m_sndcpy.state()) { 80 | m_sndcpy.kill(); 81 | } 82 | 83 | #ifdef Q_OS_WIN32 84 | QStringList params{serial, QString::number(port)}; 85 | m_sndcpy.start("sndcpy.bat", params); 86 | #else 87 | QStringList params{"sndcpy.sh", serial, QString::number(port)}; 88 | m_sndcpy.start("bash", params); 89 | #endif 90 | 91 | if (!wait) { 92 | return true; 93 | } 94 | 95 | if (!m_sndcpy.waitForStarted()) { 96 | qWarning() << "AudioOutput::start sndcpy process failed"; 97 | return false; 98 | } 99 | if (!m_sndcpy.waitForFinished()) { 100 | qWarning() << "AudioOutput::sndcpy process crashed"; 101 | return false; 102 | } 103 | 104 | return true; 105 | } 106 | 107 | void AudioOutput::startAudioOutput() 108 | { 109 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 110 | if (m_audioOutput) { 111 | return; 112 | } 113 | 114 | QAudioFormat format; 115 | format.setSampleRate(48000); 116 | format.setChannelCount(2); 117 | format.setSampleSize(16); 118 | format.setCodec("audio/pcm"); 119 | format.setByteOrder(QAudioFormat::LittleEndian); 120 | format.setSampleType(QAudioFormat::SignedInt); 121 | QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); 122 | 123 | if (!info.isFormatSupported(format)) { 124 | qWarning() << "AudioOutput::audio format not supported, cannot play audio."; 125 | return; 126 | } 127 | 128 | m_audioOutput = new QAudioOutput(format, this); 129 | connect(m_audioOutput, &QAudioOutput::stateChanged, this, [](QAudio::State state) { 130 | qInfo() << "AudioOutput::audio state changed:" << state; 131 | }); 132 | m_audioOutput->setBufferSize(48000*2*15/1000 * 20); 133 | m_outputDevice = m_audioOutput->start(); 134 | #else 135 | if (m_audioSink) { 136 | return; 137 | } 138 | 139 | QAudioFormat format; 140 | format.setSampleRate(48000); 141 | format.setChannelCount(2); 142 | format.setSampleFormat(QAudioFormat::Int16); 143 | QAudioDevice defaultDevice = QMediaDevices::defaultAudioOutput(); 144 | if (!defaultDevice.isFormatSupported(format)) { 145 | qWarning() << "AudioOutput::audio format not supported, cannot play audio."; 146 | return; 147 | } 148 | m_audioSink = new QAudioSink(defaultDevice, format, this); 149 | m_outputDevice = m_audioSink->start(); 150 | if (!m_outputDevice) { 151 | qWarning() << "AudioOutput::audio output device not available, cannot play audio."; 152 | delete m_audioSink; 153 | m_audioSink = nullptr; 154 | return; 155 | } 156 | #endif 157 | } 158 | 159 | void AudioOutput::stopAudioOutput() 160 | { 161 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 162 | if (m_audioOutput) { 163 | m_audioOutput->stop(); 164 | delete m_audioOutput; 165 | m_audioOutput = nullptr; 166 | } 167 | #else 168 | if (m_audioSink) { 169 | m_audioSink->stop(); 170 | delete m_audioSink; 171 | m_audioSink = nullptr; 172 | } 173 | #endif 174 | m_outputDevice = nullptr; 175 | } 176 | 177 | void AudioOutput::startRecvData(int port) 178 | { 179 | if (m_workerThread.isRunning()) { 180 | stopRecvData(); 181 | } 182 | 183 | auto audioSocket = new QTcpSocket(); 184 | audioSocket->moveToThread(&m_workerThread); 185 | connect(&m_workerThread, &QThread::finished, audioSocket, &QObject::deleteLater); 186 | 187 | connect(this, &AudioOutput::connectTo, audioSocket, [audioSocket](int port) { 188 | audioSocket->connectToHost(QHostAddress::LocalHost, port); 189 | if (!audioSocket->waitForConnected(500)) { 190 | qWarning("AudioOutput::audio socket connect failed"); 191 | return; 192 | } 193 | qInfo("AudioOutput::audio socket connect success"); 194 | }); 195 | connect(audioSocket, &QIODevice::readyRead, audioSocket, [this, audioSocket]() { 196 | qint64 recv = audioSocket->bytesAvailable(); 197 | //qDebug() << "AudioOutput::recv data:" << recv; 198 | 199 | if (!m_outputDevice) { 200 | return; 201 | } 202 | if (m_buffer.capacity() < recv) { 203 | m_buffer.reserve(recv); 204 | } 205 | 206 | qint64 count = audioSocket->read(m_buffer.data(), recv); 207 | m_outputDevice->write(m_buffer.data(), count); 208 | }); 209 | connect(audioSocket, &QTcpSocket::stateChanged, audioSocket, [](QAbstractSocket::SocketState state) { 210 | qInfo() << "AudioOutput::audio socket state changed:" << state; 211 | 212 | }); 213 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 214 | connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) { 215 | qInfo() << "AudioOutput::audio socket error occurred:" << error; 216 | }); 217 | #else 218 | connect(audioSocket, QOverload::of(&QAbstractSocket::error), audioSocket, [](QAbstractSocket::SocketError error) { 219 | qInfo() << "AudioOutput::audio socket error occurred:" << error; 220 | }); 221 | #endif 222 | 223 | m_workerThread.start(); 224 | emit connectTo(port); 225 | } 226 | 227 | void AudioOutput::stopRecvData() 228 | { 229 | if (!m_workerThread.isRunning()) { 230 | return; 231 | } 232 | 233 | m_workerThread.quit(); 234 | m_workerThread.wait(); 235 | } 236 | -------------------------------------------------------------------------------- /QtScrcpy/audio/audiooutput.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIOOUTPUT_H 2 | #define AUDIOOUTPUT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class QAudioSink; 10 | class QAudioOutput; 11 | class QIODevice; 12 | class AudioOutput : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit AudioOutput(QObject *parent = nullptr); 17 | ~AudioOutput(); 18 | 19 | bool start(const QString& serial, int port); 20 | void stop(); 21 | void installonly(const QString& serial, int port); 22 | 23 | private: 24 | bool runSndcpyProcess(const QString& serial, int port, bool wait = true); 25 | void startAudioOutput(); 26 | void stopAudioOutput(); 27 | void startRecvData(int port); 28 | void stopRecvData(); 29 | 30 | signals: 31 | void connectTo(int port); 32 | 33 | private: 34 | QPointer m_outputDevice; 35 | QThread m_workerThread; 36 | QProcess m_sndcpy; 37 | QVector m_buffer; 38 | bool m_running = false; 39 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 40 | QAudioOutput* m_audioOutput = nullptr; 41 | #else 42 | QAudioSink *m_audioSink = nullptr; 43 | #endif 44 | }; 45 | 46 | #endif // AUDIOOUTPUT_H 47 | -------------------------------------------------------------------------------- /QtScrcpy/clang-format-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # clang-format-all: a tool to run clang-format on an entire project 4 | # Copyright (C) 2016 Evan Klitzke 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | function usage { 20 | echo "Usage: $0 DIR..." 21 | exit 1 22 | } 23 | 24 | if [ $# -eq 0 ]; then 25 | usage 26 | fi 27 | 28 | # Variable that will hold the name of the clang-format command 29 | FMT="" 30 | 31 | # Some distros just call it clang-format. Others (e.g. Ubuntu) are insistent 32 | # that the version number be part of the command. We prefer clang-format if 33 | # that's present, otherwise we work backwards from highest version to lowest 34 | # version. 35 | for clangfmt in clang-format{,-{4,3}.{9,8,7,6,5,4,3,2,1,0}}; do 36 | if which "$clangfmt" &>/dev/null; then 37 | FMT="$clangfmt" 38 | break 39 | fi 40 | done 41 | 42 | # Check if we found a working clang-format 43 | if [ -z "$FMT" ]; then 44 | echo "failed to find clang-format" 45 | exit 1 46 | fi 47 | 48 | # Check all of the arguments first to make sure they're all directories 49 | for dir in "$@"; do 50 | if [ ! -d "${dir}" ]; then 51 | echo "${dir} is not a directory" 52 | usage 53 | fi 54 | done 55 | 56 | # Find a dominating file, starting from a given directory and going up. 57 | find-dominating-file() { 58 | if [ -r "$1"/"$2" ]; then 59 | return 0 60 | fi 61 | if [ "$1" = "/" ]; then 62 | return 1 63 | fi 64 | find-dominating-file "$(realpath "$1"/..)" "$2" 65 | return $? 66 | } 67 | 68 | # Run clang-format -i on all of the things 69 | for dir in "$@"; do 70 | pushd "${dir}" &>/dev/null 71 | if ! find-dominating-file . .clang-format; then 72 | echo "Failed to find dominating .clang-format starting at $PWD" 73 | continue 74 | fi 75 | find . \ 76 | \( -name '*.c' \ 77 | -o -name '*.cc' \ 78 | -o -name '*.cpp' \ 79 | -o -name '*.h' \ 80 | -o -name '*.hh' \ 81 | -o -name '*.hpp' \) \ 82 | -exec "${FMT}" -i '{}' \; 83 | popd &>/dev/null 84 | done 85 | -------------------------------------------------------------------------------- /QtScrcpy/fontawesome/iconhelper.cpp: -------------------------------------------------------------------------------- 1 | #include "iconhelper.h" 2 | 3 | IconHelper *IconHelper::_instance = 0; 4 | IconHelper::IconHelper(QObject *) : QObject(qApp) 5 | { 6 | int fontId = QFontDatabase::addApplicationFont(":/font/fontawesome-webfont.ttf"); 7 | QString fontName = QFontDatabase::applicationFontFamilies(fontId).at(0); 8 | iconFont = QFont(fontName); 9 | } 10 | 11 | void IconHelper::SetIcon(QLabel *lab, QChar c, int size) 12 | { 13 | iconFont.setPointSize(size); 14 | lab->setFont(iconFont); 15 | lab->setText(c); 16 | } 17 | 18 | void IconHelper::SetIcon(QPushButton *btn, QChar c, int size) 19 | { 20 | iconFont.setPointSize(size); 21 | btn->setFont(iconFont); 22 | btn->setText(c); 23 | } 24 | -------------------------------------------------------------------------------- /QtScrcpy/fontawesome/iconhelper.h: -------------------------------------------------------------------------------- 1 | #ifndef ICONHELPER_H 2 | #define ICONHELPER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | class IconHelper : public QObject 13 | { 14 | private: 15 | explicit IconHelper(QObject *parent = 0); 16 | QFont iconFont; 17 | static IconHelper *_instance; 18 | 19 | public: 20 | static IconHelper *Instance() 21 | { 22 | static QMutex mutex; 23 | if (!_instance) { 24 | QMutexLocker locker(&mutex); 25 | if (!_instance) { 26 | _instance = new IconHelper; 27 | } 28 | } 29 | return _instance; 30 | } 31 | 32 | void SetIcon(QLabel *lab, QChar c, int size = 10); 33 | void SetIcon(QPushButton *btn, QChar c, int size = 10); 34 | }; 35 | 36 | #endif // ICONHELPER_H 37 | -------------------------------------------------------------------------------- /QtScrcpy/groupcontroller/groupcontroller.h: -------------------------------------------------------------------------------- 1 | #ifndef GROUPCONTROLLER_H 2 | #define GROUPCONTROLLER_H 3 | 4 | #include 5 | #include 6 | 7 | #include "QtScrcpyCore.h" 8 | 9 | class GroupController : public QObject, public qsc::DeviceObserver 10 | { 11 | Q_OBJECT 12 | public: 13 | static GroupController& instance(); 14 | 15 | void updateDeviceState(const QString& serial); 16 | void addDevice(const QString& serial); 17 | void removeDevice(const QString& serial); 18 | 19 | private: 20 | // DeviceObserver 21 | void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) override; 22 | void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) override; 23 | void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) override; 24 | 25 | void postGoBack() override; 26 | void postGoHome() override; 27 | void postGoMenu() override; 28 | void postAppSwitch() override; 29 | void postPower() override; 30 | void postVolumeUp() override; 31 | void postVolumeDown() override; 32 | void postCopy() override; 33 | void postCut() override; 34 | void setDisplayPower(bool on) override; 35 | void expandNotificationPanel() override; 36 | void collapsePanel() override; 37 | void postBackOrScreenOn(bool down) override; 38 | void postTextInput(QString &text) override; 39 | void requestDeviceClipboard() override; 40 | void setDeviceClipboard(bool pause = true) override; 41 | void clipboardPaste() override; 42 | void pushFileRequest(const QString &file, const QString &devicePath = "") override; 43 | void installApkRequest(const QString &apkFile) override; 44 | void screenshot() override; 45 | void showTouch(bool show) override; 46 | 47 | private: 48 | explicit GroupController(QObject *parent = nullptr); 49 | bool isHost(const QString& serial); 50 | QSize getFrameSize(const QString& serial); 51 | 52 | private: 53 | QVector m_devices; 54 | }; 55 | 56 | #endif // GROUPCONTROLLER_H 57 | -------------------------------------------------------------------------------- /QtScrcpy/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "config.h" 10 | #include "dialog.h" 11 | #include "mousetap/mousetap.h" 12 | 13 | static Dialog *g_mainDlg = Q_NULLPTR; 14 | static QtMessageHandler g_oldMessageHandler = Q_NULLPTR; 15 | void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg); 16 | void installTranslator(); 17 | 18 | static QtMsgType g_msgType = QtInfoMsg; 19 | QtMsgType covertLogLevel(const QString &logLevel); 20 | 21 | int main(int argc, char *argv[]) 22 | { 23 | // set env 24 | #ifdef Q_OS_WIN32 25 | qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/win/adb.exe"); 26 | qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server"); 27 | qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap"); 28 | qputenv("QTSCRCPY_CONFIG_PATH", "../../../config"); 29 | #endif 30 | 31 | #ifdef Q_OS_OSX 32 | qputenv("QTSCRCPY_ADB_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/mac/adb"); 33 | qputenv("QTSCRCPY_SERVER_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server"); 34 | qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../../../keymap"); 35 | qputenv("QTSCRCPY_CONFIG_PATH", "../../../../../../config"); 36 | #endif 37 | 38 | #ifdef Q_OS_LINUX 39 | qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb"); 40 | qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server"); 41 | qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap"); 42 | qputenv("QTSCRCPY_CONFIG_PATH", "../../../config"); 43 | #endif 44 | 45 | g_msgType = covertLogLevel(Config::getInstance().getLogLevel()); 46 | 47 | // set on QApplication before 48 | // bug: config path is error on mac 49 | int opengl = Config::getInstance().getDesktopOpenGL(); 50 | if (0 == opengl) { 51 | QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); 52 | } else if (1 == opengl) { 53 | QApplication::setAttribute(Qt::AA_UseOpenGLES); 54 | } else if (2 == opengl) { 55 | QApplication::setAttribute(Qt::AA_UseDesktopOpenGL); 56 | } 57 | 58 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 59 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 60 | 61 | #if (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) 62 | QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); 63 | #endif 64 | #endif 65 | 66 | QSurfaceFormat varFormat = QSurfaceFormat::defaultFormat(); 67 | varFormat.setVersion(2, 0); 68 | varFormat.setProfile(QSurfaceFormat::NoProfile); 69 | /* 70 | varFormat.setSamples(4); 71 | varFormat.setAlphaBufferSize(8); 72 | varFormat.setBlueBufferSize(8); 73 | varFormat.setRedBufferSize(8); 74 | varFormat.setGreenBufferSize(8); 75 | varFormat.setDepthBufferSize(24); 76 | */ 77 | QSurfaceFormat::setDefaultFormat(varFormat); 78 | 79 | g_oldMessageHandler = qInstallMessageHandler(myMessageOutput); 80 | QApplication a(argc, argv); 81 | 82 | // windows下通过qmake VERSION变量或者rc设置版本号和应用名称后,这里可以直接拿到 83 | // mac下拿到的是CFBundleVersion的值 84 | qDebug() << a.applicationVersion(); 85 | qDebug() << a.applicationName(); 86 | 87 | //update version 88 | QStringList versionList = QCoreApplication::applicationVersion().split("."); 89 | if (versionList.size() >= 3) { 90 | QString version = versionList[0] + "." + versionList[1] + "." + versionList[2]; 91 | a.setApplicationVersion(version); 92 | } 93 | 94 | installTranslator(); 95 | #if defined(Q_OS_WIN32) || defined(Q_OS_OSX) 96 | MouseTap::getInstance()->initMouseEventTap(); 97 | #endif 98 | 99 | // load style sheet 100 | QFile file(":/qss/psblack.css"); 101 | if (file.open(QFile::ReadOnly)) { 102 | QString qss = QLatin1String(file.readAll()); 103 | QString paletteColor = qss.mid(20, 7); 104 | qApp->setPalette(QPalette(QColor(paletteColor))); 105 | qApp->setStyleSheet(qss); 106 | file.close(); 107 | } 108 | 109 | qsc::AdbProcess::setAdbPath(Config::getInstance().getAdbPath()); 110 | 111 | g_mainDlg = new Dialog {}; 112 | g_mainDlg->show(); 113 | 114 | qInfo() << QObject::tr("This software is completely open source and free. Use it at your own risk. You can download it at the " 115 | "following address:"); 116 | qInfo() << QString("QtScrcpy %1 ").arg(QCoreApplication::applicationVersion()); 117 | 118 | qInfo() << QObject::tr("If you need more professional batch control mirror software, you can try the following software:"); 119 | qInfo() << QString(QObject::tr("QuickMirror") + " "); 120 | 121 | qInfo() << QObject::tr("If you need more professional game keymap mirror software, you can try the following software:"); 122 | qInfo() << QString(QObject::tr("QuickAssistant") + " "); 123 | 124 | qInfo() << QObject::tr("You can contact me with telegram "); 125 | 126 | int ret = a.exec(); 127 | delete g_mainDlg; 128 | 129 | #if defined(Q_OS_WIN32) || defined(Q_OS_OSX) 130 | MouseTap::getInstance()->quitMouseEventTap(); 131 | #endif 132 | return ret; 133 | } 134 | 135 | void installTranslator() 136 | { 137 | static QTranslator translator; 138 | QLocale locale; 139 | QLocale::Language language = locale.language(); 140 | 141 | if (Config::getInstance().getLanguage() == "zh_CN") { 142 | language = QLocale::Chinese; 143 | } else if (Config::getInstance().getLanguage() == "en_US") { 144 | language = QLocale::English; 145 | } 146 | 147 | QString languagePath = ":/i18n/"; 148 | switch (language) { 149 | case QLocale::Chinese: 150 | languagePath += "zh_CN.qm"; 151 | break; 152 | case QLocale::English: 153 | default: 154 | languagePath += "en_US.qm"; 155 | break; 156 | } 157 | 158 | auto loaded = translator.load(languagePath); 159 | if (!loaded) { 160 | qWarning() << "Failed to load translation file:" << languagePath; 161 | } 162 | qApp->installTranslator(&translator); 163 | } 164 | 165 | QtMsgType covertLogLevel(const QString &logLevel) 166 | { 167 | if ("debug" == logLevel) { 168 | return QtDebugMsg; 169 | } 170 | 171 | if ("info" == logLevel) { 172 | return QtInfoMsg; 173 | } 174 | 175 | if ("warn" == logLevel) { 176 | return QtWarningMsg; 177 | } 178 | 179 | if ("error" == logLevel) { 180 | return QtCriticalMsg; 181 | } 182 | 183 | #ifdef QT_NO_DEBUG 184 | return QtInfoMsg; 185 | #else 186 | return QtDebugMsg; 187 | #endif 188 | } 189 | 190 | void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) 191 | { 192 | if (g_oldMessageHandler) { 193 | g_oldMessageHandler(type, context, msg); 194 | } 195 | 196 | // Is Qt log level higher than warning? 197 | float fLogLevel = g_msgType; 198 | if (QtInfoMsg == g_msgType) { 199 | fLogLevel = QtDebugMsg + 0.5f; 200 | } 201 | float fLogLevel2 = type; 202 | if (QtInfoMsg == type) { 203 | fLogLevel2 = QtDebugMsg + 0.5f; 204 | } 205 | 206 | if (fLogLevel <= fLogLevel2) { 207 | if (g_mainDlg && g_mainDlg->isVisible() && !g_mainDlg->filterLog(msg)) { 208 | g_mainDlg->outLog(msg); 209 | } 210 | } 211 | 212 | if (QtFatalMsg == type) { 213 | //abort(); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /QtScrcpy/render/qyuvopenglwidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "qyuvopenglwidget.h" 6 | 7 | // 存储顶点坐标和纹理坐标 8 | // 存在一起缓存在vbo 9 | // 使用glVertexAttribPointer指定访问方式即可 10 | static const GLfloat coordinate[] = { 11 | // 顶点坐标,存储4个xyz坐标 12 | // 坐标范围为[-1,1],中心点为 0,0 13 | // 二维图像z始终为0 14 | // GL_TRIANGLE_STRIP的绘制方式: 15 | // 使用前3个坐标绘制一个三角形,使用后三个坐标绘制一个三角形,正好为一个矩形 16 | // x y z 17 | -1.0f, 18 | -1.0f, 19 | 0.0f, 20 | 1.0f, 21 | -1.0f, 22 | 0.0f, 23 | -1.0f, 24 | 1.0f, 25 | 0.0f, 26 | 1.0f, 27 | 1.0f, 28 | 0.0f, 29 | 30 | // 纹理坐标,存储4个xy坐标 31 | // 坐标范围为[0,1],左下角为 0,0 32 | 0.0f, 33 | 1.0f, 34 | 1.0f, 35 | 1.0f, 36 | 0.0f, 37 | 0.0f, 38 | 1.0f, 39 | 0.0f 40 | }; 41 | 42 | // 顶点着色器 43 | static const QString s_vertShader = R"( 44 | attribute vec3 vertexIn; // xyz顶点坐标 45 | attribute vec2 textureIn; // xy纹理坐标 46 | varying vec2 textureOut; // 传递给片段着色器的纹理坐标 47 | void main(void) 48 | { 49 | gl_Position = vec4(vertexIn, 1.0); // 1.0表示vertexIn是一个顶点位置 50 | textureOut = textureIn; // 纹理坐标直接传递给片段着色器 51 | } 52 | )"; 53 | 54 | // 片段着色器 55 | static QString s_fragShader = R"( 56 | varying vec2 textureOut; // 由顶点着色器传递过来的纹理坐标 57 | uniform sampler2D textureY; // uniform 纹理单元,利用纹理单元可以使用多个纹理 58 | uniform sampler2D textureU; // sampler2D是2D采样器 59 | uniform sampler2D textureV; // 声明yuv三个纹理单元 60 | void main(void) 61 | { 62 | vec3 yuv; 63 | vec3 rgb; 64 | 65 | // SDL2 BT709_SHADER_CONSTANTS 66 | // https://github.com/spurious/SDL-mirror/blob/4ddd4c445aa059bb127e101b74a8c5b59257fbe2/src/render/opengl/SDL_shaders_gl.c#L102 67 | const vec3 Rcoeff = vec3(1.1644, 0.000, 1.7927); 68 | const vec3 Gcoeff = vec3(1.1644, -0.2132, -0.5329); 69 | const vec3 Bcoeff = vec3(1.1644, 2.1124, 0.000); 70 | 71 | // 根据指定的纹理textureY和坐标textureOut来采样 72 | yuv.x = texture2D(textureY, textureOut).r; 73 | yuv.y = texture2D(textureU, textureOut).r - 0.5; 74 | yuv.z = texture2D(textureV, textureOut).r - 0.5; 75 | 76 | // 采样完转为rgb 77 | // 减少一些亮度 78 | yuv.x = yuv.x - 0.0625; 79 | rgb.r = dot(yuv, Rcoeff); 80 | rgb.g = dot(yuv, Gcoeff); 81 | rgb.b = dot(yuv, Bcoeff); 82 | // 输出颜色值 83 | gl_FragColor = vec4(rgb, 1.0); 84 | } 85 | )"; 86 | 87 | QYUVOpenGLWidget::QYUVOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent) 88 | { 89 | /* 90 | QSurfaceFormat format = QSurfaceFormat::defaultFormat(); 91 | format.setColorSpace(QSurfaceFormat::sRGBColorSpace); 92 | format.setProfile(QSurfaceFormat::CompatibilityProfile); 93 | format.setMajorVersion(3); 94 | format.setMinorVersion(2); 95 | QSurfaceFormat::setDefaultFormat(format); 96 | */ 97 | } 98 | 99 | QYUVOpenGLWidget::~QYUVOpenGLWidget() 100 | { 101 | makeCurrent(); 102 | m_vbo.destroy(); 103 | deInitTextures(); 104 | doneCurrent(); 105 | } 106 | 107 | QSize QYUVOpenGLWidget::minimumSizeHint() const 108 | { 109 | return QSize(50, 50); 110 | } 111 | 112 | QSize QYUVOpenGLWidget::sizeHint() const 113 | { 114 | return size(); 115 | } 116 | 117 | void QYUVOpenGLWidget::setFrameSize(const QSize &frameSize) 118 | { 119 | if (m_frameSize != frameSize) { 120 | m_frameSize = frameSize; 121 | m_needUpdate = true; 122 | // inittexture immediately 123 | repaint(); 124 | } 125 | } 126 | 127 | const QSize &QYUVOpenGLWidget::frameSize() 128 | { 129 | return m_frameSize; 130 | } 131 | 132 | void QYUVOpenGLWidget::updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV) 133 | { 134 | if (m_textureInited) { 135 | updateTexture(m_texture[0], 0, dataY, linesizeY); 136 | updateTexture(m_texture[1], 1, dataU, linesizeU); 137 | updateTexture(m_texture[2], 2, dataV, linesizeV); 138 | update(); 139 | } 140 | } 141 | 142 | void QYUVOpenGLWidget::initializeGL() 143 | { 144 | initializeOpenGLFunctions(); 145 | glDisable(GL_DEPTH_TEST); 146 | 147 | // 顶点缓冲对象初始化 148 | m_vbo.create(); 149 | m_vbo.bind(); 150 | m_vbo.allocate(coordinate, sizeof(coordinate)); 151 | initShader(); 152 | // 设置背景清理色为黑色 153 | glClearColor(0.0, 0.0, 0.0, 0.0); 154 | // 清理颜色背景 155 | glClear(GL_COLOR_BUFFER_BIT); 156 | } 157 | 158 | void QYUVOpenGLWidget::paintGL() 159 | { 160 | m_shaderProgram.bind(); 161 | 162 | if (m_needUpdate) { 163 | deInitTextures(); 164 | initTextures(); 165 | m_needUpdate = false; 166 | } 167 | 168 | if (m_textureInited) { 169 | glActiveTexture(GL_TEXTURE0); 170 | glBindTexture(GL_TEXTURE_2D, m_texture[0]); 171 | 172 | glActiveTexture(GL_TEXTURE1); 173 | glBindTexture(GL_TEXTURE_2D, m_texture[1]); 174 | 175 | glActiveTexture(GL_TEXTURE2); 176 | glBindTexture(GL_TEXTURE_2D, m_texture[2]); 177 | 178 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 179 | } 180 | 181 | m_shaderProgram.release(); 182 | } 183 | 184 | void QYUVOpenGLWidget::resizeGL(int width, int height) 185 | { 186 | glViewport(0, 0, width, height); 187 | repaint(); 188 | } 189 | 190 | void QYUVOpenGLWidget::initShader() 191 | { 192 | // opengles的float、int等要手动指定精度 193 | if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES)) { 194 | s_fragShader.prepend(R"( 195 | precision mediump int; 196 | precision mediump float; 197 | )"); 198 | } 199 | m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, s_vertShader); 200 | m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, s_fragShader); 201 | m_shaderProgram.link(); 202 | m_shaderProgram.bind(); 203 | 204 | // 指定顶点坐标在vbo中的访问方式 205 | // 参数解释:顶点坐标在shader中的参数名称,顶点坐标为float,起始偏移为0,顶点坐标类型为vec3,步幅为3个float 206 | m_shaderProgram.setAttributeBuffer("vertexIn", GL_FLOAT, 0, 3, 3 * sizeof(float)); 207 | // 启用顶点属性 208 | m_shaderProgram.enableAttributeArray("vertexIn"); 209 | 210 | // 指定纹理坐标在vbo中的访问方式 211 | // 参数解释:纹理坐标在shader中的参数名称,纹理坐标为float,起始偏移为12个float(跳过前面存储的12个顶点坐标),纹理坐标类型为vec2,步幅为2个float 212 | m_shaderProgram.setAttributeBuffer("textureIn", GL_FLOAT, 12 * sizeof(float), 2, 2 * sizeof(float)); 213 | m_shaderProgram.enableAttributeArray("textureIn"); 214 | 215 | // 关联片段着色器中的纹理单元和opengl中的纹理单元(opengl一般提供16个纹理单元) 216 | m_shaderProgram.setUniformValue("textureY", 0); 217 | m_shaderProgram.setUniformValue("textureU", 1); 218 | m_shaderProgram.setUniformValue("textureV", 2); 219 | } 220 | 221 | void QYUVOpenGLWidget::initTextures() 222 | { 223 | // 创建纹理 224 | glGenTextures(1, &m_texture[0]); 225 | glBindTexture(GL_TEXTURE_2D, m_texture[0]); 226 | // 设置纹理缩放时的策略 227 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 228 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 229 | // 设置st方向上纹理超出坐标时的显示策略 230 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 231 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 232 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width(), m_frameSize.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr); 233 | 234 | glGenTextures(1, &m_texture[1]); 235 | glBindTexture(GL_TEXTURE_2D, m_texture[1]); 236 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 237 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 238 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 239 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 240 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width() / 2, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr); 241 | 242 | glGenTextures(1, &m_texture[2]); 243 | glBindTexture(GL_TEXTURE_2D, m_texture[2]); 244 | // 设置纹理缩放时的策略 245 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 246 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 247 | // 设置st方向上纹理超出坐标时的显示策略 248 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 249 | glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 250 | glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width() / 2, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr); 251 | 252 | m_textureInited = true; 253 | } 254 | 255 | void QYUVOpenGLWidget::deInitTextures() 256 | { 257 | if (QOpenGLFunctions::isInitialized(QOpenGLFunctions::d_ptr)) { 258 | glDeleteTextures(3, m_texture); 259 | } 260 | 261 | memset(m_texture, 0, sizeof(m_texture)); 262 | m_textureInited = false; 263 | } 264 | 265 | void QYUVOpenGLWidget::updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride) 266 | { 267 | if (!pixels) 268 | return; 269 | 270 | QSize size = 0 == textureType ? m_frameSize : m_frameSize / 2; 271 | 272 | makeCurrent(); 273 | glBindTexture(GL_TEXTURE_2D, texture); 274 | glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast(stride)); 275 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.width(), size.height(), GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels); 276 | doneCurrent(); 277 | } 278 | -------------------------------------------------------------------------------- /QtScrcpy/render/qyuvopenglwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef QYUVOPENGLWIDGET_H 2 | #define QYUVOPENGLWIDGET_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class QYUVOpenGLWidget 9 | : public QOpenGLWidget 10 | , protected QOpenGLFunctions 11 | { 12 | Q_OBJECT 13 | public: 14 | explicit QYUVOpenGLWidget(QWidget *parent = nullptr); 15 | virtual ~QYUVOpenGLWidget() override; 16 | 17 | QSize minimumSizeHint() const override; 18 | QSize sizeHint() const override; 19 | 20 | void setFrameSize(const QSize &frameSize); 21 | const QSize &frameSize(); 22 | void updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV); 23 | 24 | protected: 25 | void initializeGL() override; 26 | void paintGL() override; 27 | void resizeGL(int width, int height) override; 28 | 29 | private: 30 | void initShader(); 31 | void initTextures(); 32 | void deInitTextures(); 33 | void updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride); 34 | 35 | private: 36 | // 视频帧尺寸 37 | QSize m_frameSize = { -1, -1 }; 38 | bool m_needUpdate = false; 39 | bool m_textureInited = false; 40 | 41 | // 顶点缓冲对象(Vertex Buffer Objects, VBO):默认即为VertexBuffer(GL_ARRAY_BUFFER)类型 42 | QOpenGLBuffer m_vbo; 43 | 44 | // 着色器程序:编译链接着色器 45 | QOpenGLShaderProgram m_shaderProgram; 46 | 47 | // YUV纹理,用于生成纹理贴图 48 | GLuint m_texture[3] = { 0 }; 49 | }; 50 | 51 | #endif // QYUVOPENGLWIDGET_H 52 | -------------------------------------------------------------------------------- /QtScrcpy/res/Info_Mac.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | zh-Hans 7 | CFBundleExecutable 8 | QtScrcpy 9 | CFBundleGetInfoString 10 | Created by rankun 11 | CFBundleIconFile 12 | QtScrcpy 13 | CFBundleIdentifier 14 | rankun.QtScrcpy 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | QtScrcpy 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | ${BUNDLE_VERSION} 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | CFBundleVersion 28 | ${BUNDLE_VERSION} 29 | LSMinimumSystemVersion 30 | 10.10 31 | NSAppleEventsUsageDescription 32 | 33 | NSHumanReadableCopyright 34 | Copyright © 2018-2038 rankun. All rights reserved. 35 | NSMainStoryboardFile 36 | Main 37 | NSPrincipalClass 38 | NSApplication 39 | NSSupportsAutomaticGraphicsSwitching 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /QtScrcpy/res/QtScrcpy.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/QtScrcpy.icns -------------------------------------------------------------------------------- /QtScrcpy/res/QtScrcpy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/QtScrcpy.ico -------------------------------------------------------------------------------- /QtScrcpy/res/QtScrcpy.rc: -------------------------------------------------------------------------------- 1 | #include "winres.h" 2 | 3 | IDI_ICON1 ICON "QtScrcpy.ico" 4 | // GB2312编码的话,在中文系统上打包FileDescription可以显示中文 5 | // 在github action(英文系统)打包后FileDescription是乱码,utf8编码也不行。。 6 | VS_VERSION_INFO VERSIONINFO 7 | FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH 8 | PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH 9 | FILEFLAGSMASK 0x3fL 10 | #ifdef _DEBUG 11 | FILEFLAGS 0x1L 12 | #else 13 | FILEFLAGS 0x0L 14 | #endif 15 | FILEOS 0x40004L 16 | FILETYPE 0x1L 17 | FILESUBTYPE 0x0L 18 | BEGIN 19 | BLOCK "StringFileInfo" 20 | BEGIN 21 | BLOCK "080404b0" 22 | BEGIN 23 | VALUE "CompanyName", "RanKun" 24 | VALUE "FileDescription", "Android real-time display control software" 25 | VALUE "FileVersion", VERSION_RC_STR 26 | VALUE "LegalCopyright", "Copyright (C) RanKun 2018-2038. All rights reserved." 27 | VALUE "ProductName", "QtScrcpy" 28 | VALUE "ProductVersion", VERSION_RC_STR 29 | END 30 | END 31 | BLOCK "VarFileInfo" 32 | BEGIN 33 | VALUE "Translation", 0x804, 1200 34 | END 35 | END 36 | -------------------------------------------------------------------------------- /QtScrcpy/res/font/fontawesome-webfont.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/font/fontawesome-webfont.pdf -------------------------------------------------------------------------------- /QtScrcpy/res/font/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/font/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /QtScrcpy/res/i18n/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 声明ts文件 2 | set(QC_TS_FILES 3 | ${CMAKE_CURRENT_SOURCE_DIR}/zh_CN.ts 4 | ${CMAKE_CURRENT_SOURCE_DIR}/en_US.ts 5 | ) 6 | # 设置qm文件生成目录 7 | set_source_files_properties(${QC_TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}") 8 | # 引入LinguistTools 9 | find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools REQUIRED) 10 | find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools REQUIRED) 11 | 12 | # qt5_create_translation会依次执行 lupdate更新ts、lrelease更新qm 13 | qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../.. ${QC_TS_FILES}) 14 | # 自定义目标依赖QM_FILES,否则不会生成qm文件 15 | add_custom_target(QC_QM_GENERATOR DEPENDS ${QM_FILES}) 16 | 17 | # qt5_create_translation的bug:cmake clean的时候会删除翻译好的ts文件,导致翻译丢失 18 | # (qt官方说qt6没问题,只用qt6的可以考虑qt5_create_translation) 19 | # 网上查到的CLEAN_NO_CUSTOM办法只能在makefile生成器下生效,解决不了问题 20 | # https://cmake.org/cmake/help/latest/prop_dir/CLEAN_NO_CUSTOM.html 21 | # set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM true) 22 | # 目前唯一的解决办法是每次clean后,都手动在git中恢复一下ts文件 23 | 24 | #[[ 25 | 总结: 26 | cmake qt项目下,利用cmake脚本有三种方式处理翻译: 27 | 1. 完全使用qt自带的cmake LinguistTools脚本:qt5_create_translation&qt5_add_translation 28 | 这两个脚本都满足不了需求: 29 | qt5_add_translation只能根据已有ts文件生成qm文件(lrelease),不能更新ts文件(lupdate) 30 | qt5_create_translation在cmake clean的时候会删除翻译好的ts文件,导致翻译丢失 31 | 32 | 2. cmake add_custom_command + cmake LinguistTools脚本(其实qt5_create_translation内部使用的也是add_custom_command) 33 | 例如add_custom_command执行lupdate,配合qt5_add_translation更新qm, 34 | 参考:https://github.com/maratnek/QtFirstProgrammCMake/blob/2c93b59e2ba85ff6ee0e727487e14003381687d3/CMakeLists.txt 35 | 36 | 3. 完全使用cmake命令来执行lupdate和lrelease 37 | 例如add_custom_command/add_custom_target/execute_process都可以实现执行lupdate和lrelease命令 38 | 39 | 上面3个方案都有一个共同问题:就是翻译文件处理都是和编译绑定在一起的,每次编译都会检测执行,实际的翻译工作是所有 40 | 编程工作都完成以后,统一执行一次lupdate、翻译、lrelease就可以了,不应该和编译绑定在一起 41 | 所以写两个shell脚本lupdate.sh和lrelease.sh来处理比较合适,其实非常简单: 42 | 1. 更新ts:lupdate -no-obsolete ./QtScrcpy -ts ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts 43 | 2. 手动翻译ts 44 | 3. 发布:lrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts 45 | 46 | 参考文档 47 | 1. qt知道qt5_create_translation的bug,但是不肯解决,只确定了qt6没问题 https://bugreports.qt.io/browse/QTBUG-96549 48 | 2. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-add-translation.html 49 | 3. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-create-translation.html 50 | 4. execute_process 参考:https://blog.csdn.net/u010255072/article/details/120326833 51 | 5. add_custom_target 参考:https://www.cnblogs.com/apocelipes/p/14355460.html -------------------------------------------------------------------------------- /QtScrcpy/res/i18n/en_US.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/i18n/en_US.qm -------------------------------------------------------------------------------- /QtScrcpy/res/i18n/en_US.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dialog 6 | 7 | show 8 | show 9 | 10 | 11 | quit 12 | quit 13 | 14 | 15 | original 16 | original 17 | 18 | 19 | no lock 20 | no lock 21 | 22 | 23 | Notice 24 | Notice 25 | 26 | 27 | Hidden here! 28 | Hidden here! 29 | 30 | 31 | select path 32 | select path 33 | 34 | 35 | Clear History 36 | Clear History 37 | 38 | 39 | 40 | QObject 41 | 42 | This software is completely open source and free. Use it at your own risk. You can download it at the following address: 43 | This software is completely open source and free. Use it at your own risk. You can download it at the following address: 44 | 45 | 46 | QuickMirror 47 | QuickMirror 48 | 49 | 50 | If you need more professional batch control mirror software, you can try the following software: 51 | If you need more professional batch control mirror software, you can try the following software: 52 | 53 | 54 | If you need more professional game keymap mirror software, you can try the following software: 55 | If you need more professional game keymap mirror software, you can try the following software: 56 | 57 | 58 | QuickAssistant 59 | QuickAssistant 60 | 61 | 62 | You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1> 63 | You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1> 64 | 65 | 66 | 67 | ToolForm 68 | 69 | Tool 70 | Tool 71 | 72 | 73 | full screen 74 | full screen 75 | 76 | 77 | expand notify 78 | expand notify 79 | 80 | 81 | touch switch 82 | touch switch 83 | 84 | 85 | close screen 86 | close screen 87 | 88 | 89 | power 90 | power 91 | 92 | 93 | volume up 94 | volume up 95 | 96 | 97 | volume down 98 | volume down 99 | 100 | 101 | app switch 102 | app switch 103 | 104 | 105 | menu 106 | menu 107 | 108 | 109 | home 110 | home 111 | 112 | 113 | return 114 | return 115 | 116 | 117 | screen shot 118 | screen shot 119 | 120 | 121 | open screen 122 | open screen 123 | 124 | 125 | group control 126 | group control 127 | 128 | 129 | 130 | VideoForm 131 | 132 | file does not exist 133 | file does not exist 134 | 135 | 136 | 137 | Widget 138 | 139 | Wireless 140 | Wireless 141 | 142 | 143 | wireless connect 144 | wireless connect 145 | 146 | 147 | wireless disconnect 148 | wireless disconnect 149 | 150 | 151 | Start Config 152 | Start Config 153 | 154 | 155 | select path 156 | select path 157 | 158 | 159 | record format: 160 | record format: 161 | 162 | 163 | record screen 164 | record screen 165 | 166 | 167 | frameless 168 | frameless 169 | 170 | 171 | Use Simple Mode 172 | Use Simple Mode 173 | Use Simple Mode 174 | 175 | 176 | Simple Mode 177 | Simple Mode 178 | Simple Mode 179 | 180 | 181 | WIFI Connect 182 | WIFI Connect 183 | WIFI Connect 184 | 185 | 186 | USB Connect 187 | USB Connect 188 | USB Connect 189 | 190 | 191 | Double click to connect: 192 | Double click to connect: 193 | 194 | 195 | lock orientation: 196 | lock orientation: 197 | 198 | 199 | show fps 200 | show fps 201 | 202 | 203 | stay awake 204 | stay awake 205 | 206 | 207 | device name: 208 | device name: 209 | device name: 210 | 211 | 212 | update name 213 | update name 214 | update name 215 | 216 | 217 | stop all server 218 | stop all server 219 | 220 | 221 | adb command: 222 | adb command: 223 | 224 | 225 | terminate 226 | terminate 227 | 228 | 229 | execute 230 | execute 231 | 232 | 233 | clear 234 | clear 235 | 236 | 237 | reverse connection 238 | reverse connection 239 | 240 | 241 | background record 242 | background record 243 | 244 | 245 | screen-off 246 | screen-off 247 | 248 | 249 | apply 250 | apply 251 | 252 | 253 | max size: 254 | max size: 255 | 256 | 257 | always on top 258 | always on top 259 | 260 | 261 | refresh script 262 | refresh script 263 | 264 | 265 | get device IP 266 | get device IP 267 | 268 | 269 | USB line 270 | USB line 271 | 272 | 273 | stop server 274 | stop server 275 | 276 | 277 | start server 278 | start server 279 | 280 | 281 | device serial: 282 | device serial: 283 | 284 | 285 | bit rate: 286 | bit rate: 287 | 288 | 289 | start adbd 290 | start adbd 291 | 292 | 293 | refresh devices 294 | refresh devices 295 | 296 | 297 | install sndcpy 298 | install sndcpy 299 | 300 | 301 | start audio 302 | start audio 303 | 304 | 305 | stop audio 306 | stop audio 307 | 308 | 309 | auto update 310 | auto update 311 | 312 | 313 | show toolbar 314 | show toolbar 315 | 316 | 317 | record save path: 318 | record save path: 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /QtScrcpy/res/i18n/zh_CN.qm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/i18n/zh_CN.qm -------------------------------------------------------------------------------- /QtScrcpy/res/i18n/zh_CN.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dialog 6 | 7 | show 8 | 显示 9 | 10 | 11 | quit 12 | 退出 13 | 14 | 15 | original 16 | 原始 17 | 18 | 19 | no lock 20 | 不锁定 21 | 22 | 23 | Notice 24 | 提示 25 | 26 | 27 | Hidden here! 28 | 安卓录屏程序隐藏在这! 29 | 30 | 31 | select path 32 | 选择路径 33 | 34 | 35 | Clear History 36 | 清理历史 37 | 38 | 39 | 40 | QObject 41 | 42 | This software is completely open source and free. Use it at your own risk. You can download it at the following address: 43 | 本软件完全开源免费,作者不对使用该软件产生的一切后果负责。你可以在以下地址下载: 44 | 45 | 46 | QuickMirror 47 | 极限投屏 48 | 49 | 50 | If you need more professional batch control mirror software, you can try the following software: 51 | 如果你需要更专业的批量控制投屏软件,你可以尝试下面软件: 52 | 53 | 54 | If you need more professional game keymap mirror software, you can try the following software: 55 | 如果你需要更专业的游戏映射投屏软件,你可以尝试下面软件: 56 | 57 | 58 | QuickAssistant 59 | 极限手游助手 60 | 61 | 62 | You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1> 63 | 你可以通过QQ群联系我 <901736468> 64 | 65 | 66 | 67 | ToolForm 68 | 69 | Tool 70 | 工具 71 | 72 | 73 | full screen 74 | 全屏 75 | 76 | 77 | expand notify 78 | 下拉通知 79 | 80 | 81 | touch switch 82 | 触摸显示开关 83 | 84 | 85 | close screen 86 | 关闭屏幕 87 | 88 | 89 | power 90 | 电源 91 | 92 | 93 | volume up 94 | 音量加 95 | 96 | 97 | volume down 98 | 音量减 99 | 100 | 101 | app switch 102 | 切换应用 103 | 104 | 105 | menu 106 | 菜单 107 | 108 | 109 | home 110 | 主界面 111 | 112 | 113 | return 114 | 返回 115 | 116 | 117 | screen shot 118 | 截图 119 | 120 | 121 | open screen 122 | 打开屏幕 123 | 124 | 125 | group control 126 | 群控 127 | 128 | 129 | 130 | VideoForm 131 | 132 | file does not exist 133 | 文件不存在 134 | 135 | 136 | 137 | Widget 138 | 139 | Wireless 140 | 无线 141 | 142 | 143 | wireless connect 144 | 无线连接 145 | 146 | 147 | wireless disconnect 148 | 无线断开 149 | 150 | 151 | Start Config 152 | 启动配置 153 | 154 | 155 | select path 156 | 选择路径 157 | 158 | 159 | record format: 160 | 录制格式: 161 | 162 | 163 | record screen 164 | 录制屏幕 165 | 166 | 167 | frameless 168 | 无边框 169 | 170 | 171 | Use Simple Mode 172 | 启用精简模式 173 | 启用精简模式 174 | 175 | 176 | Simple Mode 177 | 精简模式 178 | 精简模式 179 | 180 | 181 | WIFI Connect 182 | 一键WIFI连接 183 | 一键WIFI连接 184 | 185 | 186 | USB Connect 187 | 一键USB连接 188 | 一键USB连接 189 | 190 | 191 | Double click to connect: 192 | 双击连接: 193 | 194 | 195 | lock orientation: 196 | 锁定方向: 197 | 198 | 199 | show fps 200 | 显示fps 201 | 202 | 203 | stay awake 204 | 保持唤醒 205 | 206 | 207 | device name: 208 | 设备名称: 209 | 设备名称: 210 | 211 | 212 | update name 213 | 更新设置名称 214 | 更新设置名称 215 | 216 | 217 | stop all server 218 | 停止所有服务 219 | 220 | 221 | adb command: 222 | adb命令: 223 | 224 | 225 | terminate 226 | 终止 227 | 228 | 229 | execute 230 | 执行 231 | 232 | 233 | clear 234 | 清理 235 | 236 | 237 | reverse connection 238 | 反向连接 239 | 240 | 241 | background record 242 | 后台录制 243 | 244 | 245 | screen-off 246 | 自动息屏 247 | 248 | 249 | apply 250 | 应用脚本 251 | 252 | 253 | max size: 254 | 最大尺寸: 255 | 256 | 257 | always on top 258 | 窗口置顶 259 | 260 | 261 | refresh script 262 | 刷新脚本 263 | 264 | 265 | get device IP 266 | 获取设备IP 267 | 268 | 269 | USB line 270 | USB线 271 | 272 | 273 | stop server 274 | 停止服务 275 | 276 | 277 | start server 278 | 启动服务 279 | 280 | 281 | device serial: 282 | 设备序列号: 283 | 284 | 285 | bit rate: 286 | 比特率: 287 | 288 | 289 | start adbd 290 | 启动adbd 291 | 292 | 293 | refresh devices 294 | 刷新设备列表 295 | 296 | 297 | install sndcpy 298 | 安装sndcpy 299 | 300 | 301 | start audio 302 | 开始音频 303 | 304 | 305 | stop audio 306 | 停止音频 307 | 308 | 309 | auto update 310 | 自动刷新 311 | 312 | 313 | show toolbar 314 | 显示工具栏 315 | 316 | 317 | record save path: 318 | 录像保存路径 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /QtScrcpy/res/image/tray/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/image/tray/logo.png -------------------------------------------------------------------------------- /QtScrcpy/res/image/videoform/phone-h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/image/videoform/phone-h.png -------------------------------------------------------------------------------- /QtScrcpy/res/image/videoform/phone-v.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/image/videoform/phone-v.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/add_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/add_bottom.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/add_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/add_left.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/add_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/add_right.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/add_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/add_top.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/branch_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/branch_close.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/branch_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/branch_open.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/calendar_nextmonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/calendar_nextmonth.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/calendar_prevmonth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/calendar_prevmonth.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/checkbox_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/checkbox_checked.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/checkbox_checked_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/checkbox_checked_disable.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/checkbox_parcial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/checkbox_parcial.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/checkbox_parcial_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/checkbox_parcial_disable.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/checkbox_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/checkbox_unchecked.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/checkbox_unchecked_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/checkbox_unchecked_disable.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/radiobutton_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/radiobutton_checked.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/radiobutton_checked_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/radiobutton_checked_disable.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/radiobutton_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/radiobutton_unchecked.png -------------------------------------------------------------------------------- /QtScrcpy/res/qss/psblack/radiobutton_unchecked_disable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/res/qss/psblack/radiobutton_unchecked_disable.png -------------------------------------------------------------------------------- /QtScrcpy/res/res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | font/fontawesome-webfont.ttf 4 | image/videoform/phone-h.png 5 | image/videoform/phone-v.png 6 | qss/psblack.css 7 | qss/psblack/add_bottom.png 8 | qss/psblack/add_left.png 9 | qss/psblack/add_right.png 10 | qss/psblack/add_top.png 11 | qss/psblack/branch_close.png 12 | qss/psblack/branch_open.png 13 | qss/psblack/calendar_nextmonth.png 14 | qss/psblack/calendar_prevmonth.png 15 | qss/psblack/checkbox_checked.png 16 | qss/psblack/checkbox_checked_disable.png 17 | qss/psblack/checkbox_parcial.png 18 | qss/psblack/checkbox_parcial_disable.png 19 | qss/psblack/checkbox_unchecked.png 20 | qss/psblack/checkbox_unchecked_disable.png 21 | qss/psblack/radiobutton_checked.png 22 | qss/psblack/radiobutton_checked_disable.png 23 | qss/psblack/radiobutton_unchecked.png 24 | qss/psblack/radiobutton_unchecked_disable.png 25 | i18n/en_US.qm 26 | i18n/zh_CN.qm 27 | image/tray/logo.png 28 | 29 | 30 | -------------------------------------------------------------------------------- /QtScrcpy/sndcpy/sndcpy.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/QtScrcpy/sndcpy/sndcpy.apk -------------------------------------------------------------------------------- /QtScrcpy/sndcpy/sndcpy.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo Begin Runing... 4 | set SNDCPY_PORT=28200 5 | set SNDCPY_APK=sndcpy.apk 6 | set ADB=adb.exe 7 | 8 | if not "%1"=="" ( 9 | set serial=-s %1 10 | ) 11 | if not "%2"=="" ( 12 | set SNDCPY_PORT=%2 13 | ) 14 | 15 | echo Waiting for device %1... 16 | %ADB% %serial% wait-for-device || goto :error 17 | echo Find device %1 18 | 19 | for /f "delims=" %%i in ('%ADB% %serial% shell pm path com.rom1v.sndcpy') do set sndcpy_installed=%%i 20 | if "%sndcpy_installed%"=="" ( 21 | echo Install %SNDCPY_APK%... 22 | %ADB% %serial% uninstall com.rom1v.sndcpy || echo uninstall failed 23 | %ADB% %serial% install -t -r -g %SNDCPY_APK% || goto :error 24 | echo Install %SNDCPY_APK% success 25 | ) 26 | 27 | echo Request PROJECT_MEDIA permission... 28 | %ADB% %serial% shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow 29 | 30 | echo Forward port %SNDCPY_PORT%... 31 | %ADB% %serial% forward tcp:%SNDCPY_PORT% localabstract:sndcpy || goto :error 32 | 33 | echo Start %SNDCPY_APK%... 34 | %ADB% %serial% shell am start com.rom1v.sndcpy/.MainActivity || goto :error 35 | 36 | :check_start 37 | echo Waiting %SNDCPY_APK% start... 38 | ::timeout /T 1 /NOBREAK > nul 39 | %ADB% %serial% shell sleep 0.1 40 | for /f "delims=" %%i in ("%ADB% shell 'ps | grep com.rom1v.sndcpy'") do set sndcpy_started=%%i 41 | if "%sndcpy_started%"=="" ( 42 | goto :check_start 43 | ) 44 | echo %SNDCPY_APK% started... 45 | 46 | echo Ready playing... 47 | ::vlc.exe -Idummy --demux rawaud --network-caching=0 --play-and-exit tcp://localhost:%SNDCPY_PORT% 48 | ::ffplay.exe -nodisp -autoexit -probesize 32 -sync ext -f s16le -ar 48k -ac 2 tcp://localhost:%SNDCPY_PORT% 49 | goto :EOF 50 | 51 | :error 52 | echo Failed with error #%errorlevel%. 53 | exit /b %errorlevel% 54 | -------------------------------------------------------------------------------- /QtScrcpy/sndcpy/sndcpy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo Begin Runing... 4 | SNDCPY_PORT=28200 5 | SNDCPY_APK=sndcpy.apk 6 | ADB=./adb 7 | 8 | serial= 9 | if [[ $# -ge 2 ]] 10 | then 11 | serial="-s $1" 12 | SNDCPY_PORT=$2 13 | fi 14 | 15 | echo "Waiting for device $1..." 16 | $ADB $serial wait-for-device 17 | echo "Find device $1" 18 | 19 | sndcpy_installed=$($ADB $serial shell pm path com.rom1v.sndcpy) 20 | if [[ $sndcpy_installed == "" ]]; then 21 | echo Install $SNDCPY_APK... 22 | $ADB $serial uninstall com.rom1v.sndcpy || echo uninstall failed 23 | $ADB $serial install -t -r -g $SNDCPY_APK 24 | echo Install $SNDCPY_APK success 25 | fi 26 | 27 | echo Request PROJECT_MEDIA permission... 28 | $ADB $serial shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow 29 | 30 | echo Forward port $SNDCPY_PORT... 31 | $ADB $serial forward tcp:$SNDCPY_PORT localabstract:sndcpy 32 | 33 | echo Start $SNDCPY_APK... 34 | $ADB $serial shell am start com.rom1v.sndcpy/.MainActivity 35 | 36 | while ((1)) 37 | do 38 | echo Waiting $SNDCPY_APK start... 39 | sleep 0.1 40 | sndcpy_started=$($ADB shell 'ps | grep com.rom1v.sndcpy') 41 | if [[ $sndcpy_started != "" ]]; then 42 | break 43 | fi 44 | done 45 | 46 | echo Ready playing... -------------------------------------------------------------------------------- /QtScrcpy/ui/dialog.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOG_H 2 | #define DIALOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | #include "adbprocess.h" 14 | #include "../QtScrcpyCore/include/QtScrcpyCore.h" 15 | #include "audio/audiooutput.h" 16 | 17 | namespace Ui 18 | { 19 | class Widget; 20 | } 21 | 22 | class QYUVOpenGLWidget; 23 | class Dialog : public QWidget 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | explicit Dialog(QWidget *parent = 0); 29 | ~Dialog(); 30 | 31 | void outLog(const QString &log, bool newLine = true); 32 | bool filterLog(const QString &log); 33 | void getIPbyIp(); 34 | 35 | private slots: 36 | void onDeviceConnected(bool success, const QString& serial, const QString& deviceName, const QSize& size); 37 | void onDeviceDisconnected(QString serial); 38 | 39 | void on_updateDevice_clicked(); 40 | void on_startServerBtn_clicked(); 41 | void on_stopServerBtn_clicked(); 42 | void on_wirelessConnectBtn_clicked(); 43 | void on_startAdbdBtn_clicked(); 44 | void on_getIPBtn_clicked(); 45 | void on_wirelessDisConnectBtn_clicked(); 46 | void on_selectRecordPathBtn_clicked(); 47 | void on_recordPathEdt_textChanged(const QString &arg1); 48 | void on_adbCommandBtn_clicked(); 49 | void on_stopAdbBtn_clicked(); 50 | void on_clearOut_clicked(); 51 | void on_stopAllServerBtn_clicked(); 52 | void on_refreshGameScriptBtn_clicked(); 53 | void on_applyScriptBtn_clicked(); 54 | void on_recordScreenCheck_clicked(bool checked); 55 | void on_usbConnectBtn_clicked(); 56 | void on_wifiConnectBtn_clicked(); 57 | void on_connectedPhoneList_itemDoubleClicked(QListWidgetItem *item); 58 | void on_updateNameBtn_clicked(); 59 | void on_useSingleModeCheck_clicked(); 60 | void on_serialBox_currentIndexChanged(const QString &arg1); 61 | 62 | void on_startAudioBtn_clicked(); 63 | 64 | void on_stopAudioBtn_clicked(); 65 | 66 | void on_installSndcpyBtn_clicked(); 67 | 68 | void on_autoUpdatecheckBox_toggled(bool checked); 69 | 70 | void showIpEditMenu(const QPoint &pos); 71 | 72 | private: 73 | bool checkAdbRun(); 74 | void initUI(); 75 | void updateBootConfig(bool toView = true); 76 | void execAdbCmd(); 77 | void delayMs(int ms); 78 | QString getGameScript(const QString &fileName); 79 | void slotActivated(QSystemTrayIcon::ActivationReason reason); 80 | int findDeviceFromeSerialBox(bool wifi); 81 | quint32 getBitRate(); 82 | const QString &getServerPath(); 83 | void loadIpHistory(); 84 | void saveIpHistory(const QString &ip); 85 | 86 | protected: 87 | void closeEvent(QCloseEvent *event); 88 | 89 | private: 90 | Ui::Widget *ui; 91 | qsc::AdbProcess m_adb; 92 | QSystemTrayIcon *m_hideIcon; 93 | QMenu *m_menu; 94 | QAction *m_showWindow; 95 | QAction *m_quit; 96 | AudioOutput m_audioOutput; 97 | QTimer m_autoUpdatetimer; 98 | }; 99 | 100 | #endif // DIALOG_H 101 | -------------------------------------------------------------------------------- /QtScrcpy/ui/toolform.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "iconhelper.h" 7 | #include "toolform.h" 8 | #include "ui_toolform.h" 9 | #include "videoform.h" 10 | #include "../groupcontroller/groupcontroller.h" 11 | 12 | ToolForm::ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos) : MagneticWidget(adsorbWidget, adsorbPos), ui(new Ui::ToolForm) 13 | { 14 | ui->setupUi(this); 15 | setWindowFlags(windowFlags() | Qt::FramelessWindowHint); 16 | //setWindowFlags(windowFlags() & ~Qt::WindowMinMaxButtonsHint); 17 | 18 | updateGroupControl(); 19 | 20 | initStyle(); 21 | } 22 | 23 | ToolForm::~ToolForm() 24 | { 25 | delete ui; 26 | } 27 | 28 | void ToolForm::setSerial(const QString &serial) 29 | { 30 | m_serial = serial; 31 | } 32 | 33 | bool ToolForm::isHost() 34 | { 35 | return m_isHost; 36 | } 37 | 38 | void ToolForm::initStyle() 39 | { 40 | IconHelper::Instance()->SetIcon(ui->fullScreenBtn, QChar(0xf0b2), 15); 41 | IconHelper::Instance()->SetIcon(ui->menuBtn, QChar(0xf096), 15); 42 | IconHelper::Instance()->SetIcon(ui->homeBtn, QChar(0xf1db), 15); 43 | //IconHelper::Instance()->SetIcon(ui->returnBtn, QChar(0xf104), 15); 44 | IconHelper::Instance()->SetIcon(ui->returnBtn, QChar(0xf053), 15); 45 | IconHelper::Instance()->SetIcon(ui->appSwitchBtn, QChar(0xf24d), 15); 46 | IconHelper::Instance()->SetIcon(ui->volumeUpBtn, QChar(0xf028), 15); 47 | IconHelper::Instance()->SetIcon(ui->volumeDownBtn, QChar(0xf027), 15); 48 | IconHelper::Instance()->SetIcon(ui->openScreenBtn, QChar(0xf06e), 15); 49 | IconHelper::Instance()->SetIcon(ui->closeScreenBtn, QChar(0xf070), 15); 50 | IconHelper::Instance()->SetIcon(ui->powerBtn, QChar(0xf011), 15); 51 | IconHelper::Instance()->SetIcon(ui->expandNotifyBtn, QChar(0xf103), 15); 52 | IconHelper::Instance()->SetIcon(ui->screenShotBtn, QChar(0xf0c4), 15); 53 | IconHelper::Instance()->SetIcon(ui->touchBtn, QChar(0xf111), 15); 54 | IconHelper::Instance()->SetIcon(ui->groupControlBtn, QChar(0xf0c0), 15); 55 | } 56 | 57 | void ToolForm::updateGroupControl() 58 | { 59 | if (m_isHost) { 60 | ui->groupControlBtn->setStyleSheet("color: red"); 61 | } else { 62 | ui->groupControlBtn->setStyleSheet("color: green"); 63 | } 64 | 65 | GroupController::instance().updateDeviceState(m_serial); 66 | } 67 | 68 | void ToolForm::mousePressEvent(QMouseEvent *event) 69 | { 70 | if (event->button() == Qt::LeftButton) { 71 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 72 | m_dragPosition = event->globalPos() - frameGeometry().topLeft(); 73 | #else 74 | m_dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft(); 75 | #endif 76 | event->accept(); 77 | } 78 | } 79 | 80 | void ToolForm::mouseReleaseEvent(QMouseEvent *event) 81 | { 82 | Q_UNUSED(event) 83 | } 84 | 85 | void ToolForm::mouseMoveEvent(QMouseEvent *event) 86 | { 87 | if (event->buttons() & Qt::LeftButton) { 88 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 89 | move(event->globalPos() - m_dragPosition); 90 | #else 91 | move(event->globalPosition().toPoint() - m_dragPosition); 92 | #endif 93 | event->accept(); 94 | } 95 | } 96 | 97 | void ToolForm::showEvent(QShowEvent *event) 98 | { 99 | Q_UNUSED(event) 100 | qDebug() << "show event"; 101 | } 102 | 103 | void ToolForm::hideEvent(QHideEvent *event) 104 | { 105 | Q_UNUSED(event) 106 | qDebug() << "hide event"; 107 | } 108 | 109 | void ToolForm::on_fullScreenBtn_clicked() 110 | { 111 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 112 | if (!device) { 113 | return; 114 | } 115 | 116 | dynamic_cast(parent())->switchFullScreen(); 117 | } 118 | 119 | void ToolForm::on_returnBtn_clicked() 120 | { 121 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 122 | if (!device) { 123 | return; 124 | } 125 | device->postGoBack(); 126 | } 127 | 128 | void ToolForm::on_homeBtn_clicked() 129 | { 130 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 131 | if (!device) { 132 | return; 133 | } 134 | device->postGoHome(); 135 | } 136 | 137 | void ToolForm::on_menuBtn_clicked() 138 | { 139 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 140 | if (!device) { 141 | return; 142 | } 143 | device->postGoMenu(); 144 | } 145 | 146 | void ToolForm::on_appSwitchBtn_clicked() 147 | { 148 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 149 | if (!device) { 150 | return; 151 | } 152 | device->postAppSwitch(); 153 | } 154 | 155 | void ToolForm::on_powerBtn_clicked() 156 | { 157 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 158 | if (!device) { 159 | return; 160 | } 161 | device->postPower(); 162 | } 163 | 164 | void ToolForm::on_screenShotBtn_clicked() 165 | { 166 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 167 | if (!device) { 168 | return; 169 | } 170 | device->screenshot(); 171 | } 172 | 173 | void ToolForm::on_volumeUpBtn_clicked() 174 | { 175 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 176 | if (!device) { 177 | return; 178 | } 179 | device->postVolumeUp(); 180 | } 181 | 182 | void ToolForm::on_volumeDownBtn_clicked() 183 | { 184 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 185 | if (!device) { 186 | return; 187 | } 188 | device->postVolumeDown(); 189 | } 190 | 191 | void ToolForm::on_closeScreenBtn_clicked() 192 | { 193 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 194 | if (!device) { 195 | return; 196 | } 197 | device->setDisplayPower(false); 198 | } 199 | 200 | void ToolForm::on_expandNotifyBtn_clicked() 201 | { 202 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 203 | if (!device) { 204 | return; 205 | } 206 | device->expandNotificationPanel(); 207 | } 208 | 209 | void ToolForm::on_touchBtn_clicked() 210 | { 211 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 212 | if (!device) { 213 | return; 214 | } 215 | 216 | m_showTouch = !m_showTouch; 217 | device->showTouch(m_showTouch); 218 | } 219 | 220 | void ToolForm::on_groupControlBtn_clicked() 221 | { 222 | m_isHost = !m_isHost; 223 | updateGroupControl(); 224 | } 225 | 226 | void ToolForm::on_openScreenBtn_clicked() 227 | { 228 | auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial); 229 | if (!device) { 230 | return; 231 | } 232 | device->setDisplayPower(true); 233 | } 234 | -------------------------------------------------------------------------------- /QtScrcpy/ui/toolform.h: -------------------------------------------------------------------------------- 1 | #ifndef TOOLFORM_H 2 | #define TOOLFORM_H 3 | 4 | #include 5 | #include 6 | 7 | #include "../QtScrcpyCore/include/QtScrcpyCore.h" 8 | #include "magneticwidget.h" 9 | 10 | namespace Ui 11 | { 12 | class ToolForm; 13 | } 14 | 15 | class Device; 16 | class ToolForm : public MagneticWidget 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos); 22 | ~ToolForm(); 23 | 24 | void setSerial(const QString& serial); 25 | bool isHost(); 26 | 27 | protected: 28 | void mousePressEvent(QMouseEvent *event); 29 | void mouseReleaseEvent(QMouseEvent *event); 30 | void mouseMoveEvent(QMouseEvent *event); 31 | 32 | void showEvent(QShowEvent *event); 33 | void hideEvent(QHideEvent *event); 34 | 35 | private slots: 36 | void on_fullScreenBtn_clicked(); 37 | void on_returnBtn_clicked(); 38 | void on_homeBtn_clicked(); 39 | void on_menuBtn_clicked(); 40 | void on_appSwitchBtn_clicked(); 41 | void on_powerBtn_clicked(); 42 | void on_screenShotBtn_clicked(); 43 | void on_volumeUpBtn_clicked(); 44 | void on_volumeDownBtn_clicked(); 45 | void on_closeScreenBtn_clicked(); 46 | void on_expandNotifyBtn_clicked(); 47 | void on_touchBtn_clicked(); 48 | void on_groupControlBtn_clicked(); 49 | void on_openScreenBtn_clicked(); 50 | 51 | private: 52 | void initStyle(); 53 | void updateGroupControl(); 54 | 55 | private: 56 | Ui::ToolForm *ui; 57 | QPoint m_dragPosition; 58 | QString m_serial; 59 | bool m_showTouch = false; 60 | bool m_isHost = false; 61 | }; 62 | 63 | #endif // TOOLFORM_H 64 | -------------------------------------------------------------------------------- /QtScrcpy/ui/toolform.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ToolForm 4 | 5 | 6 | 7 | 0 8 | 0 9 | 63 10 | 537 11 | 12 | 13 | 14 | Tool 15 | 16 | 17 | 18 | 19 | 20 | 21 | 30 22 | 23 | 24 | 25 | 26 | group control 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | full screen 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Qt::Vertical 47 | 48 | 49 | 50 | 20 51 | 40 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | expand notify 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | touch switch 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | open screen 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | close screen 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | power 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | volume up 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | volume down 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | app switch 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | menu 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | home 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | return 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | screen shot 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /QtScrcpy/ui/videoform.h: -------------------------------------------------------------------------------- 1 | #ifndef VIDEOFORM_H 2 | #define VIDEOFORM_H 3 | 4 | #include 5 | #include 6 | 7 | #include "../QtScrcpyCore/include/QtScrcpyCore.h" 8 | 9 | namespace Ui 10 | { 11 | class videoForm; 12 | } 13 | 14 | class ToolForm; 15 | class FileHandler; 16 | class QYUVOpenGLWidget; 17 | class QLabel; 18 | class VideoForm : public QWidget, public qsc::DeviceObserver 19 | { 20 | Q_OBJECT 21 | public: 22 | explicit VideoForm(bool framelessWindow = false, bool skin = true, bool showToolBar = true, QWidget *parent = 0); 23 | ~VideoForm(); 24 | 25 | void staysOnTop(bool top = true); 26 | void updateShowSize(const QSize &newSize); 27 | void updateRender(int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV, int linesizeY, int linesizeU, int linesizeV); 28 | void setSerial(const QString& serial); 29 | QRect getGrabCursorRect(); 30 | const QSize &frameSize(); 31 | void resizeSquare(); 32 | void removeBlackRect(); 33 | void showFPS(bool show); 34 | void switchFullScreen(); 35 | bool isHost(); 36 | 37 | private: 38 | void onFrame(int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV, 39 | int linesizeY, int linesizeU, int linesizeV) override; 40 | void updateFPS(quint32 fps) override; 41 | void grabCursor(bool grab) override; 42 | 43 | void updateStyleSheet(bool vertical); 44 | QMargins getMargins(bool vertical); 45 | void initUI(); 46 | 47 | void showToolForm(bool show = true); 48 | void moveCenter(); 49 | void installShortcut(); 50 | QRect getScreenRect(); 51 | 52 | protected: 53 | void mousePressEvent(QMouseEvent *event) override; 54 | void mouseReleaseEvent(QMouseEvent *event) override; 55 | void mouseMoveEvent(QMouseEvent *event) override; 56 | void mouseDoubleClickEvent(QMouseEvent *event) override; 57 | void wheelEvent(QWheelEvent *event) override; 58 | void keyPressEvent(QKeyEvent *event) override; 59 | void keyReleaseEvent(QKeyEvent *event) override; 60 | 61 | void paintEvent(QPaintEvent *) override; 62 | void showEvent(QShowEvent *event) override; 63 | void resizeEvent(QResizeEvent *event) override; 64 | void closeEvent(QCloseEvent *event) override; 65 | 66 | void dragEnterEvent(QDragEnterEvent *event) override; 67 | void dragMoveEvent(QDragMoveEvent *event) override; 68 | void dragLeaveEvent(QDragLeaveEvent *event) override; 69 | void dropEvent(QDropEvent *event) override; 70 | 71 | private: 72 | // ui 73 | Ui::videoForm *ui; 74 | QPointer m_toolForm; 75 | QPointer m_loadingWidget; 76 | QPointer m_videoWidget; 77 | QPointer m_fpsLabel; 78 | 79 | //inside member 80 | QSize m_frameSize; 81 | QSize m_normalSize; 82 | QPoint m_dragPosition; 83 | float m_widthHeightRatio = 0.5f; 84 | bool m_skin = true; 85 | QPoint m_fullScreenBeforePos; 86 | QString m_serial; 87 | 88 | //Whether to display the toolbar when connecting a device. 89 | bool show_toolbar = true; 90 | }; 91 | 92 | #endif // VIDEOFORM_H 93 | -------------------------------------------------------------------------------- /QtScrcpy/ui/videoform.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | videoForm 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 800 11 | 12 | 13 | 14 | true 15 | 16 | 17 | 18 | 19 | 20 | #videoForm { 21 | border-image: url(:/res/phone-v.png) 150px 142px 85px 142px; 22 | border-width: 150px 142px 85px 142px; 23 | } 24 | 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 0 34 | 35 | 36 | 0 37 | 38 | 39 | 0 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | KeepRatioWidget 49 | QWidget 50 |
keepratiowidget.h
51 | 1 52 |
53 |
54 | 55 | 56 |
57 | -------------------------------------------------------------------------------- /QtScrcpy/uibase/keepratiowidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "keepratiowidget.h" 5 | 6 | KeepRatioWidget::KeepRatioWidget(QWidget *parent) : QWidget(parent) {} 7 | 8 | KeepRatioWidget::~KeepRatioWidget() {} 9 | 10 | void KeepRatioWidget::setWidget(QWidget *w) 11 | { 12 | if (!w) { 13 | return; 14 | } 15 | w->setParent(this); 16 | m_subWidget = w; 17 | } 18 | 19 | void KeepRatioWidget::setWidthHeightRatio(float widthHeightRatio) 20 | { 21 | if (fabs(m_widthHeightRatio - widthHeightRatio) < 0.000001f) { 22 | return; 23 | } 24 | m_widthHeightRatio = widthHeightRatio; 25 | adjustSubWidget(); 26 | } 27 | 28 | const QSize KeepRatioWidget::goodSize() 29 | { 30 | if (!m_subWidget || m_widthHeightRatio < 0.0f) { 31 | return QSize(); 32 | } 33 | return m_subWidget->size(); 34 | } 35 | 36 | void KeepRatioWidget::resizeEvent(QResizeEvent *event) 37 | { 38 | Q_UNUSED(event) 39 | adjustSubWidget(); 40 | } 41 | 42 | void KeepRatioWidget::adjustSubWidget() 43 | { 44 | if (!m_subWidget) { 45 | return; 46 | } 47 | 48 | QSize curSize = size(); 49 | QPoint pos(0, 0); 50 | int width = 0; 51 | int height = 0; 52 | if (m_widthHeightRatio > 1.0f) { 53 | // base width 54 | width = curSize.width(); 55 | height = curSize.width() / m_widthHeightRatio; 56 | pos.setY((curSize.height() - height) / 2); 57 | } else if (m_widthHeightRatio > 0.0f) { 58 | // base height 59 | height = curSize.height(); 60 | width = curSize.height() * m_widthHeightRatio; 61 | pos.setX((curSize.width() - width) / 2); 62 | } else { 63 | // full widget 64 | height = curSize.height(); 65 | width = curSize.width(); 66 | } 67 | m_subWidget->setGeometry(pos.x(), pos.y(), width, height); 68 | } 69 | -------------------------------------------------------------------------------- /QtScrcpy/uibase/keepratiowidget.h: -------------------------------------------------------------------------------- 1 | #ifndef KEEPRATIOWIDGET_H 2 | #define KEEPRATIOWIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | class KeepRatioWidget : public QWidget 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit KeepRatioWidget(QWidget *parent = nullptr); 12 | ~KeepRatioWidget(); 13 | 14 | void setWidget(QWidget *w); 15 | void setWidthHeightRatio(float widthHeightRatio); 16 | const QSize goodSize(); 17 | 18 | protected: 19 | void resizeEvent(QResizeEvent *event); 20 | void adjustSubWidget(); 21 | 22 | private: 23 | float m_widthHeightRatio = -1.0f; 24 | QPointer m_subWidget; 25 | QSize m_goodSize; 26 | }; 27 | 28 | #endif // KEEPRATIOWIDGET_H 29 | -------------------------------------------------------------------------------- /QtScrcpy/uibase/magneticwidget.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "magneticwidget.h" 6 | 7 | MagneticWidget::MagneticWidget(QWidget *adsorbWidget, AdsorbPositions adsorbPos) : QWidget(Q_NULLPTR), m_adsorbPos(adsorbPos), m_adsorbWidget(adsorbWidget) 8 | { 9 | Q_ASSERT(m_adsorbWidget); 10 | setParent(m_adsorbWidget); 11 | setWindowFlags(windowFlags() | Qt::Tool); 12 | m_adsorbWidgetSize = m_adsorbWidget->size(); 13 | 14 | m_adsorbWidget->installEventFilter(this); 15 | } 16 | 17 | MagneticWidget::~MagneticWidget() 18 | { 19 | if (m_adsorbWidget) { 20 | m_adsorbWidget->removeEventFilter(this); 21 | } 22 | } 23 | 24 | bool MagneticWidget::isAdsorbed() 25 | { 26 | return m_adsorbed; 27 | } 28 | 29 | bool MagneticWidget::eventFilter(QObject *watched, QEvent *event) 30 | { 31 | if (watched != m_adsorbWidget || !event) { 32 | return false; 33 | } 34 | // 始终记录adsorbWidget最新size 35 | if (QEvent::Resize == event->type()) { 36 | m_adsorbWidgetSize = m_adsorbWidget->size(); 37 | } 38 | if (m_adsorbed && QEvent::Move == event->type()) { 39 | move(m_adsorbWidget->pos() - m_relativePos); 40 | } 41 | if (m_adsorbed && (QEvent::Show == event->type() || QEvent::FocusIn == event->type())) { 42 | show(); 43 | raise(); 44 | } 45 | if (m_adsorbed && QEvent::Resize == event->type()) { 46 | QRect parentRect; 47 | QRect targetRect; 48 | getGeometry(parentRect, targetRect); 49 | QPoint pos(parentRect.left(), parentRect.top()); 50 | switch (m_curAdsorbPosition) { 51 | case AP_OUTSIDE_LEFT: 52 | pos.setX(pos.x() - width()); 53 | pos.setY(pos.y() - m_relativePos.y()); 54 | break; 55 | case AP_OUTSIDE_RIGHT: 56 | pos.setX(pos.x() + m_adsorbWidgetSize.width()); 57 | pos.setY(pos.y() - m_relativePos.y()); 58 | break; 59 | case AP_OUTSIDE_TOP: 60 | pos.setX(pos.x() - m_relativePos.x()); 61 | pos.setY(pos.y() - targetRect.height()); 62 | break; 63 | case AP_OUTSIDE_BOTTOM: 64 | pos.setX(pos.x() - m_relativePos.x()); 65 | pos.setY(pos.y() + parentRect.height()); 66 | break; 67 | case AP_INSIDE_LEFT: 68 | pos.setY(pos.y() - m_relativePos.y()); 69 | break; 70 | case AP_INSIDE_RIGHT: 71 | pos.setX(parentRect.right() - targetRect.width()); 72 | pos.setY(pos.y() - m_relativePos.y()); 73 | break; 74 | case AP_INSIDE_TOP: 75 | pos.setX(pos.x() - m_relativePos.x()); 76 | break; 77 | case AP_INSIDE_BOTTOM: 78 | pos.setX(pos.x() - m_relativePos.x()); 79 | pos.setY(parentRect.bottom() - targetRect.height()); 80 | break; 81 | default: 82 | break; 83 | } 84 | move(pos); 85 | } 86 | return false; 87 | } 88 | 89 | void MagneticWidget::moveEvent(QMoveEvent *event) 90 | { 91 | Q_UNUSED(event) 92 | if (!m_adsorbWidget) { 93 | return; 94 | } 95 | 96 | QRect parentRect; 97 | QRect targetRect; 98 | getGeometry(parentRect, targetRect); 99 | 100 | int parentLeft = parentRect.left(); 101 | int parentRight = parentRect.right(); 102 | int parentTop = parentRect.top(); 103 | int parentBottom = parentRect.bottom(); 104 | int targetLeft = targetRect.left(); 105 | int targetRight = targetRect.right(); 106 | int targetTop = targetRect.top(); 107 | int targetBottom = targetRect.bottom(); 108 | 109 | QPoint finalPosition = pos(); 110 | int adsorbDistance = 30; 111 | 112 | m_adsorbed = false; 113 | 114 | if (m_adsorbPos & AP_INSIDE_LEFT && parentRect.intersects(targetRect) && qAbs(parentLeft - targetLeft) < adsorbDistance) { 115 | finalPosition.setX(parentLeft); 116 | m_adsorbed |= true; 117 | m_curAdsorbPosition = AP_INSIDE_LEFT; 118 | } 119 | 120 | if (m_adsorbPos & AP_OUTSIDE_RIGHT && parentRect.intersects(targetRect.translated(-adsorbDistance, 0)) && qAbs(parentRight - targetLeft) < adsorbDistance) { 121 | finalPosition.setX(parentRight); 122 | m_adsorbed |= true; 123 | m_curAdsorbPosition = AP_OUTSIDE_RIGHT; 124 | } 125 | 126 | if (m_adsorbPos & AP_OUTSIDE_LEFT && parentRect.intersects(targetRect.translated(adsorbDistance, 0)) && qAbs(parentLeft - targetRight) < adsorbDistance) { 127 | finalPosition.setX(parentLeft - targetRect.width()); 128 | m_adsorbed |= true; 129 | m_curAdsorbPosition = AP_OUTSIDE_LEFT; 130 | } 131 | 132 | if (m_adsorbPos & AP_INSIDE_RIGHT && parentRect.intersects(targetRect) && qAbs(parentRight - targetRight) < adsorbDistance) { 133 | finalPosition.setX(parentRight - targetRect.width()); 134 | m_adsorbed |= true; 135 | m_curAdsorbPosition = AP_INSIDE_RIGHT; 136 | } 137 | 138 | if (m_adsorbPos & AP_INSIDE_TOP && parentRect.intersects(targetRect) && qAbs(parentTop - targetTop) < adsorbDistance) { 139 | finalPosition.setY(parentTop); 140 | m_adsorbed |= true; 141 | m_curAdsorbPosition = AP_INSIDE_TOP; 142 | } 143 | 144 | if (m_adsorbPos & AP_OUTSIDE_TOP && parentRect.intersects(targetRect.translated(0, adsorbDistance)) && qAbs(parentTop - targetBottom) < adsorbDistance) { 145 | finalPosition.setY(parentTop - targetRect.height()); 146 | m_adsorbed |= true; 147 | m_curAdsorbPosition = AP_OUTSIDE_TOP; 148 | } 149 | 150 | if (m_adsorbPos & AP_OUTSIDE_BOTTOM && parentRect.intersects(targetRect.translated(0, -adsorbDistance)) 151 | && qAbs(parentBottom - targetTop) < adsorbDistance) { 152 | finalPosition.setY(parentBottom); 153 | m_adsorbed |= true; 154 | m_curAdsorbPosition = AP_OUTSIDE_BOTTOM; 155 | } 156 | 157 | if (m_adsorbPos & AP_INSIDE_BOTTOM && parentRect.intersects(targetRect) && qAbs(parentBottom - targetBottom) < adsorbDistance) { 158 | finalPosition.setY(parentBottom - targetRect.height()); 159 | m_adsorbed |= true; 160 | m_curAdsorbPosition = AP_INSIDE_BOTTOM; 161 | } 162 | 163 | if (m_adsorbed) { 164 | m_relativePos = m_adsorbWidget->pos() - pos(); 165 | } 166 | 167 | move(finalPosition); 168 | } 169 | 170 | void MagneticWidget::getGeometry(QRect &relativeWidgetRect, QRect &targetWidgetRect) 171 | { 172 | relativeWidgetRect.setTopLeft(m_adsorbWidget->pos()); 173 | relativeWidgetRect.setWidth(m_adsorbWidgetSize.width()); 174 | relativeWidgetRect.setHeight(m_adsorbWidgetSize.height()); 175 | 176 | targetWidgetRect.setTopLeft(pos()); 177 | targetWidgetRect.setWidth(width()); 178 | targetWidgetRect.setHeight(height()); 179 | } 180 | -------------------------------------------------------------------------------- /QtScrcpy/uibase/magneticwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef MAGNETICWIDGET_H 2 | #define MAGNETICWIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | /* 8 | * a magnetic widget 9 | * window title bar support not good 10 | */ 11 | 12 | class MagneticWidget : public QWidget 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | enum AdsorbPosition 18 | { 19 | AP_OUTSIDE_LEFT = 0x01, // 吸附外部左边框 20 | AP_OUTSIDE_TOP = 0x02, // 吸附外部上边框 21 | AP_OUTSIDE_RIGHT = 0x04, // 吸附外部右边框 22 | AP_OUTSIDE_BOTTOM = 0x08, // 吸附外部下边框 23 | AP_INSIDE_LEFT = 0x10, // 吸附内部左边框 24 | AP_INSIDE_TOP = 0x20, // 吸附内部上边框 25 | AP_INSIDE_RIGHT = 0x40, // 吸附内部右边框 26 | AP_INSIDE_BOTTOM = 0x80, // 吸附内部下边框 27 | AP_ALL = 0xFF, // 全吸附 28 | }; 29 | Q_DECLARE_FLAGS(AdsorbPositions, AdsorbPosition) 30 | 31 | public: 32 | explicit MagneticWidget(QWidget *adsorbWidget, AdsorbPositions adsorbPos = AP_ALL); 33 | ~MagneticWidget(); 34 | 35 | bool isAdsorbed(); 36 | 37 | protected: 38 | bool eventFilter(QObject *watched, QEvent *event) override; 39 | void moveEvent(QMoveEvent *event) override; 40 | 41 | private: 42 | void getGeometry(QRect &relativeWidgetRect, QRect &targetWidgetRect); 43 | 44 | private: 45 | AdsorbPositions m_adsorbPos = AP_ALL; 46 | QPoint m_relativePos; 47 | bool m_adsorbed = false; 48 | QPointer m_adsorbWidget; 49 | // 单独记录adsorbWidgetSize,因为Widget setGeometry的时候,会先收到Move事件,后收到Resize事件, 50 | // 但是收到Move事件时Widget的size()已经是setGeometry指定的size了 51 | QSize m_adsorbWidgetSize; 52 | AdsorbPosition m_curAdsorbPosition; 53 | }; 54 | 55 | Q_DECLARE_OPERATORS_FOR_FLAGS(MagneticWidget::AdsorbPositions) 56 | #endif // MAGNETICWIDGET_H 57 | -------------------------------------------------------------------------------- /QtScrcpy/util/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct UserBootConfig 9 | { 10 | QString recordPath = ""; 11 | quint32 bitRate = 2000000; 12 | int maxSizeIndex = 0; 13 | int recordFormatIndex = 0; 14 | int lockOrientationIndex = 0; 15 | bool recordScreen = false; 16 | bool recordBackground = false; 17 | bool reverseConnect = true; 18 | bool showFPS = false; 19 | bool windowOnTop = false; 20 | bool autoOffScreen = false; 21 | bool framelessWindow = false; 22 | bool keepAlive = false; 23 | bool simpleMode = false; 24 | bool autoUpdateDevice = true; 25 | bool showToolbar = true; 26 | }; 27 | 28 | class QSettings; 29 | class Config : public QObject 30 | { 31 | Q_OBJECT 32 | public: 33 | 34 | static Config &getInstance(); 35 | 36 | // config 37 | QString getLanguage(); 38 | QString getTitle(); 39 | QString getServerVersion(); 40 | int getMaxFps(); 41 | int getDesktopOpenGL(); 42 | int getSkin(); 43 | int getRenderExpiredFrames(); 44 | QString getPushFilePath(); 45 | QString getServerPath(); 46 | QString getAdbPath(); 47 | QString getLogLevel(); 48 | QString getCodecOptions(); 49 | QString getCodecName(); 50 | QStringList getConnectedGroups(); 51 | 52 | // user data:common 53 | void setUserBootConfig(const UserBootConfig &config); 54 | UserBootConfig getUserBootConfig(); 55 | void setTrayMessageShown(bool shown); 56 | bool getTrayMessageShown(); 57 | 58 | // user data:device 59 | void setNickName(const QString &serial, const QString &name); 60 | QString getNickName(const QString &serial); 61 | void setRect(const QString &serial, const QRect &rc); 62 | QRect getRect(const QString &serial); 63 | 64 | void deleteGroup(const QString &serial); 65 | 66 | // IP history methods 67 | void saveIpHistory(const QString &ip); 68 | QStringList getIpHistory(); 69 | void clearIpHistory(); 70 | 71 | private: 72 | explicit Config(QObject *parent = nullptr); 73 | const QString &getConfigPath(); 74 | 75 | private: 76 | static QString s_configPath; 77 | QPointer m_settings; 78 | QPointer m_userData; 79 | }; 80 | 81 | #endif // CONFIG_H 82 | -------------------------------------------------------------------------------- /QtScrcpy/util/mousetap/cocoamousetap.h: -------------------------------------------------------------------------------- 1 | #ifndef COCOAMOUSETAP_H 2 | #define COCOAMOUSETAP_H 3 | #include 4 | #include 5 | 6 | #include "mousetap.h" 7 | 8 | struct MouseEventTapData; 9 | class QWidget; 10 | class CocoaMouseTap 11 | : public MouseTap 12 | , public QThread 13 | { 14 | public: 15 | CocoaMouseTap(QObject *parent = Q_NULLPTR); 16 | virtual ~CocoaMouseTap(); 17 | 18 | void initMouseEventTap() override; 19 | void quitMouseEventTap() override; 20 | void enableMouseEventTap(QRect rc, bool enabled) override; 21 | 22 | protected: 23 | void run() override; 24 | 25 | private: 26 | MouseEventTapData *m_tapData = Q_NULLPTR; 27 | QSemaphore m_runloopStartedSemaphore; 28 | }; 29 | 30 | #endif // COCOAMOUSETAP_H 31 | -------------------------------------------------------------------------------- /QtScrcpy/util/mousetap/cocoamousetap.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #include 3 | 4 | #include "cocoamousetap.h" 5 | 6 | static const CGEventMask movementEventsMask = 7 | CGEventMaskBit(kCGEventLeftMouseDragged) 8 | | CGEventMaskBit(kCGEventRightMouseDragged) 9 | | CGEventMaskBit(kCGEventMouseMoved); 10 | 11 | static const CGEventMask allGrabbedEventsMask = 12 | CGEventMaskBit(kCGEventLeftMouseDown) | CGEventMaskBit(kCGEventLeftMouseUp) 13 | | CGEventMaskBit(kCGEventRightMouseDown) | CGEventMaskBit(kCGEventRightMouseUp) 14 | | CGEventMaskBit(kCGEventOtherMouseDown) | CGEventMaskBit(kCGEventOtherMouseUp) 15 | | CGEventMaskBit(kCGEventLeftMouseDragged) | CGEventMaskBit(kCGEventRightMouseDragged) 16 | | CGEventMaskBit(kCGEventMouseMoved); 17 | 18 | typedef struct MouseEventTapData{ 19 | CFMachPortRef tap = Q_NULLPTR; 20 | CFRunLoopRef runloop = Q_NULLPTR; 21 | CFRunLoopSourceRef runloopSource = Q_NULLPTR; 22 | QRect rc; 23 | } MouseEventTapData; 24 | 25 | static CGEventRef Cocoa_MouseTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) 26 | { 27 | Q_UNUSED(proxy); 28 | MouseEventTapData *tapdata = (MouseEventTapData*)refcon; 29 | switch (type) { 30 | case kCGEventTapDisabledByTimeout: 31 | { 32 | CGEventTapEnable(tapdata->tap, true); 33 | return nullptr; 34 | } 35 | case kCGEventTapDisabledByUserInput: 36 | { 37 | return nullptr; 38 | } 39 | default: 40 | break; 41 | } 42 | 43 | 44 | if (tapdata->rc.isEmpty()) { 45 | return event; 46 | } 47 | 48 | NSRect limitWindowRect = NSMakeRect(tapdata->rc.left(), tapdata->rc.top(), 49 | tapdata->rc.width(), tapdata->rc.height()); 50 | // check rect samll than limit rect 51 | NSRect checkWindowRect = NSMakeRect(limitWindowRect.origin.x + 10, limitWindowRect.origin.y + 10, 52 | limitWindowRect.size.width - 10, limitWindowRect.size.height - 10); 53 | /* This is in CGs global screenspace coordinate system, which has a 54 | * flipped Y. 55 | */ 56 | CGPoint eventLocation = CGEventGetLocation(event); 57 | if (!NSMouseInRect(NSPointFromCGPoint(eventLocation), checkWindowRect, NO)) { 58 | if (eventLocation.x <= NSMinX(limitWindowRect)) { 59 | eventLocation.x = NSMinX(limitWindowRect) + 1.0; 60 | } else if (eventLocation.x >= NSMaxX(limitWindowRect)) { 61 | eventLocation.x = NSMaxX(limitWindowRect) - 1.0; 62 | } 63 | 64 | if (eventLocation.y <= NSMinY(limitWindowRect)) { 65 | eventLocation.y = NSMinY(limitWindowRect) + 1.0; 66 | } else if (eventLocation.y >= NSMaxY(limitWindowRect)) { 67 | eventLocation.y = NSMaxY(limitWindowRect) - 1.0; 68 | } 69 | 70 | CGWarpMouseCursorPosition(eventLocation); 71 | CGAssociateMouseAndMouseCursorPosition(YES); 72 | 73 | if ((CGEventMaskBit(type) & movementEventsMask) == 0) { 74 | /* For click events, we just constrain the event to the window, so 75 | * no other app receives the click event. We can't due the same to 76 | * movement events, since they mean that our warp cursor above 77 | * behaves strangely. 78 | */ 79 | CGEventSetLocation(event, eventLocation); 80 | } 81 | } 82 | 83 | return event; 84 | } 85 | 86 | static void SemaphorePostCallback(CFRunLoopTimerRef timer, void *info) 87 | { 88 | Q_UNUSED(timer); 89 | QSemaphore *runloopStartedSemaphore = (QSemaphore *)info; 90 | if (runloopStartedSemaphore) { 91 | runloopStartedSemaphore->release(); 92 | } 93 | } 94 | 95 | CocoaMouseTap::CocoaMouseTap(QObject *parent) 96 | : QThread(parent) 97 | { 98 | m_tapData = new MouseEventTapData; 99 | } 100 | 101 | CocoaMouseTap::~CocoaMouseTap() 102 | { 103 | if (m_tapData) { 104 | delete m_tapData; 105 | m_tapData = Q_NULLPTR; 106 | } 107 | } 108 | 109 | void CocoaMouseTap::initMouseEventTap() 110 | { 111 | if (!m_tapData) { 112 | return; 113 | } 114 | 115 | m_tapData->tap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 116 | kCGEventTapOptionDefault, allGrabbedEventsMask, 117 | &Cocoa_MouseTapCallback, m_tapData); 118 | if (!m_tapData->tap) { 119 | return; 120 | } 121 | /* Tap starts disabled, until app requests mouse grab */ 122 | CGEventTapEnable(m_tapData->tap, false); 123 | start(); 124 | } 125 | 126 | void CocoaMouseTap::quitMouseEventTap() 127 | { 128 | bool status; 129 | if (m_tapData == Q_NULLPTR || m_tapData->tap == Q_NULLPTR) { 130 | /* event tap was already cleaned up (possibly due to CGEventTapCreate 131 | * returning null.) 132 | */ 133 | return; 134 | } 135 | 136 | /* Ensure that the runloop has been started first. 137 | * TODO: Move this to InitMouseEventTap, check for error conditions that can 138 | * happen in Cocoa_MouseTapThread, and fall back to the non-EventTap way of 139 | * grabbing the mouse if it fails to Init. 140 | */ 141 | status = m_runloopStartedSemaphore.tryAcquire(1, 5000); 142 | if (status) { 143 | /* Then stop it, which will cause Cocoa_MouseTapThread to return. */ 144 | CFRunLoopStop(m_tapData->runloop); 145 | /* And then wait for Cocoa_MouseTapThread to finish cleaning up. It 146 | * releases some of the pointers in tapdata. */ 147 | wait(); 148 | } 149 | } 150 | 151 | void CocoaMouseTap::enableMouseEventTap(QRect rc, bool enabled) 152 | { 153 | if (m_tapData && m_tapData->tap) 154 | { 155 | enabled ? m_tapData->rc = rc : m_tapData->rc = QRect(); 156 | CGEventTapEnable(m_tapData->tap, enabled); 157 | } 158 | } 159 | 160 | void CocoaMouseTap::run() 161 | { 162 | /* Tap was created on main thread but we own it now. */ 163 | CFMachPortRef eventTap = m_tapData->tap; 164 | if (eventTap) { 165 | /* Try to create a runloop source we can schedule. */ 166 | CFRunLoopSourceRef runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0); 167 | if (runloopSource) { 168 | m_tapData->runloopSource = runloopSource; 169 | } else { 170 | CFRelease(eventTap); 171 | m_runloopStartedSemaphore.release(); 172 | /* TODO: Both here and in the return below, set some state in 173 | * tapdata to indicate that initialization failed, which we should 174 | * check in InitMouseEventTap, after we move the semaphore check 175 | * from Quit to Init. 176 | */ 177 | return; 178 | } 179 | } else { 180 | m_runloopStartedSemaphore.release(); 181 | return; 182 | } 183 | 184 | m_tapData->runloop = CFRunLoopGetCurrent(); 185 | CFRunLoopAddSource(m_tapData->runloop, m_tapData->runloopSource, kCFRunLoopCommonModes); 186 | CFRunLoopTimerContext context{}; 187 | context.info = &m_runloopStartedSemaphore; 188 | /* We signal the runloop started semaphore *after* the run loop has started, indicating it's safe to CFRunLoopStop it. */ 189 | CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, &SemaphorePostCallback, &context); 190 | CFRunLoopAddTimer(m_tapData->runloop, timer, kCFRunLoopCommonModes); 191 | CFRelease(timer); 192 | 193 | /* Run the event loop to handle events in the event tap. */ 194 | CFRunLoopRun(); 195 | /* Make sure this is signaled so that SDL_QuitMouseEventTap knows it can safely SDL_WaitThread for us. */ 196 | if (m_runloopStartedSemaphore.available() < 1) { 197 | m_runloopStartedSemaphore.release(); 198 | } 199 | CFRunLoopRemoveSource(m_tapData->runloop, m_tapData->runloopSource, kCFRunLoopCommonModes); 200 | 201 | /* Clean up. */ 202 | CGEventTapEnable(m_tapData->tap, false); 203 | CFRelease(m_tapData->runloopSource); 204 | CFRelease(m_tapData->tap); 205 | m_tapData->runloopSource = Q_NULLPTR; 206 | m_tapData->tap = Q_NULLPTR; 207 | 208 | return; 209 | } 210 | -------------------------------------------------------------------------------- /QtScrcpy/util/mousetap/mousetap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "mousetap.h" 4 | #ifdef Q_OS_WIN32 5 | #include "winmousetap.h" 6 | #endif 7 | #ifdef Q_OS_OSX 8 | #include "cocoamousetap.h" 9 | #endif 10 | #ifdef Q_OS_LINUX 11 | #include "xmousetap.h" 12 | #endif 13 | 14 | MouseTap *MouseTap::s_instance = Q_NULLPTR; 15 | MouseTap *MouseTap::getInstance() 16 | { 17 | if (s_instance == Q_NULLPTR) { 18 | #ifdef Q_OS_WIN32 19 | s_instance = new WinMouseTap(); 20 | #endif 21 | #ifdef Q_OS_OSX 22 | s_instance = new CocoaMouseTap(); 23 | #endif 24 | #ifdef Q_OS_LINUX 25 | s_instance = new XMouseTap(); 26 | #endif 27 | } 28 | return s_instance; 29 | } 30 | -------------------------------------------------------------------------------- /QtScrcpy/util/mousetap/mousetap.h: -------------------------------------------------------------------------------- 1 | #ifndef MOUSETAP_H 2 | #define MOUSETAP_H 3 | #include 4 | 5 | class QWidget; 6 | class MouseTap 7 | { 8 | public: 9 | static MouseTap *getInstance(); 10 | virtual void initMouseEventTap() = 0; 11 | virtual void quitMouseEventTap() = 0; 12 | // rc base global screenspace coordinate system, which has a flipped Y. 13 | virtual void enableMouseEventTap(QRect rc, bool enabled) = 0; 14 | 15 | private: 16 | static MouseTap *s_instance; 17 | }; 18 | #endif // MOUSETAP_H 19 | -------------------------------------------------------------------------------- /QtScrcpy/util/mousetap/winmousetap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "winmousetap.h" 6 | 7 | WinMouseTap::WinMouseTap() {} 8 | 9 | WinMouseTap::~WinMouseTap() {} 10 | 11 | void WinMouseTap::initMouseEventTap() {} 12 | 13 | void WinMouseTap::quitMouseEventTap() {} 14 | 15 | void WinMouseTap::enableMouseEventTap(QRect rc, bool enabled) 16 | { 17 | if (enabled && rc.isEmpty()) { 18 | return; 19 | } 20 | if (enabled) { 21 | RECT mainRect; 22 | mainRect.left = (LONG)rc.left(); 23 | mainRect.right = (LONG)rc.right(); 24 | mainRect.top = (LONG)rc.top(); 25 | mainRect.bottom = (LONG)rc.bottom(); 26 | ClipCursor(&mainRect); 27 | } else { 28 | ClipCursor(Q_NULLPTR); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /QtScrcpy/util/mousetap/winmousetap.h: -------------------------------------------------------------------------------- 1 | #ifndef WINMOUSETAP_H 2 | #define WINMOUSETAP_H 3 | 4 | #include "mousetap.h" 5 | 6 | class WinMouseTap : public MouseTap 7 | { 8 | public: 9 | WinMouseTap(); 10 | virtual ~WinMouseTap(); 11 | 12 | void initMouseEventTap() override; 13 | void quitMouseEventTap() override; 14 | void enableMouseEventTap(QRect rc, bool enabled) override; 15 | }; 16 | 17 | #endif // WINMOUSETAP_H 18 | -------------------------------------------------------------------------------- /QtScrcpy/util/mousetap/xmousetap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 4 | #include 5 | #else 6 | #include 7 | #endif 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "xmousetap.h" 14 | 15 | XMouseTap::XMouseTap() {} 16 | 17 | XMouseTap::~XMouseTap() {} 18 | 19 | void XMouseTap::initMouseEventTap() {} 20 | 21 | void XMouseTap::quitMouseEventTap() {} 22 | 23 | static void find_grab_window_recursive(xcb_connection_t *dpy, xcb_window_t window, 24 | QRect rc, int16_t offset_x, int16_t offset_y, 25 | xcb_window_t *grab_window, uint32_t *grab_window_size) { 26 | xcb_query_tree_cookie_t tree_cookie; 27 | xcb_query_tree_reply_t *tree; 28 | tree_cookie = xcb_query_tree(dpy, window); 29 | tree = xcb_query_tree_reply(dpy, tree_cookie, NULL); 30 | 31 | xcb_window_t *children = xcb_query_tree_children(tree); 32 | for (int i = 0; i < xcb_query_tree_children_length(tree); i++) { 33 | xcb_get_geometry_cookie_t gg_cookie; 34 | xcb_get_geometry_reply_t *gg; 35 | gg_cookie = xcb_get_geometry(dpy, children[i]); 36 | gg = xcb_get_geometry_reply(dpy, gg_cookie, NULL); 37 | 38 | if (gg->x + offset_x <= rc.left() && gg->x + offset_x + gg->width >= rc.right() && 39 | gg->y + offset_y <= rc.top() && gg->y + offset_y + gg->height >= rc.bottom()) { 40 | if (!*grab_window || gg->width * gg->height <= *grab_window_size) { 41 | *grab_window = children[i]; 42 | *grab_window_size = gg->width * gg->height; 43 | } 44 | } 45 | 46 | find_grab_window_recursive(dpy, children[i], rc, 47 | gg->x + offset_x, gg->y + offset_y, 48 | grab_window, grab_window_size); 49 | 50 | free(gg); 51 | } 52 | 53 | free(tree); 54 | } 55 | 56 | void XMouseTap::enableMouseEventTap(QRect rc, bool enabled) { 57 | if (enabled && rc.isEmpty()) { 58 | return; 59 | } 60 | 61 | xcb_connection_t *dpy = QX11Info::connection(); 62 | 63 | if (enabled) { 64 | // We grab the top-most smallest window 65 | xcb_window_t grab_window = 0; 66 | uint32_t grab_window_size = 0; 67 | 68 | find_grab_window_recursive(dpy, QX11Info::appRootWindow(QX11Info::appScreen()), 69 | rc, 0, 0, &grab_window, &grab_window_size); 70 | 71 | if (grab_window) { 72 | xcb_grab_pointer_cookie_t grab_cookie; 73 | xcb_grab_pointer_reply_t *grab; 74 | grab_cookie = xcb_grab_pointer(dpy, /* owner_events = */ 1, 75 | grab_window, /* event_mask = */ 0, 76 | XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, 77 | grab_window, XCB_NONE, XCB_CURRENT_TIME); 78 | grab = xcb_grab_pointer_reply(dpy, grab_cookie, NULL); 79 | 80 | free(grab); 81 | } 82 | } else { 83 | xcb_void_cookie_t ungrab_cookie; 84 | xcb_generic_error_t *error; 85 | ungrab_cookie = xcb_ungrab_pointer_checked(dpy, XCB_CURRENT_TIME); 86 | error = xcb_request_check(dpy, ungrab_cookie); 87 | 88 | free(error); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /QtScrcpy/util/mousetap/xmousetap.h: -------------------------------------------------------------------------------- 1 | #ifndef XMOUSETAP_H 2 | #define XMOUSETAP_H 3 | 4 | #include "mousetap.h" 5 | 6 | class XMouseTap : public MouseTap 7 | { 8 | public: 9 | XMouseTap(); 10 | virtual ~XMouseTap(); 11 | 12 | void initMouseEventTap() override; 13 | void quitMouseEventTap() override; 14 | void enableMouseEventTap(QRect rc, bool enabled) override; 15 | }; 16 | 17 | #endif // XMOUSETAP_H 18 | -------------------------------------------------------------------------------- /QtScrcpy/util/path.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Path { 4 | public: 5 | static const char* GetCurrentPath(); 6 | }; 7 | -------------------------------------------------------------------------------- /QtScrcpy/util/path.mm: -------------------------------------------------------------------------------- 1 | #include "path.h" 2 | 3 | #import 4 | 5 | const char* Path::GetCurrentPath() { 6 | return [[[NSBundle mainBundle] bundlePath] UTF8String]; 7 | } 8 | -------------------------------------------------------------------------------- /QtScrcpy/util/winutils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #pragma comment(lib, "dwmapi") 5 | 6 | #include "winutils.h" 7 | 8 | enum : WORD 9 | { 10 | DwmwaUseImmersiveDarkMode = 20, 11 | DwmwaUseImmersiveDarkModeBefore20h1 = 19 12 | }; 13 | 14 | WinUtils::WinUtils(){}; 15 | 16 | WinUtils::~WinUtils(){}; 17 | 18 | // Set dark border to window 19 | // Reference: qt/qtbase.git/tree/src/plugins/platforms/windows/qwindowswindow.cpp 20 | bool WinUtils::setDarkBorderToWindow(const HWND &hwnd, const bool &d) 21 | { 22 | const BOOL darkBorder = d ? TRUE : FALSE; 23 | const bool ok = SUCCEEDED(DwmSetWindowAttribute(hwnd, DwmwaUseImmersiveDarkMode, &darkBorder, sizeof(darkBorder))) 24 | || SUCCEEDED(DwmSetWindowAttribute(hwnd, DwmwaUseImmersiveDarkModeBefore20h1, &darkBorder, sizeof(darkBorder))); 25 | if (!ok) 26 | qWarning("%s: Unable to set dark window border.", __FUNCTION__); 27 | return ok; 28 | } 29 | -------------------------------------------------------------------------------- /QtScrcpy/util/winutils.h: -------------------------------------------------------------------------------- 1 | #ifndef WINUTILS_H 2 | #define WINUTILS_H 3 | 4 | #include 5 | #include 6 | 7 | class WinUtils 8 | { 9 | public: 10 | WinUtils(); 11 | ~WinUtils(); 12 | 13 | static bool setDarkBorderToWindow(const HWND &hwnd, const bool &d); 14 | }; 15 | 16 | #endif // WINUTILS_H 17 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | # QtScrcpy 2 | 3 | ![Windows](https://github.com/barry-ran/QtScrcpy/workflows/Windows/badge.svg) 4 | ![MacOS](https://github.com/barry-ran/QtScrcpy/workflows/MacOS/badge.svg) 5 | ![Ubuntu](https://github.com/barry-ran/QtScrcpy/workflows/Ubuntu/badge.svg) 6 | 7 | ![license](https://img.shields.io/badge/license-Apache2.0-blue.svg) 8 | ![release](https://img.shields.io/github/v/release/barry-ran/QtScrcpy.svg) 9 | ![star](https://img.shields.io/github/stars/barry-ran/QtScrcpy.svg) 10 | ![star](https://gitcode.com/barry-ran/QtScrcpy/star/badge.svg) 11 | 12 | [Speaks English? Click me for English introduction.](README.md) 13 | 14 | QtScrcpy 可以通过 USB / 网络连接Android设备,并进行显示和控制。无需root权限。 15 | 16 | 同时支持 GNU/Linux ,Windows 和 MacOS 三大主流桌面平台。 17 | 18 | 它专注于: 19 | 20 | - **精致** (仅显示设备屏幕) 21 | - **性能** (30~60fps) 22 | - **质量** (1920×1080以上) 23 | - **低延迟** ([35~70ms][低延迟]) 24 | - **快速启动** (1s 内就可以看到第一帧图像) 25 | - **非侵入性** (不在设备上安装任何软件) 26 | 27 | [低延迟]: https://github.com/Genymobile/scrcpy/pull/646 28 | 29 | ![win](screenshot/win-zh.png) 30 | 31 | ![mac](screenshot/mac-zh.png) 32 | 33 | ![linux](screenshot/linux-zh.png) 34 | 35 | ## 作者开发了更加专业的投屏软件`极限投屏` 36 | 极限投屏功能&特点: 37 | - 设备投屏&控制:批量投屏、单个控制、批量控制 38 | - 分组管理 39 | - wifi投屏/OTG投屏 40 | - adb shell快捷指令 41 | - 文件传输、apk安装 42 | - 投屏数量多:在OTG投屏模式,设置分辨率和流畅度为低的情况下,单台电脑可以同时管理500+台手机 43 | - 低延迟:usb投屏1080p延迟在30ms以内,在相同分辨率流畅度情况下,比市面上所有投屏软件延迟都低 44 | - cpu占用率低:纯C++开发,高性能GPU视频渲染 45 | - 高分辨率:可调节,最大支持安卓终端的原生分辨率 46 | - 完美中文输入:支持闲鱼app,支持三星手机 47 | - 免费版最多投屏10台,功能无限制(除了自动重新投屏) 48 | - 极限投屏使用教程:https://lrbnfell4p.feishu.cn/docx/QRMhd9nImorAGgxVLlmczxSdnYf 49 | - 极限投屏qq交流群:822464342 50 | - 极限投屏界面预览: 51 | ![quickmirror](docs/image/quickmirror.png) 52 | 53 | ## 自定义按键映射 54 | 可以根据需要,自己编写脚本将键盘按键映射为手机的触摸点击,编写规则在[这里](docs/KeyMapDes_zh.md)。 55 | 56 | 默认自带了针对和平精英手游和抖音进行键鼠映射的映射脚本,开启平精英手游后可以用键鼠像玩端游一样玩和平精英手游,开启抖音映射以后可以使用上下左右方向键模拟上下左右滑动,你也可以按照[编写规则](docs/KeyMapDes_zh.md)编写其他游戏的映射文件,默认按键映射如下: 57 | 58 | ![game](screenshot/game.png) 59 | 60 | 自定义按键映射操作方法如下: 61 | - 编写自定义脚本放入 keymap 目录 62 | - 点击刷新脚本,确保脚本可以被检测到 63 | - 选择需要的脚本 64 | - 连接手机并启动服务之后,点击应用脚本 65 | - 按`~`(即脚本中定义的 SwitchKey)键切换为自定义映射模式即可启用 66 | - 再次按~键切换为正常控制模式 67 | - (对于和平精英等游戏)若想使用方向盘控制载具,记得在载具设置中设置为单摇杆模式 68 | 69 | 如果不会自己手写映射规则,也可以去使用作者开发的`极限手游助手` 70 | 极限手游助手功能&特点: 71 | - 通过键盘鼠标畅玩安卓手机游戏 72 | - 按键映射脚本界面化编辑 73 | - 支持暂停电脑端画面,只使用键鼠操作 74 | - 截图&录制手机画面 75 | - 简单批量控制 76 | - 安卓11+支持电脑播放手机音频(开发中...) 77 | - 手机端免安装App 78 | - 极速秒连接 79 | - 低延迟:usb投屏1080p延迟在30ms以内,在相同分辨率流畅度情况下,比市面上所有投屏软件延迟都低 80 | - cpu占用率低:纯C++开发,高性能GPU视频渲染 81 | - 高分辨率:可调节,最大支持安卓终端的原生分辨率 82 | - [QQ交流群:901736468](https://qm.qq.com/q/wRJJaWLWc8) 83 | - [极限手游助手说明文档](https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh) 84 | 85 | ## 批量操作 86 | 你可以同时控制所有的手机 87 | 88 | ## Star历史 89 | 90 | [![Star History Chart](https://api.star-history.com/svg?repos=barry-ran/QtScrcpy&type=Date)](https://star-history.com/#barry-ran/QtScrcpy&Date) 91 | 92 | ![gc](docs/image/group-control.gif) 93 | 94 | ## 感谢 95 | 96 | 基于[Genymobile](https://github.com/Genymobile)的[scrcpy](https://github.com/Genymobile/scrcpy)项目进行复刻,重构,非常感谢。 97 | 98 | ## 比较 99 | QtScrcpy 和 Scrcpy 区别如下: 100 | 关键点|scrcpy|QtScrcpy 101 | --|:--:|:--: 102 | 界面|sdl|qt 103 | 视频解码|ffmpeg|ffmpeg 104 | 视频渲染|sdl|opengl 105 | 跨平台基础设施|自己封装|Qt 106 | 编程语言|C|C++ 107 | 编程方式|同步|异步 108 | 按键映射|不支持自定义|支持自定义按键映射 109 | 编译方式|Meson+Gradle|CMake 110 | 111 | - 使用Qt可以非常容易的定制自己的界面 112 | - 基于Qt的信号槽机制的异步编程提高性能 113 | - 方便新手学习 114 | - 增加多点触控支持 115 | 116 | 117 | ## 学习它 118 | 如果你对它感兴趣,想学习它的实现原理而又感觉无从下手,可以选择购买我录制的视频课程, 119 | 里面详细介绍了整个软件的开发架构以及开发流程,带你从无到有的开发 QtScrcpy: 120 | 121 | 课程介绍:[https://blog.csdn.net/rankun1/article/details/87970523](https://blog.csdn.net/rankun1/article/details/87970523) 122 | 123 | 或者你也可以加入我的 QtScrcpy QQ 群,和志同道合的朋友一块互相交流技术: 124 | 125 | QQ群号:901736468 126 | 127 | 128 | ## 要求 129 | Android 部分至少需要 API 21(Android 5.0)。 130 | 131 | 您要确保在 Android 设备上[启用adb调试][enable-adb]。 132 | 133 | [enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling 134 | 135 | 136 | ## 下载 137 | 138 | [gitee-download]: https://gitee.com/Barryda/QtScrcpy/releases 139 | [github-download]: https://github.com/barry-ran/QtScrcpy/releases 140 | 141 | ### Windows 142 | 143 | Windows 平台,你可以直接使用我编译好的可执行程序: 144 | 145 | - [国内下载][gitee-download] 146 | - [国外下载][github-download] 147 | 148 | 你也可以[自己编译](##编译) 149 | 150 | ### Mac OS 151 | 152 | Mac OS 平台,你可以直接使用我编译好的可执行程序: 153 | 154 | - [国内下载][gitee-download] 155 | - [国外下载][github-download] 156 | 157 | 你也可以[自己编译](##编译) 158 | 159 | ### Linux 160 | 161 | 对于 Arch Linux 用户,可以使用 AUR 安装:`yay -Syu qtscrcpy`(可能版本并非最新;维护者:[yochananmarqos](https://aur.archlinux.org/account/yochananmarqos)) 162 | 163 | 其他发行版的用户可以直接使用我编译好的可执行程序: 164 | 165 | - [国外下载][github-download] 166 | 167 | 你也可以从 [GitHub Actions](https://github.com/UjhhgtgTeams/QtScrcpy/actions/workflows/ubuntu.yml) 获取最新的自动编译好的软件 168 | 169 | 当然,你也可以[自己编译](##编译)(不推荐,需要准备环境) 170 | 171 | 目前只在 Ubuntu 和 Arch Linux 上测试过编译过程 172 | 173 | ## 运行 174 | 在你的电脑上接入Android设备,然后运行程序,点击 `一键USB连接` 或者 `一键WIFI连接` 175 | 176 | ### 无线连接步骤 177 | 1. 将手机和电脑连接到同一局域网 178 | 2. 安卓手机端在开发者选项中打开 USB 调试 179 | 3. 通过 USB 连接安卓手机到电脑 180 | 4. 点击刷新设备,会看到有设备号更新出来 181 | 5. 点击获取设备 IP 182 | 6. 点击启动 adbd 183 | 7. 无线连接 184 | 8. 再次点击刷新设备,发现多出了一个 IP 地址开头的设备,选择这个设备 185 | 9. 启动服务 186 | 187 | 备注:启动 adbd 以后无需继续连接 USB 线,以后连接断开都不再需要,除非 adbd 停止运行 188 | 189 | ## 界面解释 190 | 191 | - 启动配置:启动服务前的功能参数设置 192 | 193 | 分别可以设置本地录制视频的比特率、分辨率、录制格式、录像保存路径等。 194 | 195 | - 仅后台录制:启动服务不显示界面,只录制 Android 设备屏幕 196 | - 窗口置顶:Android 设备显示窗口置顶 197 | - 自动息屏:启动服务以后,自动关闭 Android 设备屏幕以节省电量 198 | - 使用 Reverse:服务启动模式,出现服务启动失败报错 "more than one device" 可以去掉这个勾选尝试连接 199 | 200 | - 刷新设备列表:刷新当前连接的设备 201 | - 启动服务:连接到 Android 设备 202 | - 停止服务:断开与 Android 设备的连接 203 | - 停止所有服务:断开所有已连接的 Android 设备 204 | - 获取设备ip:获取到 Android 设备的 IP 地址,更新到无线区域中,方便进行无线连接 205 | - 启动adbd:启动 Android 设备的 adbd 服务,无线连接之前,必须要启动 206 | - 无线连接:使用无线方式连接 Android 设备 207 | - 无线断开:断开无线方式连接的 Android 设备 208 | - 命令行:执行自定义 adb 命令(目前不支持阻塞命令,例如shell) 209 | 210 | 211 | ## 功能 212 | - 实时显示 Android 设备屏幕 213 | - 实时键鼠控制Android设备 214 | - 屏幕录制 215 | - 截图 216 | - 无线连接 217 | - 多设备连接与批量操作 218 | - 全屏显示 219 | - 窗口置顶 220 | - 安装 apk:拖拽apk到显示窗口即可安装 221 | - 传输文件:拖拽文件到显示窗口即可发送文件到 Android 设备 222 | - 后台录制:只录制屏幕,不显示界面 223 | - 剪贴板同步: 224 | 在计算机和设备之间同步剪贴板: 225 | - `Ctrl + c`将设备剪贴板复制到计算机剪贴板; 226 | - `Ctrl + Shift + v`将计算机剪贴板复制到设备剪贴板; 227 | - `Ctrl + v` 将计算机剪贴板作为一系列文本事件发送到设备(不支持非ASCII字符) 228 | - 同步设备扬声器声音到电脑(基于[sndcpy](https://github.com/rom1v/sndcpy),仅支持安卓10级以上,目前不推荐使用,可使用蓝牙连接替代) 229 | 230 | ## 快捷键 231 | 232 | | 功能 | 快捷键(Windows) | 快捷键 (macOS) 233 | | -------------------------------------- |:----------------------------- |:----------------------------- 234 | | 切换全屏 | `Ctrl`+`f` | `Cmd`+`f` 235 | | 调整窗口大小为 1:1 | `Ctrl`+`g` | `Cmd`+`g` 236 | | 调整窗口大小去除黑边 | `Ctrl`+`w` \| _左键双击_ | `Cmd`+`w` \| _左键双击_ 237 | | 点击 `主页` | `Ctrl`+`h` \| _点击鼠标中键_ | `Ctrl`+`h` \| _点击鼠标中键_ 238 | | 点击 `BACK` | `Ctrl`+`b` \| _右键双击_ | `Cmd`+`b` \| _右键双击_ 239 | | 点击 `APP_SWITCH` | `Ctrl`+`s` | `Cmd`+`s` 240 | | 点击 `MENU` | `Ctrl`+`m` | `Ctrl`+`m` 241 | | 点击 `VOLUME_UP` | `Ctrl`+`↑` _(上)_ | `Cmd`+`↑` _(上)_ 242 | | 点击 `VOLUME_DOWN` | `Ctrl`+`↓` _(下)_ | `Cmd`+`↓` _(下)_ 243 | | 点击 `POWER` | `Ctrl`+`p` | `Cmd`+`p` 244 | | 打开电源 | _右键双击_ | _右键双击_ 245 | | 关闭屏幕 (保持投屏) | `Ctrl`+`o` | `Cmd`+`o` 246 | | 打开下拉菜单 | `Ctrl`+`n` | `Cmd`+`n` 247 | | 关闭下拉菜单 | `Ctrl`+`Shift`+`n` | `Cmd`+`Shift`+`n` 248 | | 复制到剪切板 | `Ctrl`+`c` | `Cmd`+`c` 249 | | 剪切到剪切板 | `Ctrl`+`x` | `Cmd`+`x` 250 | | 同步剪切板并粘贴 | `Ctrl`+`v` | `Cmd`+`v` 251 | | 注入电脑剪切板文本 | `Ctrl`+`Shift`+`v` | `Cmd`+`Shift`+`v` 252 | 253 | 鼠标左键双击黑色区域可以去除黑色区域 254 | 255 | 如果电源关闭,鼠标右键双击打开电源;如果电源开启,鼠标右键双击相当于返回 256 | 257 | ## TODO 258 | [后期计划](docs/TODO.md) 259 | 260 | ## FAQ 261 | [常见问题说明](docs/FAQ.md) 262 | 263 | ## 开发者 264 | [开发相关](docs/DEVELOP.md) 265 | 266 | 欢迎大家一起维护这个项目,贡献自己的代码,不过请遵循以下几点要求: 267 | 1. PR 请推向 dev 分支,不要推向 master 分支 268 | 2. 提交 PR 之前请先变基原项目 269 | 3. PR 请以少量多次的原则提交(即一个功能点提交一个 PR) 270 | 4. 代码风格请保持和原有风格一致 271 | 272 | ## 为什么开发 QtScrcpy? 273 | 综合起来有以下几个原因,比重从大到小排列: 274 | 1. 学习Qt的过程中需要一个项目实战一下 275 | 2. 本身具有音视频相关技能,对音视频很感兴趣 276 | 3. 本身具有 Android 开发技能,好久没用有点生疏,需要巩固一下 277 | 4. 发现了 Scrcpy,决定用新的技术栈(C++ + Qt + Opengl + FFmpeg)进行复刻 278 | 279 | 280 | ## 编译 281 | 尽量提供了所有依赖资源,方便傻瓜式编译。 282 | 283 | ### QtScrcpy 284 | #### 非 Arch Linux 285 | 1. 使用官方 Qt Installer 或非官方工具(如 [aqt](https://github.com/miurahr/aqtinstall))在目标平台上搭建Qt开发环境。 286 | 需要 5.12 以上版本 Qt(在 Windows 上使用 MSVC 2019) 287 | 2. 克隆该项目:`git clone --recurse-submodules git@github.com:barry-ran/QtScrcpy.git` 288 | 3. Windows 使用 QtCreator 打开项目下 CMakeLists.txt 并编译 Release 289 | 4. Linux 用终端执行 `./ci/linux/build_for_linux.sh "Release"` 290 | 注:编译结果位于 `output/x64/Release` 中 291 | 292 | #### Arch Linux 293 | 1. 安装以下包:`qt5-base qt5-multimedia qt5-x11extras`(推荐安装 `qtcreator`) 294 | 2. 克隆该项目:`git clone --recurse-submodules git@github.com:barry-ran/QtScrcpy.git` 295 | 3. 用终端执行 `./ci/linux/build_for_linux.sh "Release"` 296 | 注:编译结果位于 `output/x64/Release` 中 297 | 298 | ### Scrcpy-Server 299 | 1. 目标平台上搭建 Android 开发环境 300 | 2. 使用 Android Studio 打开项目根目录中的 server 301 | 3. 第一次打开时,如果你没有对应版本的 Gradle,Studio 会提示找不到 Gradle,是否升级 Gradle 并创建,选择取消,取消后会提示选择 Gradle 的位置,同样取消即可。Studio 会随后自动下载。 302 | 4. 按需编辑代码 303 | 5. 编译出 apk 以后改名为 scrcpy-server 并替换 `third_party/scrcpy-server` 即可 304 | 305 | ## Licence 306 | 由于是复刻的 Scrcpy,尊重它的 Licence 307 | 308 | Copyright (C) 2025 Rankun 309 | 310 | Licensed under the Apache License, Version 2.0 (the "License"); 311 | you may not use this file except in compliance with the License. 312 | You may obtain a copy of the License at 313 | 314 | http://www.apache.org/licenses/LICENSE-2.0 315 | 316 | Unless required by applicable law or agreed to in writing, software 317 | distributed under the License is distributed on an "AS IS" BASIS, 318 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 319 | See the License for the specific language governing permissions and 320 | limitations under the License. 321 | 322 | ## 关于作者 323 | 324 | [Barry 的 CSDN](https://blog.csdn.net/rankun1) 325 | 326 | 一枚普通的程序员,工作中主要使用 C++ 进行桌面客户端开发,一毕业在山东做过一年多钢铁仿真教育软件,后来转战上海先后从事安防,在线教育相关领域工作,对音视频比较熟悉,对音视频领域如语音通话,直播教育,视频会议等相关解决方案有所了解。同时具有Android,Linux服务器等开发经验。 327 | -------------------------------------------------------------------------------- /backup/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/backup/logo.png -------------------------------------------------------------------------------- /backup/myconfig.sh: -------------------------------------------------------------------------------- 1 | ./configure --disable-everything --disable-x86asm --prefix=../ffmpeg_build \ 2 | --enable-shared --enable-static \ 3 | --enable-decoder=h264 --enable-parser=h264 --enable-demuxer=h264 \ 4 | --enable-muxer=mp4 --enable-protocol=file 5 | -------------------------------------------------------------------------------- /ci/generate-version.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | if __name__ == '__main__': 5 | p = os.popen('git rev-list --tags --max-count=1') 6 | commit = p.read() 7 | p.close() 8 | 9 | p = os.popen('git describe --tags ' + commit) 10 | tag = p.read() 11 | p.close() 12 | 13 | # print('get tag:', tag) 14 | 15 | version = str(tag[1:]) 16 | version_file = os.path.abspath(os.path.join(os.path.dirname(__file__), "../QtScrcpy/appversion")) 17 | file=open(version_file, 'w') 18 | file.write(version) 19 | file.close() 20 | sys.exit(0) -------------------------------------------------------------------------------- /ci/linux/build_for_linux.sh: -------------------------------------------------------------------------------- 1 | echo --------------------------------------------------------------- 2 | echo Check \& Set Environment Variables 3 | echo --------------------------------------------------------------- 4 | 5 | # Get Qt path 6 | # ENV_QT_PATH example: /home/barry/Qt5.9.6/5.9.6 7 | echo Current ENV_QT_PATH: $ENV_QT_PATH 8 | echo Current directory: $(pwd) 9 | # Set variables 10 | qt_cmake_path=$ENV_QT_PATH/gcc_64/lib/cmake/Qt5 11 | export PATH=$qt_gcc_path/bin:$PATH 12 | 13 | # Remember working directory 14 | old_cd=$(pwd) 15 | 16 | # Set working dir to the script's path 17 | cd $(dirname "$0")/.../ 18 | 19 | echo 20 | echo 21 | echo --------------------------------------------------------------- 22 | echo Check Build Parameters 23 | echo --------------------------------------------------------------- 24 | echo Possible build modes: Debug/Release/MinSizeRel/RelWithDebInfo 25 | 26 | build_mode="$1" 27 | if [[ $build_mode != "Release" && $build_mode != "Debug" && $build_mode != "MinSizeRel" && $build_mode != "RelWithDebInfo" ]]; then 28 | echo "error: unknown build mode, exiting......" 29 | exit 1 30 | fi 31 | 32 | echo Current build mode: $build_mode 33 | 34 | echo 35 | echo 36 | echo --------------------------------------------------------------- 37 | echo CMake Build Begins 38 | echo --------------------------------------------------------------- 39 | 40 | # Remove output folder 41 | output_path=./output 42 | if [ -d "$output_path" ]; then 43 | rm -rf $output_path 44 | fi 45 | 46 | cmake_params="-DCMAKE_PREFIX_PATH=$qt_cmake_path -DCMAKE_BUILD_TYPE=$build_mode" 47 | cmake $cmake_params . 48 | if [ $? -ne 0 ] ;then 49 | echo "error: CMake failed, exiting......" 50 | exit 1 51 | fi 52 | 53 | cmake --build . --config "$build_mode" -j8 54 | if [ $? -ne 0 ] ;then 55 | echo "error: CMake build failed, exiting......" 56 | exit 1 57 | fi 58 | 59 | echo 60 | echo 61 | echo --------------------------------------------------------------- 62 | echo CMake Build Succeeded 63 | echo --------------------------------------------------------------- 64 | 65 | # Resume current directory 66 | cd $old_cd 67 | exit 0 68 | -------------------------------------------------------------------------------- /ci/linux/publish_for_ubuntu.sh.todo: -------------------------------------------------------------------------------- 1 | echo 2 | echo 3 | echo --------------------------------------------------------------- 4 | echo check ENV 5 | echo --------------------------------------------------------------- 6 | 7 | # 从环境变量获取必要参数 8 | # 例如 /home/barry/Qt5.9.6/5.9.6/gcc_64 9 | echo ENV_QT_GCC $ENV_QT_GCC 10 | 11 | # 获取绝对路径,保证其他目录执行此脚本依然正确 12 | { 13 | cd $(dirname "$0") 14 | script_path=$(pwd) 15 | cd - 16 | } &> /dev/null # disable output 17 | # 设置当前目录,cd的目录影响接下来执行程序的工作目录 18 | old_cd=$(pwd) 19 | cd $(dirname "$0") 20 | 21 | # 启动参数声明 22 | publish_dir=$1 23 | 24 | # 提示 25 | echo current publish dir: $publish_dir 26 | 27 | # 环境变量设置 28 | keymap_path=$script_path/../../keymap 29 | # config_path=$script_path/../../config 30 | 31 | publish_path=$script_path/$publish_dir 32 | release_path=$script_path/../../output/linux/release 33 | 34 | export PATH=$ENV_QT_GCC/bin:$PATH 35 | 36 | if [ -d "$publish_path" ]; then 37 | rm -rf $publish_path 38 | fi 39 | 40 | # 复制要发布的包 41 | cp -r $release_path $publish_path 42 | cp -r $keymap_path $publish_path/QtScrcpy.app/Contents/MacOS 43 | # cp -r $config_path $publish_path/QtScrcpy.app/Contents/MacOS 44 | 45 | # 添加qt依赖包 46 | macdeployqt $publish_path/QtScrcpy.app 47 | 48 | # 删除多余qt依赖包 49 | 50 | # PlugIns 51 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/iconengines 52 | # 截图功能需要libqjpeg.dylib 53 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqgif.dylib 54 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqicns.dylib 55 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqico.dylib 56 | # rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqjpeg.dylib 57 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqmacheif.dylib 58 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqmacjp2.dylib 59 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqtga.dylib 60 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqtiff.dylib 61 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqwbmp.dylib 62 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqwebp.dylib 63 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/virtualkeyboard 64 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/printsupport 65 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/platforminputcontexts 66 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/iconengines 67 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/bearer 68 | 69 | # Frameworks 70 | rm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtVirtualKeyboard.framework 71 | rm -rf $publish_path/Contents/Frameworks/QtSvg.framework 72 | 73 | # qml 74 | rm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtQml.framework 75 | rm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtQuick.framework 76 | 77 | echo 78 | echo 79 | echo --------------------------------------------------------------- 80 | echo finish!!! 81 | echo --------------------------------------------------------------- 82 | 83 | # 恢复当前目录 84 | cd $old_cd 85 | exit 0 86 | -------------------------------------------------------------------------------- /ci/lrelease.sh: -------------------------------------------------------------------------------- 1 | # https://doc.qt.io/qt-5/linguist-manager.html#lrelease 2 | # lrelease -help 3 | lrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts -------------------------------------------------------------------------------- /ci/lupdate.sh: -------------------------------------------------------------------------------- 1 | # https://doc.qt.io/qt-5/linguist-manager.html#lupdate 2 | # lupdate -help 3 | # export PATH=/D/Qt/5.15.2/msvc2019/bin:$PATH 4 | lupdate -no-obsolete ./QtScrcpy -ts ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts -------------------------------------------------------------------------------- /ci/mac/build_for_mac.sh: -------------------------------------------------------------------------------- 1 | 2 | echo 3 | echo 4 | echo --------------------------------------------------------------- 5 | echo check ENV 6 | echo --------------------------------------------------------------- 7 | 8 | # 从环境变量获取必要参数 9 | # 例如 /Users/barry/Qt5.12.5/5.12.5 10 | echo ENV_QT_PATH $ENV_QT_PATH 11 | 12 | # 获取绝对路径,保证其他目录执行此脚本依然正确 13 | { 14 | cd $(dirname "$0") 15 | script_path=$(pwd) 16 | cd - 17 | } &> /dev/null # disable output 18 | # 设置当前目录,cd的目录影响接下来执行程序的工作目录 19 | old_cd=$(pwd) 20 | cd $(dirname "$0") 21 | 22 | # 启动参数声明 23 | build_mode=RelWithDebInfo 24 | cpu_arch=arm64 25 | 26 | echo 27 | echo 28 | echo --------------------------------------------------------------- 29 | echo check build param[Debug/Release/MinSizeRel/RelWithDebInfo] 30 | echo --------------------------------------------------------------- 31 | 32 | # 编译参数检查 33 | build_mode=$(echo $1) 34 | if [[ $build_mode != "Release" && $build_mode != "Debug" && $build_mode != "MinSizeRel" && $build_mode != "RelWithDebInfo" ]]; then 35 | echo "error: unkonow build mode -- $1" 36 | exit 1 37 | fi 38 | 39 | echo 40 | echo 41 | echo --------------------------------------------------------------- 42 | echo check cpu arch[x64/arm64] 43 | echo --------------------------------------------------------------- 44 | 45 | cpu_arch=$(echo $2) 46 | if [[ $cpu_arch != "x64" && $cpu_arch != "arm64" ]]; then 47 | echo "error: unkonow cpu mode -- $2" 48 | exit 1 49 | fi 50 | 51 | # 提示 52 | echo current build mode: $build_mode 53 | echo current cpu mode: $cpu_arch 54 | 55 | cmake_arch=x86_64 56 | if [ $cpu_arch == "x64" ]; then 57 | qt_cmake_path=$ENV_QT_PATH/clang_64/lib/cmake/Qt5 58 | cmake_arch=x86_64 59 | else 60 | qt_cmake_path=$ENV_QT_PATH/macos/lib/cmake/Qt6 61 | cmake_arch=arm64 62 | fi 63 | 64 | echo 65 | echo 66 | echo --------------------------------------------------------------- 67 | echo begin cmake build 68 | echo --------------------------------------------------------------- 69 | 70 | # 删除输出目录 71 | output_path=$script_path../../output 72 | if [ -d "$output_path" ]; then 73 | rm -rf $output_path 74 | fi 75 | # 删除编译目录 76 | build_path=$script_path/../build_temp 77 | if [ -d "$build_path" ]; then 78 | rm -rf $build_path 79 | fi 80 | mkdir $build_path 81 | cd $build_path 82 | 83 | cmake_params="-DCMAKE_PREFIX_PATH=$qt_cmake_path -DCMAKE_BUILD_TYPE=$build_mode -DCMAKE_OSX_ARCHITECTURES=$cmake_arch" 84 | cmake $cmake_params ../.. 85 | if [ $? -ne 0 ] ;then 86 | echo "cmake failed" 87 | exit 1 88 | fi 89 | 90 | cmake --build . --config $build_mode -j8 91 | if [ $? -ne 0 ] ;then 92 | echo "cmake build failed" 93 | exit 1 94 | fi 95 | 96 | echo 97 | echo 98 | echo --------------------------------------------------------------- 99 | echo finish!!! 100 | echo --------------------------------------------------------------- 101 | 102 | # 恢复当前目录 103 | cd $old_cd 104 | exit 0 105 | -------------------------------------------------------------------------------- /ci/mac/package/dmg-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/ci/mac/package/dmg-background.jpg -------------------------------------------------------------------------------- /ci/mac/package/dmg-settings.json: -------------------------------------------------------------------------------- 1 | {"icon-size": 120, "format": "UDZO", "title": "QtScrcpy", "compression-level": 9, "window": {"position": {"y": 200, "x": 400}, "size": {"width": 780, "height": 480}}, "background": "/Users/barry/mygitcode/QtScrcpy/ci/mac/package/dmg-background.jpg", "contents": [{"y": 227, "x": 223, "type": "file", "path": "/Users/barry/mygitcode/QtScrcpy/ci/mac/package/../../build/QtScrcpy.app"}, {"y": 227, "x": 550, "type": "link", "path": "/Applications"}]} -------------------------------------------------------------------------------- /ci/mac/package/package.py: -------------------------------------------------------------------------------- 1 | import dmgbuild 2 | import os 3 | import json 4 | import sys 5 | 6 | current_file_path = os.path.dirname(os.path.realpath(__file__)) 7 | dmg_settings_path = '%s/dmg-settings.json' % current_file_path 8 | dmg_background_img = '%s/dmg-background.jpg' % current_file_path 9 | app_path = '%s/../../build/QtScrcpy.app' % current_file_path 10 | dmg_path = '%s/../../build/QtScrcpy.dmg' % current_file_path 11 | app_name = 'QtScrcpy' 12 | 13 | def console_print(msg): 14 | print(msg) 15 | sys.stdout.flush() 16 | 17 | def generate_dmg_info(): 18 | with open(dmg_settings_path, 'w') as file: 19 | info = { 20 | 'title': app_name, 21 | 'icon-size': 120, 22 | 'background': dmg_background_img, 23 | 'format': 'UDZO', 24 | 'compression-level': 9, 25 | 'window': { 26 | 'position': {'x': 400, 'y': 200}, 27 | 'size': {'width': 780, 'height': 480} 28 | }, 29 | 'contents': [ 30 | { 31 | 'x': 223, 32 | 'y': 227, 33 | 'type': 'file', 34 | 'path': app_path 35 | }, 36 | { 37 | 'x': 550, 38 | 'y': 227, 39 | 'type': 'link', 40 | 'path': '/Applications' 41 | } 42 | ] 43 | } 44 | json.dump(info, file) 45 | 46 | if __name__ == '__main__': 47 | console_print('generate dmg info') 48 | generate_dmg_info() 49 | console_print('build dmg: %s' % dmg_path) 50 | dmgbuild.build_dmg(dmg_path, app_name, dmg_settings_path) 51 | if not os.path.exists(dmg_path): 52 | console_print('fail to create %s' % dmg_path) 53 | sys.exit(1) 54 | 55 | sys.exit(0) -------------------------------------------------------------------------------- /ci/mac/package/requirements.txt: -------------------------------------------------------------------------------- 1 | dmgbuild==1.4.2 -------------------------------------------------------------------------------- /ci/mac/package_for_mac.sh: -------------------------------------------------------------------------------- 1 | # 获取绝对路径,保证其他目录执行此脚本依然正确 2 | { 3 | cd $(dirname "$0") 4 | script_path=$(pwd) 5 | cd - 6 | } &> /dev/null # disable output 7 | # 设置当前目录,cd的目录影响接下来执行程序的工作目录 8 | old_cd=$(pwd) 9 | cd $(dirname "$0") 10 | 11 | echo 12 | echo 13 | echo --------------------------------------------------------------- 14 | echo pip install requirements 15 | echo --------------------------------------------------------------- 16 | 17 | pip install -r $script_path/package/requirements.txt 18 | if [ $? -ne 0 ] ;then 19 | echo "pip install requirements failed" 20 | exit 1 21 | fi 22 | 23 | echo 24 | echo 25 | echo --------------------------------------------------------------- 26 | echo create package 27 | echo --------------------------------------------------------------- 28 | 29 | python $script_path/package/package.py 30 | if [ $? -ne 0 ] ;then 31 | echo "create package failed" 32 | exit 1 33 | fi 34 | 35 | # 恢复当前目录 36 | cd $old_cd 37 | exit 0 38 | -------------------------------------------------------------------------------- /ci/mac/publish_for_mac.sh: -------------------------------------------------------------------------------- 1 | echo 2 | echo 3 | echo --------------------------------------------------------------- 4 | echo check ENV 5 | echo --------------------------------------------------------------- 6 | 7 | # 从环境变量获取必要参数 8 | # 例如 /Users/barry/Qt5.12.5/5.12.5 9 | echo ENV_QT_PATH $ENV_QT_PATH 10 | 11 | # 获取绝对路径,保证其他目录执行此脚本依然正确 12 | { 13 | cd $(dirname "$0") 14 | script_path=$(pwd) 15 | cd - 16 | } &> /dev/null # disable output 17 | # 设置当前目录,cd的目录影响接下来执行程序的工作目录 18 | old_cd=$(pwd) 19 | cd $(dirname "$0") 20 | 21 | # 启动参数声明 22 | publish_dir=$1 23 | cpu_arch=$2 24 | 25 | echo 26 | echo 27 | echo --------------------------------------------------------------- 28 | echo check cpu arch[x64/arm64] 29 | echo --------------------------------------------------------------- 30 | 31 | if [[ $cpu_arch != "x64" && $cpu_arch != "arm64" ]]; then 32 | echo "error: unkonow cpu mode -- $2" 33 | exit 1 34 | fi 35 | 36 | # 提示 37 | echo current cpu mode: $cpu_arch 38 | 39 | if [ $cpu_arch == "x64" ]; then 40 | qt_clang_path=$ENV_QT_PATH/clang_64 41 | else 42 | qt_clang_path=$ENV_QT_PATH/macos 43 | fi 44 | 45 | # 提示 46 | echo current publish dir: $publish_dir 47 | 48 | # 环境变量设置 49 | keymap_path=$script_path/../../keymap 50 | # config_path=$script_path/../../config 51 | 52 | publish_path=$script_path/$publish_dir 53 | release_path=$script_path/../../output/$cpu_arch/RelWithDebInfo 54 | 55 | export PATH=$qt_clang_path/bin:$PATH 56 | 57 | if [ -d "$publish_path" ]; then 58 | rm -rf $publish_path 59 | fi 60 | 61 | # 复制要发布的包 62 | cp -r $release_path $publish_path 63 | cp -r $keymap_path $publish_path/QtScrcpy.app/Contents/MacOS 64 | # cp -r $config_path $publish_path/QtScrcpy.app/Contents/MacOS 65 | 66 | # 添加qt依赖包 67 | macdeployqt $publish_path/QtScrcpy.app 68 | 69 | # 删除多余qt依赖包 70 | 71 | # PlugIns 72 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/iconengines 73 | # 截图功能需要libqjpeg.dylib 74 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqgif.dylib 75 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqicns.dylib 76 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqico.dylib 77 | # rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqjpeg.dylib 78 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqmacheif.dylib 79 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqmacjp2.dylib 80 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqtga.dylib 81 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqtiff.dylib 82 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqwbmp.dylib 83 | rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqwebp.dylib 84 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/virtualkeyboard 85 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/printsupport 86 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/platforminputcontexts 87 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/iconengines 88 | rm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/bearer 89 | 90 | # Frameworks 91 | rm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtVirtualKeyboard.framework 92 | rm -rf $publish_path/Contents/Frameworks/QtSvg.framework 93 | 94 | # qml 95 | rm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtQml.framework 96 | rm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtQuick.framework 97 | 98 | echo 99 | echo 100 | echo --------------------------------------------------------------- 101 | echo finish!!! 102 | echo --------------------------------------------------------------- 103 | 104 | # 恢复当前目录 105 | cd $old_cd 106 | exit 0 107 | -------------------------------------------------------------------------------- /ci/win/build_for_win.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo= 4 | echo= 5 | echo --------------------------------------------------------------- 6 | echo check ENV 7 | echo --------------------------------------------------------------- 8 | 9 | :: 从环境变量获取必要参数 10 | :: example: D:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvarsall.bat 11 | :: set vcvarsall="%ENV_VCVARSALL%" 12 | 13 | :: example: D:\Qt\Qt5.12.5\5.12.5 14 | :: echo ENV_VCVARSALL %ENV_VCVARSALL% 15 | echo ENV_QT_PATH %ENV_QT_PATH% 16 | 17 | :: 获取脚本绝对路径 18 | set script_path=%~dp0 19 | :: 进入脚本所在目录,因为这会影响脚本中执行的程序的工作目录 20 | set old_cd=%cd% 21 | cd /d %~dp0 22 | 23 | :: 启动参数声明 24 | set cpu_mode=x86 25 | set build_mode=RelWithDebInfo 26 | set errno=1 27 | 28 | echo= 29 | echo= 30 | echo --------------------------------------------------------------- 31 | echo check build param[Debug/Release/MinSizeRel/RelWithDebInfo] 32 | echo --------------------------------------------------------------- 33 | 34 | :: 编译参数检查 35 | if "%1"=="Debug" ( 36 | goto build_mode_ok 37 | ) 38 | if "%1"=="Release" ( 39 | goto build_mode_ok 40 | ) 41 | if "%1"=="MinSizeRel" ( 42 | goto build_mode_ok 43 | ) 44 | if "%1"=="RelWithDebInfo" ( 45 | goto build_mode_ok 46 | ) 47 | echo error: unknown build mode -- %1 48 | goto return 49 | :build_mode_ok 50 | 51 | set build_mode=%1 52 | set cmake_vs_build_mode=Win32 53 | set qt_cmake_path=%ENV_QT_PATH%\msvc2019\lib\cmake\Qt5 54 | 55 | if /i "%2"=="x86" ( 56 | set cpu_mode=x86 57 | set cmake_vs_build_mode=Win32 58 | set qt_cmake_path=%ENV_QT_PATH%\msvc2019\lib\cmake\Qt5 59 | ) 60 | if /i "%2"=="x64" ( 61 | set cpu_mode=x64 62 | set cmake_vs_build_mode=x64 63 | set qt_cmake_path=%ENV_QT_PATH%\msvc2019_64\lib\cmake\Qt5 64 | ) 65 | 66 | :: 提示 67 | echo current build mode: %build_mode% %cpu_mode% 68 | echo qt cmake path: %qt_cmake_path% 69 | 70 | echo= 71 | echo= 72 | echo --------------------------------------------------------------- 73 | echo begin cmake build 74 | echo --------------------------------------------------------------- 75 | 76 | :: 删除输出目录 77 | set output_path=%script_path%..\..\output 78 | if exist %output_path% ( 79 | rmdir /q /s %output_path% 80 | ) 81 | :: 删除临时目录 82 | set temp_path=%script_path%..\build_temp 83 | if exist %temp_path% ( 84 | rmdir /q /s %temp_path% 85 | ) 86 | md %temp_path% 87 | cd %temp_path% 88 | 89 | set cmake_params=-DCMAKE_PREFIX_PATH=%qt_cmake_path% -DCMAKE_BUILD_TYPE=%build_mode% -G "Visual Studio 16 2019" -A %cmake_vs_build_mode% 90 | echo cmake params: %cmake_params% 91 | 92 | cmake %cmake_params% ../.. 93 | if not %errorlevel%==0 ( 94 | echo "cmake failed" 95 | goto return 96 | ) 97 | 98 | cmake --build . --config %build_mode% -j8 99 | if not %errorlevel%==0 ( 100 | echo "cmake build failed" 101 | goto return 102 | ) 103 | 104 | echo= 105 | echo= 106 | echo --------------------------------------------------------------- 107 | echo finish!!! 108 | echo --------------------------------------------------------------- 109 | 110 | set errno=0 111 | 112 | :return 113 | cd %old_cd% 114 | exit /B %errno% 115 | -------------------------------------------------------------------------------- /ci/win/publish_for_win.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | echo= 4 | echo= 5 | echo --------------------------------------------------------------- 6 | echo check ENV 7 | echo --------------------------------------------------------------- 8 | 9 | :: 从环境变量获取必要参数 10 | :: example: D:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvarsall.bat 11 | set vcvarsall="%ENV_VCVARSALL%" 12 | :: 例如 d:\a\QtScrcpy\Qt\5.12.7 13 | set qt_msvc_path="%ENV_QT_PATH%" 14 | :: 设置了VCINSTALLDIR,windeployqt会自动copy vc_redist.x**.exe(vcruntime dll安装包) 15 | :: set VCINSTALLDIR="%ENV_VCINSTALL%" 16 | 17 | echo ENV_VCVARSALL %ENV_VCVARSALL% 18 | echo ENV_QT_PATH %ENV_QT_PATH% 19 | 20 | :: 获取脚本绝对路径 21 | set script_path=%~dp0 22 | :: 进入脚本所在目录,因为这会影响脚本中执行的程序的工作目录 23 | set old_cd=%cd% 24 | cd /d %~dp0 25 | 26 | :: 启动参数声明 27 | set cpu_mode=x86 28 | set publish_dir=%2 29 | set errno=1 30 | 31 | if /i "%1"=="x86" ( 32 | set cpu_mode=x86 33 | ) 34 | if /i "%1"=="x64" ( 35 | set cpu_mode=x64 36 | ) 37 | 38 | :: 提示 39 | echo current build mode: %cpu_mode% 40 | echo current publish dir: %publish_dir% 41 | 42 | :: 环境变量设置 43 | set adb_path=%script_path%..\..\QtScrcpy\QtScrcpyCore\src\third_party\adb\win\*.* 44 | set jar_path=%script_path%..\..\QtScrcpy\QtScrcpyCore\src\third_party\scrcpy-server 45 | set keymap_path=%script_path%..\..\keymap 46 | set config_path=%script_path%..\..\config 47 | 48 | if /i %cpu_mode% == x86 ( 49 | set publish_path=%script_path%%publish_dir%\ 50 | set release_path=%script_path%..\..\output\x86\RelWithDebInfo 51 | set qt_msvc_path=%qt_msvc_path%\msvc2019\bin 52 | ) else ( 53 | set publish_path=%script_path%%publish_dir%\ 54 | set release_path=%script_path%..\..\output\x64\RelWithDebInfo 55 | set qt_msvc_path=%qt_msvc_path%\msvc2019_64\bin 56 | ) 57 | set PATH=%qt_msvc_path%;%PATH% 58 | 59 | :: 注册vc环境(注册以后,windeployqt会把vc_redist复制过来(vcruntime安装包)) 60 | if /i %cpu_mode% == x86 ( 61 | call %vcvarsall% %cpu_mode% 62 | ) else ( 63 | call %vcvarsall% %cpu_mode% 64 | ) 65 | 66 | if exist %publish_path% ( 67 | rmdir /s/q %publish_path% 68 | ) 69 | 70 | :: 复制要发布的包 71 | xcopy %release_path% %publish_path% /E /Y 72 | xcopy %adb_path% %publish_path% /Y 73 | xcopy %jar_path% %publish_path% /Y 74 | xcopy %keymap_path% %publish_path%keymap\ /E /Y 75 | xcopy %config_path% %publish_path%config\ /E /Y 76 | 77 | :: 添加qt依赖包 78 | windeployqt %publish_path%\QtScrcpy.exe 79 | 80 | :: 删除多余qt依赖包 81 | rmdir /s/q %publish_path%\iconengines 82 | rmdir /s/q %publish_path%\translations 83 | 84 | :: 截图功能需要qjpeg.dll 85 | del %publish_path%\imageformats\qgif.dll 86 | del %publish_path%\imageformats\qicns.dll 87 | del %publish_path%\imageformats\qico.dll 88 | ::del %publish_path%\imageformats\qjpeg.dll 89 | del %publish_path%\imageformats\qsvg.dll 90 | del %publish_path%\imageformats\qtga.dll 91 | del %publish_path%\imageformats\qtiff.dll 92 | del %publish_path%\imageformats\qwbmp.dll 93 | del %publish_path%\imageformats\qwebp.dll 94 | 95 | :: 删除vc_redist,自己copy vcruntime dll 96 | if /i %cpu_mode% == x86 ( 97 | del %publish_path%\vc_redist.x86.exe 98 | ) else ( 99 | del %publish_path%\vc_redist.x64.exe 100 | ) 101 | 102 | :: copy vcruntime dll 103 | if /i %cpu_mode% == x64 ( 104 | cp "C:\Windows\System32\msvcp140_1.dll" %publish_path%\msvcp140_1.dll 105 | cp "C:\Windows\System32\msvcp140.dll" %publish_path%\msvcp140.dll 106 | cp "C:\Windows\System32\vcruntime140.dll" %publish_path%\vcruntime140.dll 107 | :: 只有x64需要 108 | cp "C:\Windows\System32\vcruntime140_1.dll" %publish_path%\vcruntime140_1.dll 109 | ) else ( 110 | cp "C:\Windows\SysWOW64\msvcp140_1.dll" %publish_path%\msvcp140_1.dll 111 | cp "C:\Windows\SysWOW64\msvcp140.dll" %publish_path%\msvcp140.dll 112 | cp "C:\Windows\SysWOW64\vcruntime140.dll" %publish_path%\vcruntime140.dll 113 | 114 | ) 115 | 116 | ::cp "C:\Program Files (x86)\Microsoft Visual Studio\Installer\VCRUNTIME140.dll" %publish_path%\VCRUNTIME140.dll 117 | ::cp "C:\Program Files (x86)\Microsoft Visual Studio\Installer\api-ms-win-crt-runtime-l1-1-0.dll" %publish_path%\api-ms-win-crt-runtime-l1-1-0.dll 118 | ::cp "C:\Program Files (x86)\Microsoft Visual Studio\Installer\api-ms-win-crt-heap-l1-1-0.dll" %publish_path%\api-ms-win-crt-heap-l1-1-0.dll 119 | ::cp "C:\Program Files (x86)\Microsoft Visual Studio\Installer\api-ms-win-crt-math-l1-1-0.dll" %publish_path%\api-ms-win-crt-math-l1-1-0.dll 120 | ::cp "C:\Program Files (x86)\Microsoft Visual Studio\Installer\api-ms-win-crt-stdio-l1-1-0.dll" %publish_path%\api-ms-win-crt-stdio-l1-1-0.dll 121 | ::cp "C:\Program Files (x86)\Microsoft Visual Studio\Installer\api-ms-win-crt-locale-l1-1-0.dll" %publish_path%\api-ms-win-crt-locale-l1-1-0.dll 122 | 123 | echo= 124 | echo= 125 | echo --------------------------------------------------------------- 126 | echo finish!!! 127 | echo --------------------------------------------------------------- 128 | 129 | set errno=0 130 | 131 | :return 132 | cd %old_cd% 133 | exit /B %errno% -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | [common] 2 | # 语言 Auto=自动,zh_CN=简体中文,en_US=English 3 | Language=Auto 4 | # 窗口标题 5 | WindowTitle=QtScrcpy 6 | # 推送到安卓设备的文件保存路径(必须以/结尾) 7 | PushFilePath=/sdcard/ 8 | # 最大fps(仅支持Android 10以上) 9 | MaxFps=0 10 | # 是否渲染过期视频帧(跳过过期视频帧意味着更低的延迟) 11 | RenderExpiredFrames=0 12 | # 视频解码方式:-1 自动,0 软解,1 dx硬解,2 opengl硬解 13 | UseDesktopOpenGL=-1 14 | # scrcpy-server的版本号(不要修改) 15 | ServerVersion=3.2 16 | # scrcpy-server推送到安卓设备的路径 17 | ServerPath=/data/local/tmp/scrcpy-server.jar 18 | # 自定义adb路径,例如D:/android/tools/adb.exe 19 | AdbPath= 20 | # 编码选项 ""表示默认 21 | # 例如 CodecOptions="profile=1,level=2" 22 | # 更多编码选项参考 https://d.android.com/reference/android/media/MediaFormat 23 | CodecOptions="" 24 | # 指定编码器名称(必须是H.264编码器),""表示默认 25 | # 例如 CodecName="OMX.qcom.video.encoder.avc" 26 | CodecName="" 27 | 28 | # Set the log level (verbose, debug, info, warn, error) 29 | LogLevel=verbose 30 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 一些经常问的问题 3 | 4 | 如果在此文档没有解决你的问题,描述你的问题,截图软件控制台中打印的日志,一起发到QQ群里提问。 5 | 6 | # adb问题 7 | ## ADB版本之间的冲突 8 | ``` 9 | adb server version (41) doesn't match this client (39); killing... 10 | ``` 11 | 当你的电脑中运行不同版本的adb时,会发生此错误。你必须保证所有程序使用相同版本的adb。 12 | 现在你有两个办法解决这个问题: 13 | 1. 任务管理器找到adb进程并杀死 14 | 2. 配置QtScrcpy的config.ini中的AdbPath路径指向当前使用的adb 15 | 16 | ## 手机通过数据线连接电脑,刷新设备列表以后,没有任何设备出现 17 | 随便下载一个手机助手,尝试连接成功以后,再用QtScrcpy刷新设备列表连接 18 | 19 | # 控制问题 20 | ## 可以看到画面,但无法控制 21 | 有些手机(小米等手机)需要额外打开控制权限,检查是否USB调试里打开了允许模拟点击 22 | 23 | ![image](image/USB调试(安全设置).jpg) 24 | 25 | # 其它 26 | ## 支持声音(软件不做支持) 27 | [关于转发安卓声音到PC的讨论](https://github.com/Genymobile/scrcpy/issues/14#issuecomment-543204526) 28 | 29 | ## 画面不清晰 30 | 在Windows上,您可能需要配置缩放行为。 31 | 32 | QtScrcpy.exe>属性>兼容性>更改高DPI设置>覆盖高DPI缩放行为>由以下人员执行缩放:应用程序。 33 | 34 | 如果视频窗口大小远远小于设备屏幕的大小,则画面会不清晰。这在文字上尤其明显 35 | 36 | ## 玩和平精英上下车操作会失效 37 | 这是由于游戏中上车会创建新的界面,导致鼠标触摸点失效,目前技术上没有好的解决办法 38 | 39 | 可以通过`连续按两次~键(数字键1左边)`来恢复,这是目前最好的办法。 40 | 41 | ## 无法输入中文 42 | 手机端安装搜狗输入法/QQ输入法就可以支持输入中文了 43 | 44 | ## 可以控制,但无法看到画面 45 | 控制台错误信息可能会包含 QOpenGLShaderProgram::attributeLocation(vertexIn): shader program is not linked 46 | 47 | 一般是由于显卡不支持当前的视频渲染方式,config.ini里修改下解码方式,改成1或者2试试 48 | 49 | ## 错误信息:AdbProcess::error:adb server version (40) doesnt match this client (41) 50 | 任务管理找到adb进程并杀死,重新操作即可 51 | 52 | ## 错误信息:Could not open video stream 53 | 导致这个错误的原因有很多,最简单的解决方法是在分辨率设置中,选择一个较低的分辨率 54 | 55 | -------------------------------------------------------------------------------- /docs/KeyMapDes.md: -------------------------------------------------------------------------------- 1 | # Custom key mapping instructions 2 | 3 | The key map file is in json format, and the new key map file needs to be placed in the keymap directory to be recognized by QtScrcpy. 4 | 5 | The specific writing format of the button mapping file will be introduced below, and you can also refer to the button mapping file that comes with it. 6 | 7 | ## Key mapping script format description 8 | 9 | ### General Instructions 10 | 11 | -The coordinate positions in the key map are all expressed by relative positions, and the width and height of the screen are expressed by 1, for example, the pixels of the screen are 1920x1080, then the coordinates (0.5,0.5) indicate 12 | Taking the upper left corner of the screen as the origin, the position of the pixel coordinates (1920,1080)*(0.5,0.5)=(960,540). 13 | 14 | Or when the left mouse button clicks, the console will output the pos at this time, just use this pos directly 15 | ![](image/debug-keymap-pos.png) 16 | 17 | -The key codes in the key map are represented by Qt enumerations, detailed description can be [refer to Qt documentation](https://doc.qt.io/qt-5/qt.html) (search for The key names used by Qt. can be quickly located). 18 | -Open the following two settings in the developer options, you can easily observe the coordinates of the touch point: 19 | ![](image/display pointer position.jpg) 20 | 21 | ### Mapping type description 22 | 23 | -switchKey: Switch the key of the custom key mapping. The default is the normal mapping. You need to use this key to switch between the normal mapping and the custom mapping. 24 | 25 | -mouseMoveMap: mouse movement mapping, the movement of the mouse will be mapped to startPos as the starting point, and the direction of the mouse movement as the direction of the finger drag operation (after the mouse movement map is turned on, the mouse will be hidden, limiting the range of mouse movement). 26 | Generally used to adjust the character field of vision in FPS mobile games. 27 | -startPos finger drag starting point 28 | -speedRatio mouse sensitivity of the finger dragging. The value must be at least 0.00225. The greater the value, the lower the sensitivity. The Y-axis translates with a ratio of 2.25. If this does not fit your phone screen, please use the following two settings to set individual sensitivity values. 29 | -speedRatioX sensitivity of the mouse X-axis. This value must be at least 0.001. 30 | -speedRatioY sensitivity of the mouse Y-axis. This value must be at least 0.001. 31 | -smallEyes The button that triggers the small eyes. After pressing this button, the mouse movement will be mapped to the finger drag operation with the smallEyes.pos as the starting point and the mouse movement direction as the movement direction 32 | 33 | -keyMapNodes general key map, json array, all general key maps are placed in this array, map the keys of the keyboard to ordinary finger clicks. 34 | 35 | There are several types of key mapping as follows: 36 | 37 | -type The type of key mapping, each element in keyMapNodes needs to be specified, and can be of the following types: 38 | -KMT_CLICK Ordinary click, key press simulates finger press, key lift simulates finger lift 39 | -KMT_CLICK_TWICE Double click, key press simulates finger press and then lift, key lift simulates finger press and then lift 40 | - KMT_CLICK_MULTI Click multiple times. According to the delay and pos in the clickNodes array, press one key to simulate touching multiple positions 41 | -KMT_DRAG drag and drop, the key press is simulated as a finger press and drag a distance, the key lift is simulated as a finger lift 42 | -KMT_STEER_WHEEL steering wheel mapping, which is dedicated to the mapping of the steering wheel for moving characters in FPS games, requires 4 buttons to cooperate. 43 | 44 | Description of the unique attributes of different key mapping types: 45 | 46 | -KMT_CLICK 47 | -key The key code to be mapped 48 | -pos simulates the location of the touch 49 | -Whether the switchMap releases the mouse. After clicking this button, besides the default simulated touch map, whether the mouse operation is released. (You can refer to the effect of M map mapping in Peace Elite Map) 50 | 51 | -KMT_CLICK_TWICE 52 | -key The key code to be mapped 53 | -pos Simulates the location of the touch 54 | 55 | -KMT_CLICK_MULTI 56 | -delay Delay `delay` ms before simulating touch 57 | -pos Simulates the location of the touch 58 | 59 | -KMT_DRAG 60 | -key The key code to be mapped 61 | -startPos Simulate the start position of touch drag 62 | -endPos Simulate the end position of touch drag 63 | -dragSpeed Speed of the drag movement (range 0-1, default 1.0). Higher values result in faster movements 64 | -startDelay Optional delay in milliseconds to wait after the initial touch before starting the drag movement 65 | 66 | -KMT_STEER_WHEEL 67 | -centerPos steering wheel center point 68 | -leftKey key control in the left direction 69 | -rightKey Right key control 70 | -UpKey key control 71 | -downKey key control in down direction 72 | -leftOffset After dragging the left arrow key, drag to the leftOffset horizontally to the centerPos 73 | -rightOffset After pressing the right direction key, drag it to the right offset of the center to the right of the centerPos position 74 | -upOffset After pressing the up arrow key, drag it to the upper offset position horizontally relative to the centerPos position 75 | -downOffset Press the down arrow key and drag it to the downOffset position horizontally relative to the centerPos position 76 | 77 | ## Visual Key Mapping Tool 78 | 79 | 1. Just use [QuickAssistant](https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh) 80 | 81 | ![game](../screenshot/game.png) 82 | 83 | 2. A web-based GUI tool is available to help you create and manage key mappings visually: [ScrcpyKeyMapper](https://github.com/w4po/ScrcpyKeyMapper) 84 | 85 | ![ScrcpyKeyMapper Screenshot](https://raw.githubusercontent.com/w4po/ScrcpyKeyMapper/main/assets/screenshot.png) 86 | 87 | You can use this tool to: 88 | - Create key mappings visually 89 | - Test your mappings in real-time 90 | - Export mappings as JSON files 91 | - Import existing mappings for editing 92 | 93 | Try it online: [ScrcpyKeyMapper Web App](https://w4po.github.io/ScrcpyKeyMapper) 94 | 95 | -------------------------------------------------------------------------------- /docs/KeyMapDes_zh.md: -------------------------------------------------------------------------------- 1 | # 自定义按键映射说明 2 | 3 | 按键映射文件为json格式,新增自己的按键映射文件需要放在keymap目录中才可以被QtScrcpy识别。 4 | 5 | 按键映射文件的具体编写格式下面会介绍,也可以参考自带的按键映射文件。 6 | 7 | ## 按键映射脚本格式说明 8 | 9 | ### 通用说明 10 | 11 | - 按键映射中的坐标位置都是用相对位置表示的,屏幕的宽高都用1表示,例如屏幕的像素为1920x1080,那么坐标(0.5,0.5)则表示的是 12 | 以屏幕左上角为原点,像素坐标(1920,1080)*(0.5,0.5)=(960,540)的位置。 13 | 14 | 或者鼠标左键单击时控制台会输出此时的pos,直接使用这个pos即可 15 | ![](image/debug-keymap-pos.png) 16 | 17 | - 按键映射中的按键码是用Qt的枚举表示的,详细说明可以[参考Qt文档]( https://doc.qt.io/qt-5/qt.html )(搜索 The key names used by Qt. 可以快速定位)。 18 | - 开发人员选项中打开如下两个设置,可以方便的观察触摸点的坐标: 19 | ![](image/显示指针位置.jpg) 20 | 21 | ### 映射类型说明 22 | 23 | - switchKey:切换自定义按键映射的开关键,默认为普通映射,需要使用这个按键在普通映射和自定义映射之间切换。 24 | 25 | - mouseMoveMap:鼠标移动映射,鼠标的移动将被映射为以startPos为起点,以鼠标移动方向为移动方向的手指拖动操作(开启鼠标移动映射以后会隐藏鼠标,限制鼠标移动范围)。 26 | 一般在FPS手游中用来调整人物视野。 27 | - startPos 手指拖动起始点 28 | - speedRatio 鼠标移动映射为手指拖动的比例,可以控制鼠标灵敏度,数值要大于0.00225,数值越大,灵敏度越低,Y轴以2.25的比率平移。如果这不适合您的手机屏幕,请使用以下两种设置来设置单个灵敏度值。 29 | - speedRatioX 鼠标X轴的速度比灵敏度。此值必须至少为0.001。 30 | - speedRatioY 鼠标Y轴的速度比灵敏度。此值必须至少为0.001。 31 | - smallEyes 触发小眼睛的按键,按下此按键以后,鼠标的移动将被映射为以smallEyes.pos为起点,以鼠标移动方向为移动方向的手指拖动操作 32 | 33 | - keyMapNodes 一般按键的映射,json数组,所有一般按键映射都放在这个数组中,将键盘的按键映射为普通的手指点击。 34 | 35 | 一般按键映射有如下几种类型: 36 | 37 | - type 按键映射的类型,每个keyMapNodes中的元素都需要指明,可以是如下类型: 38 | - KMT_CLICK 普通点击,按键按下模拟为手指按下,按键抬起模拟为手指抬起 39 | - KMT_CLICK_TWICE 两次点击,按键按下模拟为手指按下再抬起,按键抬起模拟为手指按下再抬起 40 | - KMT_CLICK_MULTI 多次点击,根据clickNodes数组中的delay和pos实现一个按键多次点击 41 | - KMT_DRAG 拖拽,按键按下模拟为手指按下并拖动一段距离,按键抬起模拟为手指抬起 42 | - KMT_STEER_WHEEL 方向盘映射,专用于FPS游戏中移动人物脚步的方向盘的映射,需要4个按键来配合。 43 | 44 | 不同按键映射类型的专有属性说明: 45 | 46 | - KMT_CLICK 47 | - key 要映射的按键码 48 | - pos 模拟触摸的位置 49 | - switchMap 是否释放出鼠标,点击此按键后,除了默认的模拟触摸映射,是否释放出鼠标操作。(可以参考和平精英映射中M地图映射的效果) 50 | 51 | - KMT_CLICK_TWICE 52 | - key 要映射的按键码 53 | - pos 模拟触摸的位置 54 | 55 | - KMT_CLICK_MULTI 56 | - delay 延迟delay毫秒以后再模拟触摸 57 | - pos 模拟触摸的位置 58 | 59 | - KMT_DRAG 60 | - key 要映射的按键码 61 | - startPos 模拟触摸拖动的开始位置 62 | - endPos 模拟触摸拖动的结束位置 63 | - dragSpeed 拖动移动的速度(范围0-1,默认1.0)。数值越大,移动越快 64 | - startDelay 可选的延迟时间(毫秒),在开始拖动移动之前等待指定的时间 65 | 66 | - KMT_STEER_WHEEL 67 | - centerPos 方向盘中心点 68 | - leftKey 左方向的按键控制 69 | - rightKey 右方向的按键控制 70 | - upKey 上方向的按键控制 71 | - downKey 下方向的按键控制 72 | - leftOffset 按下左方向键后模拟拖动到相对centerPos位置水平偏左leftOffset处 73 | - rightOffset 按下右方向键后模拟拖动到相对centerPos位置水平偏右rightOffset处 74 | - upOffset 按下上方向键后模拟拖动到相对centerPos位置水平偏上upOffset处 75 | - downOffset 按下下方向键后模拟拖动到相对centerPos位置水平偏下downOffset处 76 | 77 | ## 可视化按键映射工具 78 | 1. 直接使用[QuickAssistant](https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh) 79 | 80 | ![game](../screenshot/game.png) 81 | 82 | 2. 还有一个基于Web的GUI工具可以帮助你直观地创建和管理按键映射:[ScrcpyKeyMapper](https://github.com/w4po/ScrcpyKeyMapper) 83 | 84 | ![ScrcpyKeyMapper截图](https://raw.githubusercontent.com/w4po/ScrcpyKeyMapper/main/assets/screenshot.png) 85 | 86 | 你可以使用这个工具来: 87 | - 直观地创建按键映射 88 | - 实时测试你的映射 89 | - 导出映射为JSON文件 90 | - 导入现有映射进行编辑 91 | 92 | 在线试用:[ScrcpyKeyMapper网页应用](https://w4po.github.io/ScrcpyKeyMapper) 93 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | ## 低优先级 3 | - text转换 https://github.com/Genymobile/scrcpy/commit/c916af0984f72a60301d13fa8ef9a85112f54202?tdsourcetag=s_pctim_aiomsg 4 | - 关闭number lock时的数字小键盘处理 https://github.com/Genymobile/scrcpy/commit/cd69eb4a4fecf8167208399def4ef536b59c9d22 5 | - mipmapping https://github.com/Genymobile/scrcpy/commit/bea7658807d276aeab7d18d856a366c83ee05827 6 | 7 | ## 中优先级 8 | - 脚本 9 | - 某些机器软解不行 10 | - opengles 3.0 兼容性参考[这里](https://github.com/libretro/glsl-shaders/blob/master/nnedi3/shaders/yuv-to-rgb-2x.glsl) 11 | - 通过host:track-devices实现自动连接 https://www.jianshu.com/p/2cb86c6de76c 12 | - 旋转 https://github.com/Genymobile/scrcpy/commit/d48b375a1dbc8bed92e3424b5967e59c2d8f6ca1 13 | - 禁用屏幕保护 https://github.com/Genymobile/scrcpy/commit/dc7b60e6199b90a45ea26751988f6f30f8b2efdf 14 | - 自定义快捷键 https://github.com/Genymobile/scrcpy/commit/1b76d9fd78c3a88a8503a72d4cd2f65bdb836aa4 15 | 16 | ## 高优先级 17 | - linux打包以及版本号 18 | - 关于 19 | - 音频转发 https://github.com/rom1v/sndcpy 20 | 21 | # mark 22 | ## ffmpeg 23 | [ffmpeg编译参数详解](https://www.cnblogs.com/wainiwann/p/4204230.html) 24 | 25 | ## fontawesome 26 | [fontawesome 在线搜索](http://www.fontawesome.com.cn/cheatsheet/) 27 | 28 | ## adb 29 | 以下是 ADB 和 Fastboot 的谷歌官方下载链接: 30 | 31 | ADB和Fastboot for Windows 32 | 33 | https://dl.google.com/android/repository/platform-tools-latest-windows.zip 34 | 35 | ADB和Fastboot for Mac 36 | 37 | https://dl.google.com/android/repository/platform-tools-latest-darwin.zip 38 | 39 | ADB和Fastboot for Linux 40 | 41 | https://dl.google.com/android/repository/platform-tools-latest-linux.zip 42 | 43 | 由于这些是直接的 Google 链接,用户可以确保下载不仅是官方的,而且将始终能够获得最新版本的 ADB 和 Fastboot 44 | -------------------------------------------------------------------------------- /docs/image/USB调试(安全设置).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/docs/image/USB调试(安全设置).jpg -------------------------------------------------------------------------------- /docs/image/debug-keymap-pos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/docs/image/debug-keymap-pos.png -------------------------------------------------------------------------------- /docs/image/group-control.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/docs/image/group-control.gif -------------------------------------------------------------------------------- /docs/image/quickmirror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/docs/image/quickmirror.png -------------------------------------------------------------------------------- /docs/image/显示指针位置.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/docs/image/显示指针位置.jpg -------------------------------------------------------------------------------- /keymap/FRAG.json: -------------------------------------------------------------------------------- 1 | { 2 | "old-switchKey": "Key_QuoteLeft", 3 | "switchKey": "RightButton", 4 | "mouseMoveMap": { 5 | "startPos": { 6 | "x": 0.5, 7 | "y": 0.5 8 | }, 9 | "speedRatioX": 3.25, 10 | "speedRatioY": 1.25 11 | }, 12 | "keyMapNodes": [{ 13 | "comment": "Steering Wheel", 14 | "type": "KMT_STEER_WHEEL", 15 | "centerPos": { 16 | "x": 0.194792, 17 | "y": 0.716484 18 | }, 19 | "leftOffset": 0.15, 20 | "rightOffset": 0.15, 21 | "upOffset": 0.15, 22 | "downOffset": 0.15, 23 | "leftKey": "Key_A", 24 | "rightKey": "Key_D", 25 | "upKey": "Key_W", 26 | "downKey": "Key_S" 27 | }, 28 | { 29 | "comment": "Activate item under crosshair", 30 | "type": "KMT_CLICK", 31 | "key": "LeftButton", 32 | "pos": { 33 | "x": 0.51875, 34 | "y": 0.496703 35 | }, 36 | "switchMap": false 37 | }, 38 | { 39 | "comment": "Activate first special skill", 40 | "type": "KMT_CLICK", 41 | "key": "Key_E", 42 | "pos": { 43 | "x": 0.909375, 44 | "y": 0.542857 45 | }, 46 | "switchMap": false 47 | }, 48 | { 49 | "comment": "Activate Chat", 50 | "type": "KMT_CLICK", 51 | "key": "Key_C", 52 | "pos": { 53 | "x": 0.905208, 54 | "y": 0.254945 55 | }, 56 | "switchMap": false 57 | }, 58 | { 59 | "comment": "Chat option 1", 60 | "type": "KMT_CLICK", 61 | "key": "Key_1", 62 | "pos": { 63 | "x": 0.875, 64 | "y": 0.523077 65 | }, 66 | "switchMap": false 67 | }, 68 | { 69 | "comment": "Chat option 2", 70 | "type": "KMT_CLICK", 71 | "key": "Key_2", 72 | "pos": { 73 | "x": 0.875, 74 | "y": 0.606593 75 | }, 76 | "switchMap": false 77 | }, 78 | { 79 | "comment": "Chat option 3", 80 | "type": "KMT_CLICK", 81 | "key": "Key_3", 82 | "pos": { 83 | "x": 0.875, 84 | "y": 0.685714 85 | }, 86 | "switchMap": false 87 | }, 88 | { 89 | "comment": "Chat option 4", 90 | "type": "KMT_CLICK", 91 | "key": "Key_4", 92 | "pos": { 93 | "x": 0.875, 94 | "y": 0.756044 95 | }, 96 | "switchMap": false 97 | }, 98 | { 99 | "comment": "Chat option 5", 100 | "type": "KMT_CLICK", 101 | "key": "Key_5", 102 | "pos": { 103 | "x": 0.875, 104 | "y": 0.832967 105 | }, 106 | "switchMap": false 107 | }, 108 | { 109 | "comment": "Chat option 6", 110 | "type": "KMT_CLICK", 111 | "key": "Key_6", 112 | "pos": { 113 | "x": 0.875, 114 | "y": 0.911273 115 | }, 116 | "switchMap": false 117 | } 118 | ] 119 | } 120 | -------------------------------------------------------------------------------- /keymap/gameforpeace.json: -------------------------------------------------------------------------------- 1 | { 2 | "switchKey": "Key_QuoteLeft", 3 | "mouseMoveMap": { 4 | "startPos": { 5 | "x": 0.57, 6 | "y": 0.26 7 | }, 8 | "speedRatioX": 3.25, 9 | "speedRatioY": 1.25, 10 | "smallEyes": { 11 | "comment": "小眼睛", 12 | "type": "KMT_CLICK", 13 | "key": "Key_Alt", 14 | "pos": { 15 | "x": 0.8, 16 | "y": 0.31 17 | }, 18 | "switchMap": false 19 | }, 20 | "speedRatio": 10 21 | }, 22 | "keyMapNodes": [ 23 | { 24 | "comment": "方向盘", 25 | "type": "KMT_STEER_WHEEL", 26 | "centerPos": { 27 | "x": 0.16, 28 | "y": 0.75 29 | }, 30 | "leftOffset": 0.1, 31 | "rightOffset": 0.1, 32 | "upOffset": 0.27, 33 | "downOffset": 0.2, 34 | "leftKey": "Key_A", 35 | "rightKey": "Key_D", 36 | "upKey": "Key_W", 37 | "downKey": "Key_S" 38 | }, 39 | { 40 | "comment": "左探头", 41 | "type": "KMT_CLICK_TWICE", 42 | "key": "Key_Q", 43 | "pos": { 44 | "x": 0.12, 45 | "y": 0.35 46 | } 47 | }, 48 | { 49 | "comment": "右探头", 50 | "type": "KMT_CLICK_TWICE", 51 | "key": "Key_E", 52 | "pos": { 53 | "x": 0.2, 54 | "y": 0.35 55 | } 56 | }, 57 | { 58 | "comment": "自动跑", 59 | "type": "KMT_CLICK", 60 | "key": "Key_Equal", 61 | "pos": { 62 | "x": 0.84, 63 | "y": 0.26 64 | }, 65 | "switchMap": false 66 | }, 67 | { 68 | "comment": "跳", 69 | "type": "KMT_CLICK", 70 | "key": "Key_Space", 71 | "pos": { 72 | "x": 0.96, 73 | "y": 0.7 74 | }, 75 | "switchMap": false 76 | }, 77 | { 78 | "comment": "地图", 79 | "type": "KMT_CLICK", 80 | "key": "Key_M", 81 | "pos": { 82 | "x": 0.98, 83 | "y": 0.03 84 | }, 85 | "switchMap": true 86 | }, 87 | { 88 | "comment": "背包", 89 | "type": "KMT_CLICK", 90 | "key": "Key_Tab", 91 | "pos": { 92 | "x": 0.06, 93 | "y": 0.9 94 | }, 95 | "switchMap": true 96 | }, 97 | { 98 | "comment": "视角", 99 | "type": "KMT_CLICK", 100 | "key": "Key_V", 101 | "pos": { 102 | "x": 0.23, 103 | "y": 0.95 104 | }, 105 | "switchMap": false 106 | }, 107 | { 108 | "comment": "趴", 109 | "type": "KMT_CLICK", 110 | "key": "Key_Z", 111 | "pos": { 112 | "x": 0.95, 113 | "y": 0.9 114 | }, 115 | "switchMap": false 116 | }, 117 | { 118 | "comment": "蹲", 119 | "type": "KMT_CLICK", 120 | "key": "Key_C", 121 | "pos": { 122 | "x": 0.86, 123 | "y": 0.92 124 | }, 125 | "switchMap": false 126 | }, 127 | { 128 | "comment": "换弹", 129 | "type": "KMT_CLICK", 130 | "key": "Key_R", 131 | "pos": { 132 | "x": 0.795, 133 | "y": 0.93 134 | }, 135 | "switchMap": false 136 | }, 137 | { 138 | "comment": "捡东西1", 139 | "type": "KMT_CLICK", 140 | "key": "Key_F", 141 | "pos": { 142 | "x": 0.7, 143 | "y": 0.34 144 | }, 145 | "switchMap": false 146 | }, 147 | { 148 | "comment": "捡东西2", 149 | "type": "KMT_CLICK", 150 | "key": "Key_G", 151 | "pos": { 152 | "x": 0.7, 153 | "y": 0.44 154 | }, 155 | "switchMap": false 156 | }, 157 | { 158 | "comment": "捡东西3", 159 | "type": "KMT_CLICK", 160 | "key": "Key_H", 161 | "pos": { 162 | "x": 0.7, 163 | "y": 0.54 164 | }, 165 | "switchMap": false 166 | }, 167 | { 168 | "comment": "换枪1", 169 | "type": "KMT_CLICK", 170 | "key": "Key_1", 171 | "pos": { 172 | "x": 0.45, 173 | "y": 0.9 174 | }, 175 | "switchMap": false 176 | }, 177 | { 178 | "comment": "换枪2", 179 | "type": "KMT_CLICK", 180 | "key": "Key_2", 181 | "pos": { 182 | "x": 0.55, 183 | "y": 0.9 184 | }, 185 | "switchMap": false 186 | }, 187 | { 188 | "comment": "手雷", 189 | "type": "KMT_CLICK", 190 | "key": "Key_3", 191 | "pos": { 192 | "x": 0.67, 193 | "y": 0.92 194 | }, 195 | "switchMap": false 196 | }, 197 | { 198 | "comment": "快速打药", 199 | "type": "KMT_CLICK", 200 | "key": "Key_4", 201 | "pos": { 202 | "x": 0.33, 203 | "y": 0.95 204 | }, 205 | "switchMap": false 206 | }, 207 | { 208 | "comment": "下车", 209 | "type": "KMT_CLICK", 210 | "key": "Key_5", 211 | "pos": { 212 | "x": 0.92, 213 | "y": 0.4 214 | }, 215 | "switchMap": false 216 | }, 217 | { 218 | "comment": "救人", 219 | "type": "KMT_CLICK", 220 | "key": "Key_6", 221 | "pos": { 222 | "x": 0.49, 223 | "y": 0.63 224 | }, 225 | "switchMap": false 226 | }, 227 | { 228 | "comment": "手枪", 229 | "type": "KMT_CLICK", 230 | "key": "Key_7", 231 | "pos": { 232 | "x": 0.63, 233 | "y": 0.82 234 | }, 235 | "switchMap": false 236 | }, 237 | { 238 | "comment": "车加速", 239 | "type": "KMT_CLICK", 240 | "key": "Key_Shift", 241 | "pos": { 242 | "x": 0.8, 243 | "y": 0.8 244 | }, 245 | "switchMap": false 246 | }, 247 | { 248 | "comment": "投掷物菜单", 249 | "type": "KMT_CLICK", 250 | "key": "Key_F1", 251 | "pos": { 252 | "x": 0.69, 253 | "y": 0.88 254 | }, 255 | "switchMap": true 256 | }, 257 | { 258 | "comment": "药物菜单", 259 | "type": "KMT_CLICK", 260 | "key": "Key_F2", 261 | "pos": { 262 | "x": 0.31, 263 | "y": 0.88 264 | }, 265 | "switchMap": true 266 | }, 267 | { 268 | "comment": "消息菜单", 269 | "type": "KMT_CLICK", 270 | "key": "Key_F3", 271 | "pos": { 272 | "x": 0.98, 273 | "y": 0.34 274 | }, 275 | "switchMap": true 276 | }, 277 | { 278 | "comment": "表情菜单", 279 | "type": "KMT_CLICK", 280 | "key": "Key_F4", 281 | "pos": { 282 | "x": 0.81, 283 | "y": 0.03 284 | }, 285 | "switchMap": true 286 | }, 287 | { 288 | "comment": "开关门", 289 | "type": "KMT_CLICK", 290 | "key": "Key_X", 291 | "pos": { 292 | "x": 0.7, 293 | "y": 0.7 294 | }, 295 | "switchMap": false 296 | }, 297 | { 298 | "comment": "舔包", 299 | "type": "KMT_CLICK", 300 | "key": "Key_T", 301 | "pos": { 302 | "x": 0.72, 303 | "y": 0.26 304 | }, 305 | "switchMap": false 306 | }, 307 | { 308 | "comment": "开枪", 309 | "type": "KMT_CLICK", 310 | "key": "LeftButton", 311 | "pos": { 312 | "x": 0.86, 313 | "y": 0.72 314 | }, 315 | "switchMap": false 316 | }, 317 | { 318 | "comment": "开镜", 319 | "type": "KMT_CLICK", 320 | "key": "RightButton", 321 | "pos": { 322 | "x": 0.96, 323 | "y": 0.52 324 | }, 325 | "switchMap": false 326 | } 327 | ] 328 | } 329 | -------------------------------------------------------------------------------- /keymap/identityv.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment":"https://doc.qt.io/qt-5/qt.html#Key-enum", 3 | "old-switchKey": "Key_QuoteLeft", 4 | "switchKey": "RightButton", 5 | "mouseMoveMap": { 6 | "startPos": { 7 | "x": 0.700, 8 | "y": 0.410 9 | }, 10 | "speedRatioX": 3.25, 11 | "speedRatioY": 1.25 12 | }, 13 | "keyMapNodes": [{ 14 | "comment": "退出", 15 | "type": "KMT_CLICK", 16 | "key": "Key_Escape", 17 | "pos": { 18 | "x": 0.015, 19 | "y": 0.042 20 | }, 21 | "switchMap": true 22 | }, 23 | { 24 | "comment": "方向盘", 25 | "type": "KMT_STEER_WHEEL", 26 | "centerPos": { 27 | "x": 0.16, 28 | "y": 0.70 29 | }, 30 | "leftOffset": 0.1, 31 | "rightOffset": 0.1, 32 | "upOffset": 0.1, 33 | "downOffset": 0.1, 34 | "leftKey": "Key_A", 35 | "rightKey": "Key_D", 36 | "upKey": "Key_W", 37 | "downKey": "Key_S" 38 | }, 39 | { 40 | "comment": "动作", 41 | "type": "KMT_CLICK", 42 | "key": "LeftButton", 43 | "pos": { 44 | "x": 0.907, 45 | "y": 0.842 46 | }, 47 | "switchMap": false 48 | }, 49 | { 50 | "comment": "动作", 51 | "type": "KMT_CLICK", 52 | "key": "Key_Space", 53 | "pos": { 54 | "x": 0.907, 55 | "y": 0.842 56 | }, 57 | "switchMap": false 58 | }, 59 | { 60 | "comment": "蹲", 61 | "type": "KMT_CLICK", 62 | "key": "Key_Control", 63 | "pos": { 64 | "x": 0.8125, 65 | "y": 0.912 66 | }, 67 | "switchMap": false 68 | }, 69 | { 70 | "comment": "走/翻越", 71 | "type": "KMT_CLICK", 72 | "key": "Key_C", 73 | "pos": { 74 | "x": 0.815, 75 | "y": 0.761 76 | }, 77 | "switchMap": false 78 | }, 79 | { 80 | "comment": "跑/技能1", 81 | "type": "KMT_CLICK", 82 | "key": "Key_Z", 83 | "pos": { 84 | "x": 0.868, 85 | "y": 0.636 86 | }, 87 | "switchMap": false 88 | }, 89 | { 90 | "comment": "技能2", 91 | "type": "KMT_CLICK", 92 | "key": "Key_E", 93 | "pos": { 94 | "x": 0.945, 95 | "y": 0.619 96 | }, 97 | "switchMap": false 98 | }, 99 | { 100 | "comment": "特质/道具1", 101 | "type": "KMT_CLICK", 102 | "key": "Key_Q", 103 | "pos": { 104 | "x": 0.949, 105 | "y": 0.458 106 | }, 107 | "switchMap": false 108 | }, 109 | { 110 | "comment": "底牌/切换1", 111 | "type": "KMT_CLICK", 112 | "key": "Key_Tab", 113 | "pos": { 114 | "x": 0.885, 115 | "y": 0.488 116 | }, 117 | "switchMap": false 118 | }, 119 | { 120 | "comment": "发言/道具2", 121 | "type": "KMT_CLICK", 122 | "key": "Key_R", 123 | "pos": { 124 | "x": 0.950, 125 | "y": 0.308 126 | }, 127 | "switchMap": false 128 | }, 129 | { 130 | "comment": "涂鸦", 131 | "type": "KMT_CLICK", 132 | "key": "Key_Y", 133 | "pos": { 134 | "x": 0.732, 135 | "y": 0.904 136 | }, 137 | "switchMap": false 138 | }, 139 | { 140 | "comment": "盯红蝶/挂人", 141 | "type": "KMT_CLICK", 142 | "key": "Key_F", 143 | "pos": { 144 | "x": 0.815, 145 | "y": 0.514 146 | }, 147 | "switchMap": false 148 | }, 149 | { 150 | "comment": "判定", 151 | "type": "KMT_CLICK", 152 | "key": "Key_T", 153 | "pos": { 154 | "x": 0.681, 155 | "y": 0.750 156 | }, 157 | "switchMap": false 158 | }, 159 | { 160 | "comment": "中间", 161 | "type": "KMT_CLICK", 162 | "key": "Key_Shift", 163 | "pos": { 164 | "x": 0.5, 165 | "y": 0.6 166 | }, 167 | "switchMap": false 168 | }, 169 | { 170 | "comment": "小丑零件1", 171 | "type": "KMT_DRAG", 172 | "key": "Key_1", 173 | "startPos": { 174 | "x": 0.951, 175 | "y": 0.615 176 | }, 177 | "endPos": { 178 | "x": 0.911, 179 | "y": 0.472 180 | } 181 | }, 182 | { 183 | "comment": "小丑零件2", 184 | "type": "KMT_DRAG", 185 | "key": "Key_2", 186 | "startPos": { 187 | "x": 0.951, 188 | "y": 0.615 189 | }, 190 | "endPos": { 191 | "x": 0.861, 192 | "y": 0.615 193 | } 194 | }, 195 | { 196 | "comment": "小丑零件3", 197 | "type": "KMT_DRAG", 198 | "key": "Key_3", 199 | "startPos": { 200 | "x": 0.951, 201 | "y": 0.615 202 | }, 203 | "endPos": { 204 | "x": 0.907, 205 | "y": 0.774 206 | } 207 | }, 208 | { 209 | "comment": "挣扎左", 210 | "type": "KMT_CLICK", 211 | "key": "Key_Left", 212 | "pos": { 213 | "x": 0.267, 214 | "y": 0.550 215 | } 216 | }, 217 | { 218 | "comment": "挣扎右", 219 | "type": "KMT_CLICK", 220 | "key": "Key_Right", 221 | "pos": { 222 | "x": 0.736, 223 | "y": 0.550 224 | } 225 | }, 226 | { 227 | "comment": "小镜头", 228 | "type": "KMT_CLICK", 229 | "key": "Key_Alt", 230 | "pos": { 231 | "x": 0.801, 232 | "y": 0.244 233 | }, 234 | "speedRatio": 2 235 | } 236 | ] 237 | } 238 | -------------------------------------------------------------------------------- /keymap/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "switchKey": "Key_QuoteLeft", 3 | "keyMapNodes": [ 4 | { 5 | "comment": "测试一键多点", 6 | "type": "KMT_CLICK_MULTI", 7 | "key": "Key_Space", 8 | "clickNodes": [ 9 | { 10 | "delay": 500, 11 | "pos": { 12 | "x": 0.5, 13 | "y": 0.5 14 | } 15 | }, 16 | { 17 | "delay": 500, 18 | "pos": { 19 | "x": 0.8, 20 | "y": 0.8 21 | } 22 | } 23 | ] 24 | }, 25 | { 26 | "comment": "测试拖拽", 27 | "type": "KMT_DRAG", 28 | "key": "Key_Up", 29 | "startPos": { 30 | "x": 0.5, 31 | "y": 0.7 32 | }, 33 | "endPos": { 34 | "x": 0.5, 35 | "y": 0.3 36 | } 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /keymap/tiktok.json: -------------------------------------------------------------------------------- 1 | { 2 | "switchKey": "Key_QuoteLeft", 3 | "keyMapNodes": [ 4 | { 5 | "comment": "暂停/继续", 6 | "type": "KMT_CLICK", 7 | "key": "Key_Space", 8 | "pos": { 9 | "x": 0.5, 10 | "y": 0.5 11 | }, 12 | "switchMap": false 13 | }, 14 | { 15 | "comment": "上滑", 16 | "type": "KMT_DRAG", 17 | "key": "Key_Up", 18 | "startPos": { 19 | "x": 0.5, 20 | "y": 0.7 21 | }, 22 | "endPos": { 23 | "x": 0.5, 24 | "y": 0.3 25 | } 26 | }, 27 | { 28 | "comment": "下滑", 29 | "type": "KMT_DRAG", 30 | "key": "Key_Down", 31 | "startPos": { 32 | "x": 0.5, 33 | "y": 0.3 34 | }, 35 | "endPos": { 36 | "x": 0.5, 37 | "y": 0.7 38 | } 39 | }, 40 | { 41 | "comment": "左滑", 42 | "type": "KMT_DRAG", 43 | "key": "Key_Left", 44 | "startPos": { 45 | "x": 0.7, 46 | "y": 0.5 47 | }, 48 | "endPos": { 49 | "x": 0.3, 50 | "y": 0.5 51 | } 52 | }, 53 | { 54 | "comment": "右滑", 55 | "type": "KMT_DRAG", 56 | "key": "Key_Right", 57 | "startPos": { 58 | "x": 0.3, 59 | "y": 0.5 60 | }, 61 | "endPos": { 62 | "x": 0.7, 63 | "y": 0.5 64 | } 65 | } 66 | ] 67 | } -------------------------------------------------------------------------------- /screenshot/game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/screenshot/game.png -------------------------------------------------------------------------------- /screenshot/linux-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/screenshot/linux-en.png -------------------------------------------------------------------------------- /screenshot/linux-zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/screenshot/linux-zh.png -------------------------------------------------------------------------------- /screenshot/mac-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/screenshot/mac-en.png -------------------------------------------------------------------------------- /screenshot/mac-zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/screenshot/mac-zh.png -------------------------------------------------------------------------------- /screenshot/win-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/screenshot/win-en.png -------------------------------------------------------------------------------- /screenshot/win-zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/barry-ran/QtScrcpy/98d6bd05e3572c86b433bc392ab19cad3720dfaa/screenshot/win-zh.png --------------------------------------------------------------------------------