[
  {
    "path": ".clang-format",
    "content": "---\n# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto\nLanguage:        Cpp\n# BasedOnStyle:  WebKit\n# 访问说明符(public、private等)的偏移\nAccessModifierOffset: -4\n# 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行)\nAlignAfterOpenBracket: AlwaysBreak\n# 连续赋值时，对齐所有等号\nAlignConsecutiveAssignments: false\n# 连续声明时，对齐所有声明的变量名\nAlignConsecutiveDeclarations: false\n# 左对齐逃脱换行(使用反斜杠换行)的反斜杠\nAlignEscapedNewlines: Right\n# 水平对齐二元和三元表达式的操作数\nAlignOperands:   true\n# 对齐连续的尾随的注释\nAlignTrailingComments: true\n# 允许函数声明的所有参数在放在下一行\nAllowAllParametersOfDeclarationOnNextLine: false\n# 允许短的块放在同一行\nAllowShortBlocksOnASingleLine: false\n# 允许短的case标签放在同一行\nAllowShortCaseLabelsOnASingleLine: false\n# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中，空函数), All\nAllowShortFunctionsOnASingleLine: Empty\n# 允许短的if语句保持在同一行\nAllowShortIfStatementsOnASingleLine: false\n# 允许短的循环保持在同一行\nAllowShortLoopsOnASingleLine: false\n# 总是在定义返回类型后换行(deprecated)\nAlwaysBreakAfterDefinitionReturnType: None\n# 总是在返回类型后换行: None, All, TopLevel(顶级函数，不包括在类中的函数),\n#   AllDefinitions(所有的定义，不包括声明), TopLevelDefinitions(所有的顶级函数的定义)\nAlwaysBreakAfterReturnType: None\n# 总是在多行string字面量前换行\nAlwaysBreakBeforeMultilineStrings: false\n# 总是在template声明后换行\nAlwaysBreakTemplateDeclarations: true\n# false表示函数实参要么都在同一行，要么都各自一行\nBinPackArguments: false\n# false表示所有形参要么都在同一行，要么都各自一行\nBinPackParameters: false\n\n# 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义，与Attach类似),\n#   Mozilla(除枚举、函数、记录定义，与Attach类似), Stroustrup(除函数定义、catch、else，与Attach类似),\n#   Allman(总是在大括号前换行), GNU(总是在大括号前换行，并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom\n#   注：这里认为语句块也属于函数\nBreakBeforeBraces: Custom\n# 大括号换行，只有当BreakBeforeBraces设置为Custom时才有效\nBraceWrapping:   \n  # class定义后面\n  AfterClass: true\n  # 控制语句后面\n  AfterControlStatement: false\n  # enum定义后面\n  AfterEnum: true\n  # 函数定义后面\n  AfterFunction: true\n  # 命名空间定义后面\n  AfterNamespace: true\n  # ObjC定义后面\n  AfterObjCDeclaration: false\n  # struct定义后面\n  AfterStruct: true\n  # union定义后面\n  AfterUnion: true\n  # extern 定义后面\n  AfterExternBlock: true\n  # catch之前\n  BeforeCatch: false\n  # else 之前\n  BeforeElse: false\n  # 缩进大括号\n  IndentBraces: false\n\n# 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行)\nBreakBeforeBinaryOperators: All\n\n# 继承列表的逗号前换行\nBreakBeforeInheritanceComma: true\n# 继承列表换行\n#BreakInheritanceList: BeforeColon\n# 在三元运算符前换行\nBreakBeforeTernaryOperators: true\n# 在构造函数的初始化列表的逗号前换行\nBreakConstructorInitializersBeforeComma: true\n# 初始化列表前换行\nBreakConstructorInitializers: BeforeComma\n# Java注解后换行\nBreakAfterJavaFieldAnnotations: false\n\nBreakStringLiterals: true\n# 每行字符的限制，0表示没有限制\nColumnLimit:     160\n# 描述具有特殊意义的注释的正则表达式，它不应该被分割为多行或以其它方式改变\nCommentPragmas:  '^ IWYU pragma:'\n# 紧凑 命名空间\nCompactNamespaces: false\n# 构造函数的初始化列表要么都在同一行，要么都各自一行\nConstructorInitializerAllOnOneLineOrOnePerLine: true\n# 构造函数的初始化列表的缩进宽度\nConstructorInitializerIndentWidth: 4\n# 延续的行的缩进宽度\nContinuationIndentWidth: 4\n# 去除C++11的列表初始化的大括号{后和}前的空格\nCpp11BracedListStyle: false\n# 继承最常用的指针和引用的对齐方式\nDerivePointerAlignment: false\n# 关闭格式化\nDisableFormat:   false\n# 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental)\nExperimentalAutoDetectBinPacking: false\n# 固定命名空间注释\nFixNamespaceComments: true\n# 需要被解读为foreach循环而不是函数调用的宏\nForEachMacros:   \n  - foreach\n  - Q_FOREACH\n  - BOOST_FOREACH\n\nIncludeBlocks:   Preserve\n# 对#include进行排序，匹配了某正则表达式的#include拥有对应的优先级，匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前)，\n#   可以定义负数优先级从而保证某些#include永远在最前面\nIncludeCategories: \n  - Regex:           '^\"(llvm|llvm-c|clang|clang-c)/'\n    Priority:        2\n  - Regex:           '^(<|\"(gtest|gmock|isl|json)/)'\n    Priority:        3\n  - Regex:           'stdafx\\.'  \n    Priority:        1\n  - Regex:           '.*'\n    Priority:        1\n\nIncludeIsMainRegex: '(Test)?$'\n# 缩进case标签\nIndentCaseLabels: false\n\nIndentPPDirectives: None\n# 缩进宽度\nIndentWidth:     4\n# 函数返回类型换行时，缩进函数声明或函数定义的函数名\nIndentWrappedFunctionNames: true\n\nJavaScriptQuotes: Leave\n\nJavaScriptWrapImports: true\n# 保留在块开始处的空行\nKeepEmptyLinesAtTheStartOfBlocks: true\n# 开始一个块的宏的正则表达式\nMacroBlockBegin: ''\n# 结束一个块的宏的正则表达式\nMacroBlockEnd:   ''\n# 连续空行的最大数量\nMaxEmptyLinesToKeep: 1\n\n# 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All\nNamespaceIndentation: All\nObjCBinPackProtocolList: Auto\n# 使用ObjC块时缩进宽度\nObjCBlockIndentWidth: 4\n# 在ObjC的@property后添加一个空格\nObjCSpaceAfterProperty: true\n# 在ObjC的protocol列表前添加一个空格\nObjCSpaceBeforeProtocolList: true\n\nPenaltyBreakAssignment: 2\n\nPenaltyBreakBeforeFirstCallParameter: 19\n# 在一个注释中引入换行的penalty\nPenaltyBreakComment: 300\n# 第一次在<<前换行的penalty\nPenaltyBreakFirstLessLess: 120\n# 在一个字符串字面量中引入换行的penalty\nPenaltyBreakString: 1000\nPenaltyBreakTemplateDeclaration: 10\n# 对于每个在行字符数限制之外的字符的penalty\nPenaltyExcessCharacter: 1000000\n# 将函数的返回类型放到它自己的行的penalty\nPenaltyReturnTypeOnItsOwnLine: 60\n# 指针和引用的对齐: Left, Right, Middle\nPointerAlignment: Right\n\n#RawStringFormats: \n#  - Delimiter:       pb\n#    Language:        TextProto\n#    BasedOnStyle:    google\n# 允许重新排版注释\nReflowComments:  false\n# 允许排序#include\nSortIncludes:    true\n\nSortUsingDeclarations: true\n# 在C风格类型转换后添加空格\nSpaceAfterCStyleCast: false\n# 模板关键字后面添加空格\nSpaceAfterTemplateKeyword: true\n# 在赋值运算符之前添加空格\nSpaceBeforeAssignmentOperators: true\n# 开圆括号之前添加一个空格: Never, ControlStatements, Always\nSpaceBeforeCpp11BracedList: false\nSpaceBeforeCtorInitializerColon: true\nSpaceBeforeInheritanceColon: true\nSpaceBeforeParens: ControlStatements\nSpaceBeforeRangeBasedForLoopColon: true\n# 在空的圆括号中添加空格\nSpaceInEmptyParentheses: false\n# 在尾随的评论前添加的空格数(只适用于//)\nSpacesBeforeTrailingComments: 1\n# 在尖括号的<后和>前添加空格\nSpacesInAngles:  false\n# 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格\nSpacesInContainerLiterals: true\n# 在C风格类型转换的括号中添加空格\nSpacesInCStyleCastParentheses: false\n# 在圆括号的(后和)前添加空格\nSpacesInParentheses: false\n# 在方括号的[后和]前添加空格，lamda表达式和未指明大小的数组的声明不受影响\nSpacesInSquareBrackets: false\n# 标准: Cpp03, Cpp11, Auto\nStandard:        Cpp11\n# tab宽度\nTabWidth:        4\n# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always\nUseTab:          Never\n...\n\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "\n# These are supported funding model platforms\n\ngithub: barry-ran\npatreon: # Replace with a single Patreon username\nopen_collective: QtScrcpy\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: [\"https://paypal.me/QtScrcpy\", \"https://gitee.com/Barryda/MyPictureBed/blob/master/QtScrcpy/payme.md\"]"
  },
  {
    "path": ".github/workflows/macos.yml",
    "content": "name: MacOS\non: \n  push:\n    paths:\n      - 'QtScrcpy/**'\n      - '!QtScrcpy/res/**'\n      - '.github/workflows/macos.yml'\n  pull_request:\n    paths:\n      - 'QtScrcpy/**'\n      - '!QtScrcpy/res/**'\n      - '.github/workflows/macos.yml'\njobs:\n  build:\n    name: Build\n    # install-qt-action在arm上执行macdeployqt会报parse otool错误，所以在intel mac上执行:\n    # 用qt6时在arm mac上编译arm和intel都没有问题\n    # qt5+intel mac编译intel没问题\n    # qt5+arm mac编译intel会报错\n    # https://github.com/actions/runner-images?tab=readme-ov-file#available-images\n    runs-on: macos-13\n    strategy:\n      matrix:\n        qt-ver: [5.15.2, 6.5.3]        \n        # 配置qt-ver的额外设置qt-arch-install，build-arch\n        include:\n          - qt-ver: 5.15.2\n            qt-arch-install: clang_64\n            build-arch: x64\n          - qt-ver: 6.5.3\n            qt-arch-install: arm64\n            build-arch: arm64\n    env:\n      target-name: QtScrcpy\n      qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}\n      plantform-des: mac\n    steps:\n      - name: Cache Qt\n        id: cache-qt\n        uses: actions/cache@v4\n        with:\n          path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}\n          key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}\n      - name: Install Qt5\n        if: startsWith(matrix.qt-ver, '5.')\n        uses: jurplel/install-qt-action@v4.1.1\n        with:\n          version: ${{ matrix.qt-ver }}\n          cached: ${{ steps.cache-qt.outputs.cache-hit }}\n      - name: Install Qt6        \n        if: startsWith(matrix.qt-ver, '6.')\n        uses: jurplel/install-qt-action@v4.1.1\n        with:\n          version: ${{ matrix.qt-ver }}\n          modules: qtmultimedia\n          cached: ${{ steps.cache-qt.outputs.cache-hit }}\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n          submodules: 'true'\n          ssh-key: ${{ secrets.BOT_SSH_KEY }}\n      # 编译\n      - name: Build MacOS\n        env:\n          ENV_QT_PATH: ${{ env.qt-install-path }}\n        run: |\n          python ci/generate-version.py\n          ci/mac/build_for_mac.sh RelWithDebInfo ${{ matrix.build-arch }}\n      # 获取ref最后一个/后的内容\n      - name: Get the version\n        shell: bash\n        id: get-version\n        # ${ GITHUB_REF/refs\\/tags\\// }是linux shell ${}的变量替换语法\n        run: echo ::set-output name=version::${GITHUB_REF##*/}\n      # 打包\n      - name: Package\n        id: package\n        env:\n          ENV_QT_PATH: ${{ env.qt-install-path }}\n          publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.build-arch }}-Qt${{matrix.qt-ver}}-${{ steps.get-version.outputs.version }}\n        run: |\n          ci/mac/publish_for_mac.sh ../build ${{ matrix.build-arch }}\n          ci/mac/package_for_mac.sh\n          mv ci/build/QtScrcpy.app ci/build/${{ env.publish_name }}.app\n          mv ci/build/QtScrcpy.dmg ci/build/${{ env.publish_name }}.dmg\n          echo \"::set-output name=package-name::${{ env.publish_name }}\"\n      - uses: actions/upload-artifact@v4\n        with:\n          name: ${{ steps.package.outputs.package-name }}.zip\n          path: ci/build/${{ steps.package.outputs.package-name }}.dmg\n      # Upload to release\n      - name: Upload Release\n        if: startsWith(github.ref, 'refs/tags/')\n        uses: svenstaro/upload-release-action@v1-release\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: ci/build/${{ steps.package.outputs.package-name }}.dmg\n          asset_name: ${{ steps.package.outputs.package-name }}.dmg\n          tag: ${{ github.ref }}\n          overwrite: true"
  },
  {
    "path": ".github/workflows/ubuntu.yml",
    "content": "name: Ubuntu\non: \n  push:\n    paths:\n      - 'QtScrcpy/**'\n      - '!QtScrcpy/res/**'\n      - '.github/workflows/ubuntu.yml'\n      - 'ci/linux/**'\n  pull_request:\n    paths:\n      - 'QtScrcpy/**'\n      - '!QtScrcpy/res/**'\n      - '.github/workflows/ubuntu.yml'\n      - 'ci/linux/**'\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-22.04\n    container:\n      image: ubuntu:20.04\n      options: --privileged\n    strategy:\n      matrix:\n        qt-ver: [5.15.2]\n        qt-arch-install: [gcc_64]\n        gcc-arch: [x64]\n    env:\n      target-name: QtScrcpy\n      qt-install-path: ${{ github.workspace }}/Qt/${{ matrix.qt-ver }}\n      plantform-des: ubuntu\n      DEBIAN_FRONTEND: noninteractive\n    steps:\n      - name: Install Git and basic dependencies\n        run: |\n          apt-get update\n          apt-get install -y git ca-certificates sudo\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n          submodules: 'true'\n          ssh-key: ${{ secrets.BOT_SSH_KEY }}\n      - name: Install system dependencies\n        run: |\n          apt-get update\n          apt-get install -y \\\n            build-essential \\\n            cmake \\\n            libglew-dev \\\n            libglfw3-dev \\\n            imagemagick \\\n            wget \\\n            patchelf \\\n            zip \\\n            libxcb1-dev \\\n            libxkbcommon-dev \\\n            libxkbcommon-x11-dev \\\n            libx11-dev \\\n            libx11-xcb-dev \\\n            libfontconfig1-dev \\\n            libfreetype6-dev \\\n            libxrender-dev \\\n            libxext-dev \\\n            gnupg \\\n            lsb-release \\\n            python3 \\\n            python3-pip \\\n            fuse \\\n            libasound2-dev\n      - name: Setup FUSE\n        run: |\n          apt-get install -y fuse\n          if [ ! -e /dev/fuse ]; then\n            mknod /dev/fuse c 10 229 || true\n            chmod 666 /dev/fuse || true\n          fi\n          export APPIMAGE_EXTRACT_AND_RUN=1\n      - name: Install CMake 3.19+\n        run: |\n          apt-get install -y software-properties-common\n          apt-get update\n          apt-get install -y cmake=3.19.* || {\n            wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | apt-key add -\n            apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main'\n            apt-get update\n            apt-get install -y cmake\n          }\n      - name: Cache Qt\n        id: cache-qt\n        uses: actions/cache@v4\n        with:\n          path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}\n          key: ubuntu-20.04/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}\n      - name: Install Qt\n        uses: jurplel/install-qt-action@v4.1.1\n        with:\n          version: ${{ matrix.qt-ver }}\n          cache: ${{ steps.cache-qt.outputs.cache-hit }}\n          setup-python: false\n      - name: Build Release\n        shell: bash\n        env:\n          ENV_QT_PATH: ${{ github.workspace }}/Qt/${{ matrix.qt-ver }}\n        run: |\n          python3 ci/generate-version.py\n          ci/linux/build_for_linux.sh \"Release\"\n      - name: Get the version\n        shell: bash\n        id: get-version\n        run: echo ::set-output name=version::${GITHUB_REF##*/}\n      - name: Package AppImage\n        shell: bash\n        env:\n          ENV_QT_PATH: ${{ github.workspace }}/Qt/${{ matrix.qt-ver }}\n        run: |\n          chmod +x ci/linux/package_appimage.sh\n          ci/linux/package_appimage.sh \"Release\"\n      - name: Upload AppImage Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}-AppImage\n          path: output/appimage/*.AppImage\n          if-no-files-found: error\n      - name: Prepare AppImage for Release\n        if: startsWith(github.ref, 'refs/tags/')\n        run: |\n          APPIMAGE_FILE=$(find output/appimage -name \"QtScrcpy-*.AppImage\" -type f | head -n 1)\n          if [ -z \"$APPIMAGE_FILE\" ] || [ ! -f \"$APPIMAGE_FILE\" ]; then\n            echo \"Error: AppImage file not found\"\n            exit 1\n          fi\n          FINAL_NAME=\"${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}.AppImage\"\n          cp \"$APPIMAGE_FILE\" \"$FINAL_NAME\"\n      - name: Upload AppImage to Releases\n        if: startsWith(github.ref, 'refs/tags/')\n        uses: svenstaro/upload-release-action@2.3.0\n        with:\n          file: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}.AppImage\n          asset_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}.AppImage\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          tag: ${{ github.ref }}\n          overwrite: true"
  },
  {
    "path": ".github/workflows/windows.yml",
    "content": "name: Windows\n# 触发规则详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#on\non: \n  push:\n    paths:\n      - 'QtScrcpy/**'\n      - '!QtScrcpy/res/**'\n      - '.github/workflows/windows.yml'\n      - 'ci/win**'\n  pull_request:\n    paths:\n      - 'QtScrcpy/**'\n      - '!QtScrcpy/res/**'\n      - '.github/workflows/windows.yml'\n      - 'ci/win**'\njobs:\n  build:\n    name: Build\n    # windows-latest目前是windows server 2022\n    # windows server 2019安装的是vs2019，windows server 2016安装的是vs2017\n    # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on\n    runs-on: windows-latest\n\n    # 矩阵配置 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix\n    strategy:\n      matrix:\n        qt-ver: [5.15.2]\n        qt-arch: [win64_msvc2019_64, win32_msvc2019]\n        # 配置qt-arch的额外设置msvc-arch，qt-arch-install\n        include:\n          - qt-arch: win64_msvc2019_64\n            msvc-arch: x64\n            qt-arch-install: msvc2019_64\n          - qt-arch: win32_msvc2019\n            msvc-arch: x86\n            qt-arch-install: msvc2019\n    # job env,所有steps都可以访问\n    # 不同级别env详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#env\n    # 使用表达式语法${{}}访问上下文 https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions\n    env:\n      target-name: QtScrcpy\n      vcvarsall-path: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat'\n      vcinstall-path: 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\Enterprise\\VC'\n      qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}\n      plantform-des: win\n    # 步骤\n    steps:\n      - name: Cache Qt\n        id: cache-qt\n        uses: actions/cache@v4\n        with:\n          path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}\n          key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch }}        \n      # 安装Qt\n      - name: Install Qt\n        # 使用外部action。这个action专门用来安装Qt\n        uses: jurplel/install-qt-action@v4.1.1\n        with:\n          # Version of Qt to install\n          version: ${{ matrix.qt-ver }}\n          # Target platform for build\n          target: desktop\n          # Architecture for Windows/Android\n          arch: ${{ matrix.qt-arch }}\n          cached: ${{ steps.cache-qt.outputs.cache-hit }}\n      # 拉取代码\n      - uses: actions/checkout@v2\n        with:\n          fetch-depth: 0\n          submodules: 'true'\n          ssh-key: ${{ secrets.BOT_SSH_KEY }}\n      # 编译msvc\n      - name: Build MSVC\n        # shell介绍 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell\n        shell: cmd\n        env:\n          ENV_VCVARSALL: ${{ env.vcvarsall-path }}\n          ENV_QT_PATH: ${{ env.qt-install-path }}\n        run: |\n          call python ci\\generate-version.py\n          call \"ci\\win\\build_for_win.bat\" RelWithDebInfo ${{ matrix.msvc-arch }}\n      # 获取ref最后一个/后的内容\n      - name: Get the version\n        shell: bash\n        id: get-version\n        # ${ GITHUB_REF/refs\\/tags\\// }是linux shell ${}的变量替换语法\n        run: echo ::set-output name=version::${GITHUB_REF##*/}\n      # tag 打包\n      - name: Package\n        id: package\n        env:\n          ENV_VCVARSALL: ${{ env.vcvarsall-path }}\n          ENV_VCINSTALL: ${{ env.vcinstall-path }}\n          ENV_QT_PATH: ${{ env.qt-install-path }}\n          publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.msvc-arch }}-${{ steps.get-version.outputs.version }}\n        run: |\n          cmd.exe /c ci\\win\\publish_for_win.bat ${{ matrix.msvc-arch }} ..\\build\\${{ env.publish_name }}\n          # 打包zip\n          Compress-Archive -Path ci\\build\\${{ env.publish_name }} ci\\build\\${{ env.publish_name }}.zip\n          echo \"::set-output name=package-name::${{ env.publish_name }}\"\n      # 上传artifacts\n      # https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts\n      - uses: actions/upload-artifact@v4\n        with:\n          name: ${{ steps.package.outputs.package-name }}.zip\n          path: ci\\build\\${{ steps.package.outputs.package-name }}\n      # Upload to release\n      - name: Upload Release\n        if: startsWith(github.ref, 'refs/tags/')\n        uses: svenstaro/upload-release-action@v1-release\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: ci\\build\\${{ steps.package.outputs.package-name }}.zip\n          asset_name: ${{ steps.package.outputs.package-name }}.zip\n          tag: ${{ github.ref }}\n          overwrite: true"
  },
  {
    "path": ".gitignore",
    "content": "/output\n*.user\n/QtScrcpy/*.user\n/server/.gradle\n/server/.idea\n/server/build\n/server/gradle/wrapper/gradle-wrapper.jar\n/server/gradle/wrapper/gradle-wrapper.properties\n/server/gradlew\n/server/gradlew.bat\n/server/local.properties\n/build/\nbuild-*\n*.DS_Store\nuserdata.ini\nInfo_Mac.plist\n/ci/build_temp"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"QtScrcpy/QtScrcpyCore\"]\n\tpath = QtScrcpy/QtScrcpyCore\n\turl = git@github.com:barry-ran/QtScrcpyCore.git\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "﻿cmake_minimum_required(VERSION 3.19 FATAL_ERROR)\nproject(all)\n\nadd_subdirectory(QtScrcpy)\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright (C) 2019 Rankun\n   Copyright (C) 2019-2025 Rankun\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "QtScrcpy/CMakeLists.txt",
    "content": "# For VS2019 and Xcode 12+ support.\ncmake_minimum_required(VERSION 3.19 FATAL_ERROR)\n\n#\n# Global config\n#\n\n# QC is \"Qt CMake\"\n# https://www.kdab.com/wp-content/uploads/stories/QTVTC20-Using-Modern-CMake-Kevin-Funk.pdf\n\n# QC Custom config\nset(QC_PROJECT_NAME \"QtScrcpy\")\n# Read version numbers from file\nfile(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/appversion QC_FILE_VERSION)\nset(QC_PROJECT_VERSION ${QC_FILE_VERSION})\n\n# Project declare\nproject(${QC_PROJECT_NAME} VERSION ${QC_PROJECT_VERSION} LANGUAGES CXX)\nmessage(STATUS \"[${PROJECT_NAME}] Project ${PROJECT_NAME} ${PROJECT_VERSION}\")\n\n# QC define\n\n# check arch\nif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n    set(QC_CPU_ARCH x64)\nelse()\n    set(QC_CPU_ARCH x86)\nendif()\n\n# MacOS\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    # mac default arch arm64\n    if(NOT CMAKE_OSX_ARCHITECTURES)\n        set(CMAKE_OSX_ARCHITECTURES arm64)\n    endif()\n\n    if (CMAKE_OSX_ARCHITECTURES MATCHES \"arm64\")\n        set(QC_CPU_ARCH arm64)\n    endif()\nendif()\n\nmessage(STATUS \"[${PROJECT_NAME}] CPU_ARCH:${QC_CPU_ARCH}\")\n\n# CMake set\nset(CMAKE_INCLUDE_CURRENT_DIR ON)\nset(CMAKE_CXX_STANDARD 11)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n# default RelWithDebInfo\nif(NOT CMAKE_BUILD_TYPE)\n    set(CMAKE_BUILD_TYPE RelWithDebInfo)\nendif()\nmessage(STATUS \"[${PROJECT_NAME}] BUILD_TYPE:${CMAKE_BUILD_TYPE}\")\n\n# Log configuration\noption(ENABLE_DETAILED_LOGS \"Enable detailed log output with file and line info\" OFF)\nif(ENABLE_DETAILED_LOGS)\n    message(STATUS \"[${PROJECT_NAME}] Detailed logs enabled\")\nelse()\n    message(STATUS \"[${PROJECT_NAME}] Simple logs enabled\")\nendif()\n\n# Compiler set\nmessage(STATUS \"[${PROJECT_NAME}] C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}\")\nif (MSVC)\n    # FFmpeg cannot be compiled natively by MSVC version < 12.0 (2013)\n    if(MSVC_VERSION LESS 1800)\n        message(FATAL_ERROR \"[${PROJECT_NAME}] ERROR: MSVC version is older than 12.0 (2013).\")\n    endif()\n\n    message(STATUS \"[${PROJECT_NAME}] Set Warnings as error\")\n    # warning level 3 and all warnings as errors\n    add_compile_options(/W3 /WX /wd4566)\n\n    # avoid warning C4819\n    #add_compile_options(-source-charset:utf-8)\n    # /utf-8 will set source charset and execution charset to utf-8, so we don't need to set source-charset:utf-8\n    add_compile_options(/utf-8)\n\n    # ensure we use minimal \"windows.h\" lib without the crazy min max macros\n    add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)\n    \n    # disable SAFESEH - avoid \"LNK2026: module unsafe\"(Qt5.15&&vs2019)     \n    add_link_options(/SAFESEH:NO)\nendif()\n\nif (NOT MSVC)\n    message(STATUS \"[${PROJECT_NAME}] Set warnings as error\")\n    # lots of warnings and all warnings as errors\n    add_compile_options(-Wall -Wextra -pedantic -Werror)\n\n    # disable some warning\n    add_compile_options(-Wno-nested-anon-types -Wno-c++17-extensions -Wno-overloaded-virtual)\nendif()\n\n#\n# Qt\n#\n\n# Find Qt version\nif (NOT QT_DESIRED_VERSION)\n    find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)\n    message(\"   >>> Found Qt version: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}\")\n    set(QT_DESIRED_VERSION ${QT_VERSION_MAJOR})\nendif()\n\nset(CMAKE_AUTOUIC ON)\nset(CMAKE_AUTOMOC ON)\nset(CMAKE_AUTORCC ON)\n\nset(qt_required_components Widgets Network Multimedia)\n\nif (QT_DESIRED_VERSION MATCHES 6)\n    # list(APPEND qt_required_components Core5Compat)\n    list(APPEND qt_required_components OpenGL)\n    list(APPEND qt_required_components OpenGLWidgets)\nelse()\n    if(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n        list(APPEND qt_required_components X11Extras )\n    endif()\nendif()\n\nfind_package(Qt${QT_DESIRED_VERSION} REQUIRED COMPONENTS ${qt_required_components})\n\nset(LINK_LIBS\n    Qt${QT_DESIRED_VERSION}::Widgets\n    Qt${QT_DESIRED_VERSION}::Network\n    Qt${QT_DESIRED_VERSION}::Multimedia\n)\n\nif (QT_DESIRED_VERSION MATCHES 6)\n    # list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::Core5Compat)\n    list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::GuiPrivate)\n    list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::OpenGL)\n    list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::OpenGLWidgets)\nelse()\n    if(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n        list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::X11Extras)\n    endif()\nendif()\n\nmessage(STATUS \"[${PROJECT_NAME}] Qt version is: ${QT_DESIRED_VERSION}\")\n\n#\n# Sources\n#\n\n# fontawesome\nset(QC_FONTAWESOME_SOURCES\n    fontawesome/iconhelper.h\n    fontawesome/iconhelper.cpp\n)\nsource_group(fontawesome FILES ${QC_FONTAWESOME_SOURCES})\n\n# uibase\nset(QC_UIBASE_SOURCES\n    uibase/keepratiowidget.h\n    uibase/keepratiowidget.cpp\n    uibase/magneticwidget.h\n    uibase/magneticwidget.cpp\n)\nsource_group(uibase FILES ${QC_UIBASE_SOURCES})\n\n# audio\nset(QC_AUDIO_SOURCES\n    audio/audiooutput.h\n    audio/audiooutput.cpp\n)\nsource_group(audio FILES ${QC_AUDIO_SOURCES})\n\n# ui\nset(QC_UI_SOURCES\n    ui/toolform.h\n    ui/toolform.cpp\n    ui/toolform.ui\n    ui/videoform.h\n    ui/videoform.cpp\n    ui/videoform.ui\n    ui/dialog.cpp\n    ui/dialog.h\n    ui/dialog.ui\n    render/qyuvopenglwidget.h\n    render/qyuvopenglwidget.cpp\n)\nsource_group(ui FILES ${QC_UI_SOURCES})\n\n# group controller\nset(QC_GROUP_CONTROLLER\n    groupcontroller/groupcontroller.h\n    groupcontroller/groupcontroller.cpp\n)\nsource_group(groupcontroller FILES ${QC_GROUP_CONTROLLER})\n\n# util\nset(QC_UTIL_SOURCES\n    util/config.h\n    util/config.cpp\n    util/mousetap/mousetap.h\n    util/mousetap/mousetap.cpp\n)\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES} \n        util/mousetap/winmousetap.h\n        util/mousetap/winmousetap.cpp\n        util/winutils.h\n        util/winutils.cpp\n    )\nendif()\nif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n    set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES} \n        util/mousetap/xmousetap.h\n        util/mousetap/xmousetap.cpp\n    )\nendif()\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES} \n        util/mousetap/cocoamousetap.h\n        util/mousetap/cocoamousetap.mm\n        util/path.h\n        util/path.mm\n    )\nendif()\nsource_group(util FILES ${QC_UTIL_SOURCES})\n\n# qrc\nset(QC_QRC_SOURCES \"res/res.qrc\")\n\n# main\nset(QC_MAIN_SOURCES\n    main.cpp\n    ${QC_QRC_SOURCES}\n)\n\n# plantform file\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    # Define VERSION macros for .rc file\n    add_compile_definitions(\n        VERSION_MAJOR=${PROJECT_VERSION_MAJOR}\n        VERSION_MINOR=${PROJECT_VERSION_MINOR}\n        VERSION_PATCH=${PROJECT_VERSION_PATCH}\n        VERSION_RC_STR=\"${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}\"\n    )\n    set(QC_PLANTFORM_SOURCES\n        \"${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.rc\"\n    )\nendif()\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    # Step 1. add icns to source file, for MACOSX_PACKAGE_LOCATION copy\n    set(QC_PLANTFORM_SOURCES\n        \"${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.icns\"\n    )\nendif()\n\n# 翻译相关（使用shell脚本替代cmake处理翻译）\n# add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/res/i18n)\n\n# all sources\nset(QC_PROJECT_SOURCES\n    ${QC_FONTAWESOME_SOURCES}\n    ${QC_UIBASE_SOURCES}\n    ${QC_UI_SOURCES}\n    ${QC_UTIL_SOURCES}\n    ${QC_MAIN_SOURCES}\n    ${QC_GROUP_CONTROLLER}\n    ${QC_PLANTFORM_SOURCES}\n    ${QC_AUDIO_SOURCES}\n)\n\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    set(QC_RUNTIME_TYPE MACOSX_BUNDLE)\nendif()\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    set(QC_RUNTIME_TYPE WIN32)\nendif()\n\n\nadd_executable(${PROJECT_NAME} ${QC_RUNTIME_TYPE} ${QC_PROJECT_SOURCES})\n\n# Log compile definitions\nif(ENABLE_DETAILED_LOGS)\n    target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_DETAILED_LOGS)\nendif()\n\n#\n# Internal include path (todo: remove this, use absolute path include)\n#\n\ntarget_include_directories(${PROJECT_NAME} PRIVATE fontawesome)\ntarget_include_directories(${PROJECT_NAME} PRIVATE util)\ntarget_include_directories(${PROJECT_NAME} PRIVATE uibase)\ntarget_include_directories(${PROJECT_NAME} PRIVATE ui)\ntarget_include_directories(${PROJECT_NAME} PRIVATE render)\n\n# output dir\n# https://cmake.org/cmake/help/latest/prop_gbl/GENERATOR_IS_MULTI_CONFIG.html\nget_property(QC_IS_MUTIL_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)\nmessage(STATUS \"multi config:\" QC_IS_MUTIL_CONFIG)\n\n# $<0:> 使用生成器表达式为每个config设置RUNTIME_OUTPUT_DIRECTORY，这样multi config就不会自动追加CMAKE_BUILD_TYPE子目录了\n# 1. multi config介绍 https://cmake.org/cmake/help/latest/prop_gbl/GENERATOR_IS_MULTI_CONFIG.html\n# 2. multi config在不用表达式生成器时自动追加子目录说明 https://cmake.org/cmake/help/latest/prop_tgt/RUNTIME_OUTPUT_DIRECTORY.html\n# 3. 使用表达式生成器禁止multi config自动追加子目录解决方案 https://stackoverflow.com/questions/7747857/in-cmake-how-do-i-work-around-the-debug-and-release-directories-visual-studio-2\nset_target_properties(${PROJECT_NAME} PROPERTIES\n    RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}/../output/${QC_CPU_ARCH}/${CMAKE_BUILD_TYPE}/$<0:>\"\n)\n\n#\n# plantform deps\n#\n\n# windows\nif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n    get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)\n    set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})\n\n    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E copy_if_different \"${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.bat\" \"${QSC_BIN_OUTPUT_PATH}\"\n        COMMAND ${CMAKE_COMMAND} -E copy_if_different \"${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk\" \"${QSC_BIN_OUTPUT_PATH}\"\n    )\nendif()\n\n# MacOS\nif(CMAKE_SYSTEM_NAME STREQUAL \"Darwin\")\n    # qt6 need 10.15 or later\n    set(CMAKE_OSX_DEPLOYMENT_TARGET \"10.15\")\n\n    # copy bundle file\n    get_target_property(MACOS_BUNDLE_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)\n    set(MACOS_BUNDLE_PATH ${MACOS_BUNDLE_PATH}/${PROJECT_NAME}.app/Contents)\n\n    set(QSC_DEPLOY_PATH ${MACOS_BUNDLE_PATH})\n\n    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD\n        # config file copy to Contents/MacOS/config\n        COMMAND ${CMAKE_COMMAND} -E copy_if_different \"${CMAKE_CURRENT_SOURCE_DIR}/../config/config.ini\" \"${MACOS_BUNDLE_PATH}/MacOS/config/config.ini\"\n        COMMAND ${CMAKE_COMMAND} -E copy_if_different \"${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh\" \"${MACOS_BUNDLE_PATH}/MacOS\"\n        COMMAND ${CMAKE_COMMAND} -E copy_if_different \"${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk\" \"${MACOS_BUNDLE_PATH}/MacOS\"\n    )\n\n    # Step 2. ues MACOSX_PACKAGE_LOCATION copy icns to Resources\n    set_source_files_properties(\n        ${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.icns\n        PROPERTIES MACOSX_PACKAGE_LOCATION Resources\n    )\n\n    # use MACOSX_BUNDLE_INFO_PLIST custom plist, not use MACOSX_BUNDLE_BUNDLE_NAME etc..\n    set(INFO_PLIST_TEMPLATE_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/res/Info_Mac.plist.in\")\n    set(INFO_PLIST_FILE \"${CMAKE_CURRENT_SOURCE_DIR}/res/Info_Mac.plist\")\n    file(READ \"${INFO_PLIST_TEMPLATE_FILE}\" plist_contents)\n    string(REPLACE \"\\${BUNDLE_VERSION}\" \"${PROJECT_VERSION}\" plist_contents ${plist_contents})\n    file(WRITE ${INFO_PLIST_FILE} ${plist_contents})\n    set_target_properties(${PROJECT_NAME} PROPERTIES\n        MACOSX_BUNDLE_INFO_PLIST \"${INFO_PLIST_FILE}\"\n        # \"\" disable code sign\n        XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY \"\"\n    )\n\n    # mac framework\n    target_link_libraries(${PROJECT_NAME} PRIVATE \"-framework AppKit\")\nendif()\n\n# Linux\nif(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n    get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)\n    set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})\n\n    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD\n        COMMAND ${CMAKE_COMMAND} -E copy_if_different \"${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh\" \"${QSC_BIN_OUTPUT_PATH}\"\n        COMMAND ${CMAKE_COMMAND} -E copy_if_different \"${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk\" \"${QSC_BIN_OUTPUT_PATH}\"\n    )\n\n    set(THREADS_PREFER_PTHREAD_FLAG ON)\n    find_package(Threads REQUIRED)\n\n    target_link_libraries(${PROJECT_NAME} PRIVATE\n        # xcb https://doc.qt.io/qt-5/linux-requirements.html\n        xcb\n        # pthread\n        Threads::Threads\n    )\n\n    # linux set app icon: https://blog.csdn.net/MrNoboday/article/details/82870853\nendif()\n\n#\n# common deps\n#\n\nadd_subdirectory(QtScrcpyCore)\n\n# Qt\ntarget_link_libraries(${PROJECT_NAME} PRIVATE\n    ${LINK_LIBS}\n    QtScrcpyCore\n)\n"
  },
  {
    "path": "QtScrcpy/appversion",
    "content": "0.0.0\n"
  },
  {
    "path": "QtScrcpy/audio/audiooutput.cpp",
    "content": "#include <QAudioOutput>\n#include <QCoreApplication>\n#include <QElapsedTimer>\n#include <QHostAddress>\n#include <QTcpSocket>\n#include <QTime>\n\n#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))\n#include <QAudioSink>\n#include <QAudioDevice>\n#include <QMediaDevices>\n#endif\n\n#include \"audiooutput.h\"\n\nAudioOutput::AudioOutput(QObject *parent)\n    : QObject(parent)\n{\n    m_running = false;\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n    m_audioOutput = nullptr;\n#else\n    m_audioSink = nullptr;\n#endif\n    connect(&m_sndcpy, &QProcess::readyReadStandardOutput, this, [this]() {\n        qInfo() << QString(\"AudioOutput::\") << QString(m_sndcpy.readAllStandardOutput());\n    });\n    connect(&m_sndcpy, &QProcess::readyReadStandardError, this, [this]() {\n        qInfo() << QString(\"AudioOutput::\") << QString(m_sndcpy.readAllStandardError());\n    });\n}\n\nAudioOutput::~AudioOutput()\n{\n    if (QProcess::NotRunning != m_sndcpy.state()) {\n        m_sndcpy.kill();\n    }\n    stop();\n}\n\nbool AudioOutput::start(const QString& serial, int port)\n{\n    if (m_running) {\n        stop();\n    }\n\n    QElapsedTimer timeConsumeCount;\n    timeConsumeCount.start();\n    bool ret = runSndcpyProcess(serial, port);\n    qInfo() << \"AudioOutput::run sndcpy cost:\" << timeConsumeCount.elapsed() << \"milliseconds\";\n    if (!ret) {\n        return ret;\n    }\n\n    startAudioOutput();\n    startRecvData(port);\n\n    m_running = true;\n    return true;\n}\n\nvoid AudioOutput::stop()\n{\n    if (!m_running) {\n        return;\n    }\n    m_running = false;\n\n    stopRecvData();\n    stopAudioOutput();\n}\n\nvoid AudioOutput::installonly(const QString &serial, int port)\n{\n    runSndcpyProcess(serial, port, false);\n}\n\nbool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait)\n{\n    if (QProcess::NotRunning != m_sndcpy.state()) {\n        m_sndcpy.kill();\n    }\n\n#ifdef Q_OS_WIN32\n    QStringList params{serial, QString::number(port)};\n    m_sndcpy.start(\"sndcpy.bat\", params);\n#else\n    QStringList params{\"sndcpy.sh\", serial, QString::number(port)};\n    m_sndcpy.setWorkingDirectory(QCoreApplication::applicationDirPath());\n    m_sndcpy.start(\"bash\", params);\n#endif\n\n    if (!wait) {\n        return true;\n    }\n\n    if (!m_sndcpy.waitForStarted()) {\n        qWarning() << \"AudioOutput::start sndcpy process failed\";\n        return false;\n    }\n    if (!m_sndcpy.waitForFinished()) {\n        qWarning() << \"AudioOutput::sndcpy process crashed\";\n        return false;\n    }\n\n    return true;\n}\n\nvoid AudioOutput::startAudioOutput()\n{\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n    if (m_audioOutput) {\n        return;\n    }\n\n    QAudioFormat format;\n    format.setSampleRate(48000);\n    format.setChannelCount(2);\n    format.setSampleSize(16);\n    format.setCodec(\"audio/pcm\");\n    format.setByteOrder(QAudioFormat::LittleEndian);\n    format.setSampleType(QAudioFormat::SignedInt);\n    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());\n\n    if (!info.isFormatSupported(format)) {\n        qWarning() << \"AudioOutput::audio format not supported, cannot play audio.\";\n        return;\n    }\n\n    m_audioOutput = new QAudioOutput(format, this);\n    connect(m_audioOutput, &QAudioOutput::stateChanged, this, [](QAudio::State state) {\n        qInfo() << \"AudioOutput::audio state changed:\" << state;\n    });\n    m_audioOutput->setBufferSize(48000*2*15/1000 * 20);\n    m_outputDevice = m_audioOutput->start();\n#else\n    if (m_audioSink) {\n        return;\n    }\n\n    QAudioFormat format;\n    format.setSampleRate(48000);\n    format.setChannelCount(2);\n    format.setSampleFormat(QAudioFormat::Int16);\n    QAudioDevice defaultDevice = QMediaDevices::defaultAudioOutput();\n    if (!defaultDevice.isFormatSupported(format)) {\n        qWarning() << \"AudioOutput::audio format not supported, cannot play audio.\";\n        return;\n    }\n    m_audioSink = new QAudioSink(defaultDevice, format, this);\n    m_outputDevice = m_audioSink->start();\n    if (!m_outputDevice) {\n        qWarning() << \"AudioOutput::audio output device not available, cannot play audio.\";\n        delete m_audioSink;\n        m_audioSink = nullptr;\n        return;\n    }\n#endif\n}\n\nvoid AudioOutput::stopAudioOutput()\n{\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n    if (m_audioOutput) {\n        m_audioOutput->stop();\n        delete m_audioOutput;\n        m_audioOutput = nullptr;\n    }\n#else\n    if (m_audioSink) {\n        m_audioSink->stop();\n        delete m_audioSink;\n        m_audioSink = nullptr;\n    }\n#endif\n    m_outputDevice = nullptr;\n}\n\nvoid AudioOutput::startRecvData(int port)\n{\n    if (m_workerThread.isRunning()) {\n        stopRecvData();\n    }\n\n    auto audioSocket = new QTcpSocket();\n    audioSocket->moveToThread(&m_workerThread);\n    connect(&m_workerThread, &QThread::finished, audioSocket, &QObject::deleteLater);\n\n    connect(this, &AudioOutput::connectTo, audioSocket, [audioSocket](int port) {\n        audioSocket->connectToHost(QHostAddress::LocalHost, port);\n        if (!audioSocket->waitForConnected(500)) {\n            qWarning(\"AudioOutput::audio socket connect failed\");\n            return;\n        }\n        qInfo(\"AudioOutput::audio socket connect success\");\n    });\n    connect(audioSocket, &QIODevice::readyRead, audioSocket, [this, audioSocket]() {\n        qint64 recv = audioSocket->bytesAvailable();\n        //qDebug() << \"AudioOutput::recv data:\" << recv;\n\n        if (!m_outputDevice) {\n            return;\n        }\n        if (m_buffer.capacity() < recv) {\n            m_buffer.reserve(recv);\n        }\n\n        qint64 count = audioSocket->read(m_buffer.data(), recv);\n        m_outputDevice->write(m_buffer.data(), count);\n    });\n    connect(audioSocket, &QTcpSocket::stateChanged, audioSocket, [](QAbstractSocket::SocketState state) {\n        qInfo() << \"AudioOutput::audio socket state changed:\" << state;\n\n    });\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) {\n        qInfo() << \"AudioOutput::audio socket error occurred:\" << error;\n    });\n#else\n    connect(audioSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), audioSocket, [](QAbstractSocket::SocketError error) {\n        qInfo() << \"AudioOutput::audio socket error occurred:\" << error;\n    });\n#endif\n\n    m_workerThread.start();\n    emit connectTo(port);\n}\n\nvoid AudioOutput::stopRecvData()\n{\n    if (!m_workerThread.isRunning()) {\n        return;\n    }\n\n    m_workerThread.quit();\n    m_workerThread.wait();\n}\n"
  },
  {
    "path": "QtScrcpy/audio/audiooutput.h",
    "content": "#ifndef AUDIOOUTPUT_H\n#define AUDIOOUTPUT_H\n\n#include <QThread>\n#include <QProcess>\n#include <QPointer>\n#include <QVector>\n\nclass QAudioSink;\nclass QAudioOutput;\nclass QIODevice;\nclass AudioOutput : public QObject\n{\n    Q_OBJECT\npublic:\n    explicit AudioOutput(QObject *parent = nullptr);\n    ~AudioOutput();\n\n    bool start(const QString& serial, int port);\n    void stop();\n    void installonly(const QString& serial, int port);\n\nprivate:\n    bool runSndcpyProcess(const QString& serial, int port, bool wait = true);\n    void startAudioOutput();\n    void stopAudioOutput();\n    void startRecvData(int port);\n    void stopRecvData();\n\nsignals:\n    void connectTo(int port);\n\nprivate:\n    QPointer<QIODevice> m_outputDevice;\n    QThread m_workerThread;\n    QProcess m_sndcpy;\n    QVector<char> m_buffer;\n    bool m_running = false;\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n    QAudioOutput* m_audioOutput = nullptr;\n#else\n    QAudioSink *m_audioSink = nullptr;\n#endif\n};\n\n#endif // AUDIOOUTPUT_H\n"
  },
  {
    "path": "QtScrcpy/clang-format-all.sh",
    "content": "#!/bin/bash\n#\n# clang-format-all: a tool to run clang-format on an entire project\n# Copyright (C) 2016 Evan Klitzke <evan@eklitzke.org>\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation, either version 3 of the License, or\n# (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nfunction usage {\n    echo \"Usage: $0 DIR...\"\n    exit 1\n}\n\nif [ $# -eq 0 ]; then\n    usage\nfi\n\n# Variable that will hold the name of the clang-format command\nFMT=\"\"\n\n# Some distros just call it clang-format. Others (e.g. Ubuntu) are insistent\n# that the version number be part of the command. We prefer clang-format if\n# that's present, otherwise we work backwards from highest version to lowest\n# version.\nfor clangfmt in clang-format{,-{4,3}.{9,8,7,6,5,4,3,2,1,0}}; do\n    if which \"$clangfmt\" &>/dev/null; then\n        FMT=\"$clangfmt\"\n        break\n    fi\ndone\n\n# Check if we found a working clang-format\nif [ -z \"$FMT\" ]; then\n    echo \"failed to find clang-format\"\n    exit 1\nfi\n\n# Check all of the arguments first to make sure they're all directories\nfor dir in \"$@\"; do\n    if [ ! -d \"${dir}\" ]; then\n        echo \"${dir} is not a directory\"\n        usage\n    fi\ndone\n\n# Find a dominating file, starting from a given directory and going up.\nfind-dominating-file() {\n    if [ -r \"$1\"/\"$2\" ]; then\n        return 0\n    fi\n    if [ \"$1\" = \"/\" ]; then\n        return 1\n    fi\n    find-dominating-file \"$(realpath \"$1\"/..)\" \"$2\"\n    return $?\n}\n\n# Run clang-format -i on all of the things\nfor dir in \"$@\"; do\n    pushd \"${dir}\" &>/dev/null\n    if ! find-dominating-file . .clang-format; then\n        echo \"Failed to find dominating .clang-format starting at $PWD\"\n        continue\n    fi\n    find . \\\n         \\( -name '*.c' \\\n         -o -name '*.cc' \\\n         -o -name '*.cpp' \\\n         -o -name '*.h' \\\n         -o -name '*.hh' \\\n         -o -name '*.hpp' \\) \\\n         -exec \"${FMT}\" -i '{}' \\;\n    popd &>/dev/null\ndone\n"
  },
  {
    "path": "QtScrcpy/fontawesome/iconhelper.cpp",
    "content": "#include \"iconhelper.h\"\r\n\r\nIconHelper *IconHelper::_instance = 0;\r\nIconHelper::IconHelper(QObject *) : QObject(qApp)\r\n{\r\n    int fontId = QFontDatabase::addApplicationFont(\":/font/fontawesome-webfont.ttf\");\r\n    QString fontName = QFontDatabase::applicationFontFamilies(fontId).at(0);\r\n    iconFont = QFont(fontName);\r\n}\r\n\r\nvoid IconHelper::SetIcon(QLabel *lab, QChar c, int size)\r\n{\r\n    iconFont.setPointSize(size);\r\n    lab->setFont(iconFont);\r\n    lab->setText(c);\r\n}\r\n\r\nvoid IconHelper::SetIcon(QPushButton *btn, QChar c, int size)\r\n{\r\n    iconFont.setPointSize(size);\r\n    btn->setFont(iconFont);\r\n    btn->setText(c);\r\n}\r\n"
  },
  {
    "path": "QtScrcpy/fontawesome/iconhelper.h",
    "content": "#ifndef ICONHELPER_H\r\n#define ICONHELPER_H\r\n\r\n#include <QApplication>\r\n#include <QFont>\r\n#include <QFontDatabase>\r\n#include <QLabel>\r\n#include <QMutex>\r\n#include <QObject>\r\n#include <QPushButton>\r\n\r\nclass IconHelper : public QObject\r\n{\r\nprivate:\r\n    explicit IconHelper(QObject *parent = 0);\r\n    QFont iconFont;\r\n    static IconHelper *_instance;\r\n\r\npublic:\r\n    static IconHelper *Instance()\r\n    {\r\n        static QMutex mutex;\r\n        if (!_instance) {\r\n            QMutexLocker locker(&mutex);\r\n            if (!_instance) {\r\n                _instance = new IconHelper;\r\n            }\r\n        }\r\n        return _instance;\r\n    }\r\n\r\n    void SetIcon(QLabel *lab, QChar c, int size = 10);\r\n    void SetIcon(QPushButton *btn, QChar c, int size = 10);\r\n};\r\n\r\n#endif // ICONHELPER_H\r\n"
  },
  {
    "path": "QtScrcpy/groupcontroller/groupcontroller.cpp",
    "content": "#include <QPointer>\n\n#include \"groupcontroller.h\"\n#include \"videoform.h\"\n\nGroupController::GroupController(QObject *parent) : QObject(parent)\n{\n\n}\n\nbool GroupController::isHost(const QString &serial)\n{\n    auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();\n    if (!data) {\n        return true;\n    }\n\n    return static_cast<VideoForm*>(data)->isHost();\n}\n\nQSize GroupController::getFrameSize(const QString &serial)\n{\n    auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();\n    if (!data) {\n        return QSize();\n    }\n\n    return static_cast<VideoForm*>(data)->frameSize();\n}\n\nGroupController &GroupController::instance()\n{\n    static GroupController gc;\n    return gc;\n}\n\nvoid GroupController::updateDeviceState(const QString &serial)\n{\n    if (!m_devices.contains(serial)) {\n        return;\n    }\n\n    auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n    if (!device) {\n        return;\n    }\n\n    if (isHost(serial)) {\n        device->registerDeviceObserver(this);\n    } else {\n        device->deRegisterDeviceObserver(this);\n    }\n}\n\nvoid GroupController::addDevice(const QString &serial)\n{\n    if (m_devices.contains(serial)) {\n        return;\n    }\n\n    m_devices.append(serial);\n}\n\nvoid GroupController::removeDevice(const QString &serial)\n{\n    if (!m_devices.contains(serial)) {\n        return;\n    }\n\n    m_devices.removeOne(serial);\n\n    auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n    if (!device) {\n        return;\n    }\n\n    if (isHost(serial)) {\n        device->deRegisterDeviceObserver(this);\n    }\n}\n\nvoid GroupController::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)\n{\n    Q_UNUSED(frameSize);\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->mouseEvent(from, getFrameSize(serial), showSize);\n    }\n}\n\nvoid GroupController::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)\n{\n    Q_UNUSED(frameSize);\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->wheelEvent(from, getFrameSize(serial), showSize);\n    }\n}\n\nvoid GroupController::keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)\n{\n    Q_UNUSED(frameSize);\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->keyEvent(from, getFrameSize(serial), showSize);\n    }\n}\n\nvoid GroupController::postGoBack()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postGoBack();\n    }\n}\n\nvoid GroupController::postGoHome()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postGoHome();\n    }\n}\n\nvoid GroupController::postGoMenu()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postGoMenu();\n    }\n}\n\nvoid GroupController::postAppSwitch()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postAppSwitch();\n    }\n}\n\nvoid GroupController::postPower()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postPower();\n    }\n}\n\nvoid GroupController::postVolumeUp()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postVolumeUp();\n    }\n}\n\nvoid GroupController::postVolumeDown()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postVolumeDown();\n    }\n}\n\nvoid GroupController::postCopy()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postCopy();\n    }\n}\n\nvoid GroupController::postCut()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postCut();\n    }\n}\n\nvoid GroupController::setDisplayPower(bool on)\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->setDisplayPower(on);\n    }\n}\n\nvoid GroupController::expandNotificationPanel()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->expandNotificationPanel();\n    }\n}\n\nvoid GroupController::collapsePanel()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->collapsePanel();\n    }\n}\n\nvoid GroupController::postBackOrScreenOn(bool down)\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postBackOrScreenOn(down);\n    }\n}\n\nvoid GroupController::postTextInput(QString &text)\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->postTextInput(text);\n    }\n}\n\nvoid GroupController::requestDeviceClipboard()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->requestDeviceClipboard();\n    }\n}\n\nvoid GroupController::setDeviceClipboard(bool pause)\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->setDeviceClipboard(pause);\n    }\n}\n\nvoid GroupController::clipboardPaste()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->clipboardPaste();\n    }\n}\n\nvoid GroupController::pushFileRequest(const QString &file, const QString &devicePath)\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->pushFileRequest(file, devicePath);\n    }\n}\n\nvoid GroupController::installApkRequest(const QString &apkFile)\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->installApkRequest(apkFile);\n    }\n}\n\nvoid GroupController::screenshot()\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->screenshot();\n    }\n}\n\nvoid GroupController::showTouch(bool show)\n{\n    for (const auto& serial : m_devices) {\n        if (true == isHost(serial)) {\n            continue;\n        }\n        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n        if (!device) {\n            continue;\n        }\n\n        device->showTouch(show);\n    }\n}\n"
  },
  {
    "path": "QtScrcpy/groupcontroller/groupcontroller.h",
    "content": "#ifndef GROUPCONTROLLER_H\n#define GROUPCONTROLLER_H\n\n#include <QObject>\n#include <QVector>\n\n#include \"QtScrcpyCore.h\"\n\nclass GroupController : public QObject, public qsc::DeviceObserver\n{\n    Q_OBJECT\npublic:\n    static GroupController& instance();\n\n    void updateDeviceState(const QString& serial);\n    void addDevice(const QString& serial);\n    void removeDevice(const QString& serial);\n\nprivate:\n    // DeviceObserver\n    void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) override;\n    void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) override;\n    void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) override;\n\n    void postGoBack() override;\n    void postGoHome() override;\n    void postGoMenu() override;\n    void postAppSwitch() override;\n    void postPower() override;\n    void postVolumeUp() override;\n    void postVolumeDown() override;\n    void postCopy() override;\n    void postCut() override;\n    void setDisplayPower(bool on) override;\n    void expandNotificationPanel() override;\n    void collapsePanel() override;\n    void postBackOrScreenOn(bool down) override;\n    void postTextInput(QString &text) override;\n    void requestDeviceClipboard() override;\n    void setDeviceClipboard(bool pause = true) override;\n    void clipboardPaste() override;\n    void pushFileRequest(const QString &file, const QString &devicePath = \"\") override;\n    void installApkRequest(const QString &apkFile) override;\n    void screenshot() override;\n    void showTouch(bool show) override;\n\nprivate:\n    explicit GroupController(QObject *parent = nullptr);\n    bool isHost(const QString& serial);\n    QSize getFrameSize(const QString& serial);\n\nprivate:\n    QVector<QString> m_devices;\n};\n\n#endif // GROUPCONTROLLER_H\n"
  },
  {
    "path": "QtScrcpy/main.cpp",
    "content": "﻿#include <QApplication>\n#include <QDebug>\n#include <QFile>\n#ifdef Q_OS_LINUX\n#include <QFileInfo>\n#include <QIcon>\n#endif\n#include <QSurfaceFormat>\n#include <QTcpServer>\n#include <QTcpSocket>\n#include <QTranslator>\n#include <QDateTime>\n\n#include \"config.h\"\n#include \"dialog.h\"\n#include \"mousetap/mousetap.h\"\n\nstatic Dialog *g_mainDlg = Q_NULLPTR;\nstatic QtMessageHandler g_oldMessageHandler = Q_NULLPTR;\nvoid myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);\nvoid installTranslator();\n\nstatic QtMsgType g_msgType = QtInfoMsg;\nQtMsgType covertLogLevel(const QString &logLevel);\n\nint main(int argc, char *argv[])\n{\n    // set env\n#ifdef Q_OS_WIN32\n    qputenv(\"QTSCRCPY_ADB_PATH\", \"../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/win/adb.exe\");\n    qputenv(\"QTSCRCPY_SERVER_PATH\", \"../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server\");\n    qputenv(\"QTSCRCPY_KEYMAP_PATH\", \"../../../keymap\");\n    qputenv(\"QTSCRCPY_CONFIG_PATH\", \"../../../config\");\n#endif\n\n#ifdef Q_OS_OSX\n    qputenv(\"QTSCRCPY_ADB_PATH\", \"../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/mac/adb\");\n    qputenv(\"QTSCRCPY_SERVER_PATH\", \"../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server\");\n    qputenv(\"QTSCRCPY_KEYMAP_PATH\", \"../../../../../../keymap\");\n    qputenv(\"QTSCRCPY_CONFIG_PATH\", \"../../../../../../config\");\n#endif\n\n#ifdef Q_OS_LINUX\n    // Only set environment variables if they are not already set (e.g., by AppImage AppRun)\n    if (qgetenv(\"QTSCRCPY_ADB_PATH\").isEmpty()) {\n        qputenv(\"QTSCRCPY_ADB_PATH\", \"../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb\");\n    }\n    if (qgetenv(\"QTSCRCPY_SERVER_PATH\").isEmpty()) {\n        qputenv(\"QTSCRCPY_SERVER_PATH\", \"../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server\");\n    }\n    if (qgetenv(\"QTSCRCPY_KEYMAP_PATH\").isEmpty()) {\n        qputenv(\"QTSCRCPY_KEYMAP_PATH\", \"../../../keymap\");\n    }\n    if (qgetenv(\"QTSCRCPY_CONFIG_PATH\").isEmpty()) {\n        qputenv(\"QTSCRCPY_CONFIG_PATH\", \"../../../config\");\n    }\n#endif\n\n    g_msgType = covertLogLevel(Config::getInstance().getLogLevel());\n\n    // set on QApplication before\n    // bug: config path is error on mac\n    int opengl = Config::getInstance().getDesktopOpenGL();\n    if (0 == opengl) {\n        QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);\n    } else if (1 == opengl) {\n        QApplication::setAttribute(Qt::AA_UseOpenGLES);\n    } else if (2 == opengl) {\n        QApplication::setAttribute(Qt::AA_UseDesktopOpenGL);\n    }\n\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);\n\n#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))\n    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);\n#endif\n#endif\n\n    QSurfaceFormat varFormat = QSurfaceFormat::defaultFormat();\n    varFormat.setVersion(2, 0);\n    varFormat.setProfile(QSurfaceFormat::NoProfile);\n    /*\n    varFormat.setSamples(4);\n    varFormat.setAlphaBufferSize(8);\n    varFormat.setBlueBufferSize(8);\n    varFormat.setRedBufferSize(8);\n    varFormat.setGreenBufferSize(8);\n    varFormat.setDepthBufferSize(24);\n    */\n    QSurfaceFormat::setDefaultFormat(varFormat);\n\n    g_oldMessageHandler = qInstallMessageHandler(myMessageOutput);\n    QApplication a(argc, argv);\n\n    // Set application icon for Linux (taskbar icon)\n#ifdef Q_OS_LINUX\n    // Load icon from Qt resource (logo.png is included in res.qrc)\n    QIcon appIcon(\":/image/tray/logo.png\");\n    if (!appIcon.isNull()) {\n        a.setWindowIcon(appIcon);\n    }\n#endif\n\n    // windows下通过qmake VERSION变量或者rc设置版本号和应用名称后，这里可以直接拿到\n    // mac下拿到的是CFBundleVersion的值\n    qDebug() << a.applicationVersion();\n    qDebug() << a.applicationName();\n\n    //update version\n    QStringList versionList = QCoreApplication::applicationVersion().split(\".\");\n    if (versionList.size() >= 3) {\n        QString version = versionList[0] + \".\" + versionList[1] + \".\" + versionList[2];\n        a.setApplicationVersion(version);\n    }\n\n    installTranslator();\n#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)\n    MouseTap::getInstance()->initMouseEventTap();\n#endif\n\n    // load style sheet\n    QFile file(\":/qss/psblack.css\");\n    if (file.open(QFile::ReadOnly)) {\n        QString qss = QLatin1String(file.readAll());\n        QString paletteColor = qss.mid(20, 7);\n        qApp->setPalette(QPalette(QColor(paletteColor)));\n        qApp->setStyleSheet(qss);\n        file.close();\n    }\n\n    qsc::AdbProcess::setAdbPath(Config::getInstance().getAdbPath());\n\n    g_mainDlg = new Dialog {};\n    g_mainDlg->show();\n\n    qInfo() << QObject::tr(\"This software is completely open source and free. Use it at your own risk. You can download it at the \"\n            \"following address:\");\n    qInfo() << QString(\"QtScrcpy %1 <https://github.com/barry-ran/QtScrcpy>\").arg(QCoreApplication::applicationVersion());\n\n    qInfo() << QObject::tr(\"If you need more professional batch control mirror software, you can try the following software:\");\n    qInfo() << QString(QObject::tr(\"QuickMirror\") + \" <https://lrbnfell4p.feishu.cn/drive/folder/KviYfz5uFlpUT8dXgdjccmfUnse>\");\n\n    qInfo() << QObject::tr(\"If you need more professional game keymap mirror software, you can try the following software:\");\n    qInfo() << QString(QObject::tr(\"QuickAssistant\") + \" <https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh>\");\n\n    qInfo() << QObject::tr(\"If you need more professional PC remote software, you can try the following software:\");\n    qInfo() << QString(QObject::tr(\"QuickDesk\") + \" <https://github.com/barry-ran/QuickDesk>\");\n\n    qInfo() << QObject::tr(\"You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1>\");\n\n    int ret = a.exec();\n    delete g_mainDlg;\n\n#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)\n    MouseTap::getInstance()->quitMouseEventTap();\n#endif\n    return ret;\n}\n\nvoid installTranslator()\n{\n    static QTranslator translator;\n    QLocale locale;\n    QLocale::Language language = locale.language();\n\n    if (Config::getInstance().getLanguage() == \"zh_CN\") {\n        language = QLocale::Chinese;\n    } else if (Config::getInstance().getLanguage() == \"en_US\") {\n        language = QLocale::English;\n    } else if (Config::getInstance().getLanguage() == \"ja_JP\") {\n        language = QLocale::Japanese;\n    }\n\n    QString languagePath = \":/i18n/\";\n    switch (language) {\n    case QLocale::Chinese:\n        languagePath += \"zh_CN.qm\";\n        break;\n    case QLocale::Japanese:\n        languagePath += \"ja_JP.qm\";\n        break;\n    case QLocale::English:\n    default:\n        languagePath += \"en_US.qm\";\n        break;\n    }\n\n    auto loaded = translator.load(languagePath);\n    if (!loaded) {\n        qWarning() << \"Failed to load translation file:\" << languagePath;\n    }\n    qApp->installTranslator(&translator);\n}\n\nQtMsgType covertLogLevel(const QString &logLevel)\n{\n    if (\"debug\" == logLevel) {\n        return QtDebugMsg;\n    }\n\n    if (\"info\" == logLevel) {\n        return QtInfoMsg;\n    }\n\n    if (\"warn\" == logLevel) {\n        return QtWarningMsg;\n    }\n\n    if (\"error\" == logLevel) {\n        return QtCriticalMsg;\n    }\n\n#ifdef QT_NO_DEBUG\n    return QtInfoMsg;\n#else\n    return QtDebugMsg;\n#endif\n}\n\nvoid myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)\n{\n    QString outputMsg;\n    \n#ifdef ENABLE_DETAILED_LOGS\n    QString timestamp = QDateTime::currentDateTime().toString(\"yyyy-MM-dd hh:mm:ss.zzz\");\n    \n    if (context.file && context.line > 0) {\n        QString fileName = QString::fromUtf8(context.file);\n\n        int lastSlash = fileName.lastIndexOf('/');\n        if (lastSlash >= 0) {\n            fileName = fileName.mid(lastSlash + 1);\n        }\n        lastSlash = fileName.lastIndexOf('\\\\');\n        if (lastSlash >= 0) {\n            fileName = fileName.mid(lastSlash + 1);\n        }\n        \n        outputMsg = QString(\"[ %1 %2: %3 ] %4\").arg(timestamp).arg(fileName).arg(context.line).arg(msg);\n    } else {\n        outputMsg = QString(\"[%1] %2\").arg(timestamp).arg(msg);\n    }\n\n    switch (type) {\n    case QtDebugMsg:\n        outputMsg.prepend(\"[debug] \");\n        break;\n    case QtInfoMsg:\n        outputMsg.prepend(\"[info] \");\n        break;\n    case QtWarningMsg:\n        outputMsg.prepend(\"[warring] \");\n        break;\n    case QtCriticalMsg:\n        outputMsg.prepend(\"[critical] \");\n        break;\n    case QtFatalMsg:\n        outputMsg.prepend(\"[fatal] \");\n        break;\n    }\n\n    fprintf(stderr, \"%s\\n\", outputMsg.toUtf8().constData());\n#else\n    outputMsg = msg;\n    if (g_oldMessageHandler) {\n        g_oldMessageHandler(type, context, outputMsg);\n    }\n#endif\n\n    // Is Qt log level higher than warning?\n    float fLogLevel = g_msgType;\n    if (QtInfoMsg == g_msgType) {\n        fLogLevel = QtDebugMsg + 0.5f;\n    }\n    float fLogLevel2 = type;\n    if (QtInfoMsg == type) {\n        fLogLevel2 = QtDebugMsg + 0.5f;\n    }\n\n    if (fLogLevel <= fLogLevel2) {\n        if (g_mainDlg && g_mainDlg->isVisible() && !g_mainDlg->filterLog(outputMsg)) {\n            g_mainDlg->outLog(outputMsg);\n        }\n    }\n\n    if (QtFatalMsg == type) {\n        //abort();\n    }\n}\n"
  },
  {
    "path": "QtScrcpy/render/qyuvopenglwidget.cpp",
    "content": "#include <QCoreApplication>\n#include <QOpenGLTexture>\n#include <QSurfaceFormat>\n\n#include \"qyuvopenglwidget.h\"\n\n// 存储顶点坐标和纹理坐标\n// 存在一起缓存在vbo\n// 使用glVertexAttribPointer指定访问方式即可\nstatic const GLfloat coordinate[] = {\n    // 顶点坐标，存储4个xyz坐标\n    // 坐标范围为[-1,1],中心点为 0,0\n    // 二维图像z始终为0\n    // GL_TRIANGLE_STRIP的绘制方式：\n    // 使用前3个坐标绘制一个三角形，使用后三个坐标绘制一个三角形，正好为一个矩形\n    // x     y     z\n    -1.0f,\n    -1.0f,\n    0.0f,\n    1.0f,\n    -1.0f,\n    0.0f,\n    -1.0f,\n    1.0f,\n    0.0f,\n    1.0f,\n    1.0f,\n    0.0f,\n\n    // 纹理坐标，存储4个xy坐标\n    // 坐标范围为[0,1],左下角为 0,0\n    0.0f,\n    1.0f,\n    1.0f,\n    1.0f,\n    0.0f,\n    0.0f,\n    1.0f,\n    0.0f\n};\n\n// 顶点着色器\nstatic const QString s_vertShader = R\"(\n    attribute vec3 vertexIn;    // xyz顶点坐标\n    attribute vec2 textureIn;   // xy纹理坐标\n    varying vec2 textureOut;    // 传递给片段着色器的纹理坐标\n    void main(void)\n    {\n        gl_Position = vec4(vertexIn, 1.0);  // 1.0表示vertexIn是一个顶点位置\n        textureOut = textureIn; // 纹理坐标直接传递给片段着色器\n    }\n)\";\n\n// 片段着色器\nstatic QString s_fragShader = R\"(\n    varying vec2 textureOut;        // 由顶点着色器传递过来的纹理坐标\n    uniform sampler2D textureY;     // uniform 纹理单元，利用纹理单元可以使用多个纹理\n    uniform sampler2D textureU;     // sampler2D是2D采样器\n    uniform sampler2D textureV;     // 声明yuv三个纹理单元\n    void main(void)\n    {\n        vec3 yuv;\n        vec3 rgb;\n\n        // SDL2 BT709_SHADER_CONSTANTS\n        // https://github.com/spurious/SDL-mirror/blob/4ddd4c445aa059bb127e101b74a8c5b59257fbe2/src/render/opengl/SDL_shaders_gl.c#L102\n        const vec3 Rcoeff = vec3(1.1644,  0.000,  1.7927);\n        const vec3 Gcoeff = vec3(1.1644, -0.2132, -0.5329);\n        const vec3 Bcoeff = vec3(1.1644,  2.1124,  0.000);\n\n        // 根据指定的纹理textureY和坐标textureOut来采样\n        yuv.x = texture2D(textureY, textureOut).r;\n        yuv.y = texture2D(textureU, textureOut).r - 0.5;\n        yuv.z = texture2D(textureV, textureOut).r - 0.5;\n\n        // 采样完转为rgb\n        // 减少一些亮度\n        yuv.x = yuv.x - 0.0625;\n        rgb.r = dot(yuv, Rcoeff);\n        rgb.g = dot(yuv, Gcoeff);\n        rgb.b = dot(yuv, Bcoeff);\n        // 输出颜色值\n        gl_FragColor = vec4(rgb, 1.0);\n    }\n)\";\n\nQYUVOpenGLWidget::QYUVOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)\n{\n    /*\n    QSurfaceFormat format = QSurfaceFormat::defaultFormat();\n    format.setColorSpace(QSurfaceFormat::sRGBColorSpace);\n    format.setProfile(QSurfaceFormat::CompatibilityProfile);\n    format.setMajorVersion(3);\n    format.setMinorVersion(2);\n    QSurfaceFormat::setDefaultFormat(format);\n    */\n}\n\nQYUVOpenGLWidget::~QYUVOpenGLWidget()\n{\n    makeCurrent();\n    m_vbo.destroy();\n    deInitTextures();\n    doneCurrent();\n}\n\nQSize QYUVOpenGLWidget::minimumSizeHint() const\n{\n    return QSize(50, 50);\n}\n\nQSize QYUVOpenGLWidget::sizeHint() const\n{\n    return size();\n}\n\nvoid QYUVOpenGLWidget::setFrameSize(const QSize &frameSize)\n{\n    if (m_frameSize != frameSize) {\n        m_frameSize = frameSize;\n        m_needUpdate = true;\n        // inittexture immediately\n        repaint();\n    }\n}\n\nconst QSize &QYUVOpenGLWidget::frameSize()\n{\n    return m_frameSize;\n}\n\nvoid QYUVOpenGLWidget::updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV)\n{\n    if (m_textureInited) {\n        updateTexture(m_texture[0], 0, dataY, linesizeY);\n        updateTexture(m_texture[1], 1, dataU, linesizeU);\n        updateTexture(m_texture[2], 2, dataV, linesizeV);\n        update();\n    }\n}\n\nvoid QYUVOpenGLWidget::initializeGL()\n{\n    initializeOpenGLFunctions();\n    glDisable(GL_DEPTH_TEST);\n\n    // 顶点缓冲对象初始化\n    m_vbo.create();\n    m_vbo.bind();\n    m_vbo.allocate(coordinate, sizeof(coordinate));\n    initShader();\n    // 设置背景清理色为黑色\n    glClearColor(0.0, 0.0, 0.0, 0.0);\n    // 清理颜色背景\n    glClear(GL_COLOR_BUFFER_BIT);\n}\n\nvoid QYUVOpenGLWidget::paintGL()\n{\n    m_shaderProgram.bind();\n\n    if (m_needUpdate) {\n        deInitTextures();\n        initTextures();\n        m_needUpdate = false;\n    }\n\n    if (m_textureInited) {\n        glActiveTexture(GL_TEXTURE0);\n        glBindTexture(GL_TEXTURE_2D, m_texture[0]);\n\n        glActiveTexture(GL_TEXTURE1);\n        glBindTexture(GL_TEXTURE_2D, m_texture[1]);\n\n        glActiveTexture(GL_TEXTURE2);\n        glBindTexture(GL_TEXTURE_2D, m_texture[2]);\n\n        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);\n    }\n\n    m_shaderProgram.release();\n}\n\nvoid QYUVOpenGLWidget::resizeGL(int width, int height)\n{\n    glViewport(0, 0, width, height);\n    repaint();\n}\n\nvoid QYUVOpenGLWidget::initShader()\n{\n    // opengles的float、int等要手动指定精度\n    if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES)) {\n        s_fragShader.prepend(R\"(\n                             precision mediump int;\n                             precision mediump float;\n                             )\");\n    }\n    m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, s_vertShader);\n    m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, s_fragShader);\n    m_shaderProgram.link();\n    m_shaderProgram.bind();\n\n    // 指定顶点坐标在vbo中的访问方式\n    // 参数解释：顶点坐标在shader中的参数名称，顶点坐标为float，起始偏移为0，顶点坐标类型为vec3，步幅为3个float\n    m_shaderProgram.setAttributeBuffer(\"vertexIn\", GL_FLOAT, 0, 3, 3 * sizeof(float));\n    // 启用顶点属性\n    m_shaderProgram.enableAttributeArray(\"vertexIn\");\n\n    // 指定纹理坐标在vbo中的访问方式\n    // 参数解释：纹理坐标在shader中的参数名称，纹理坐标为float，起始偏移为12个float（跳过前面存储的12个顶点坐标），纹理坐标类型为vec2，步幅为2个float\n    m_shaderProgram.setAttributeBuffer(\"textureIn\", GL_FLOAT, 12 * sizeof(float), 2, 2 * sizeof(float));\n    m_shaderProgram.enableAttributeArray(\"textureIn\");\n\n    // 关联片段着色器中的纹理单元和opengl中的纹理单元（opengl一般提供16个纹理单元）\n    m_shaderProgram.setUniformValue(\"textureY\", 0);\n    m_shaderProgram.setUniformValue(\"textureU\", 1);\n    m_shaderProgram.setUniformValue(\"textureV\", 2);\n}\n\nvoid QYUVOpenGLWidget::initTextures()\n{\n    // 创建纹理\n    glGenTextures(1, &m_texture[0]);\n    glBindTexture(GL_TEXTURE_2D, m_texture[0]);\n    // 设置纹理缩放时的策略\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    // 设置st方向上纹理超出坐标时的显示策略\n    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width(), m_frameSize.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);\n\n    glGenTextures(1, &m_texture[1]);\n    glBindTexture(GL_TEXTURE_2D, m_texture[1]);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width() / 2, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);\n\n    glGenTextures(1, &m_texture[2]);\n    glBindTexture(GL_TEXTURE_2D, m_texture[2]);\n    // 设置纹理缩放时的策略\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    // 设置st方向上纹理超出坐标时的显示策略\n    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width() / 2, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);\n\n    m_textureInited = true;\n}\n\nvoid QYUVOpenGLWidget::deInitTextures()\n{\n    if (QOpenGLFunctions::isInitialized(QOpenGLFunctions::d_ptr)) {\n        glDeleteTextures(3, m_texture);\n    }\n\n    memset(m_texture, 0, sizeof(m_texture));\n    m_textureInited = false;\n}\n\nvoid QYUVOpenGLWidget::updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride)\n{\n    if (!pixels)\n        return;\n\n    QSize size = 0 == textureType ? m_frameSize : m_frameSize / 2;\n\n    makeCurrent();\n    glBindTexture(GL_TEXTURE_2D, texture);\n    glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));\n    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.width(), size.height(), GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels);\n    doneCurrent();\n}\n"
  },
  {
    "path": "QtScrcpy/render/qyuvopenglwidget.h",
    "content": "#ifndef QYUVOPENGLWIDGET_H\n#define QYUVOPENGLWIDGET_H\n#include <QOpenGLBuffer>\n#include <QOpenGLFunctions>\n#include <QOpenGLShaderProgram>\n#include <QOpenGLWidget>\n\nclass QYUVOpenGLWidget\n    : public QOpenGLWidget\n    , protected QOpenGLFunctions\n{\n    Q_OBJECT\npublic:\n    explicit QYUVOpenGLWidget(QWidget *parent = nullptr);\n    virtual ~QYUVOpenGLWidget() override;\n\n    QSize minimumSizeHint() const override;\n    QSize sizeHint() const override;\n\n    void setFrameSize(const QSize &frameSize);\n    const QSize &frameSize();\n    void updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV);\n\nprotected:\n    void initializeGL() override;\n    void paintGL() override;\n    void resizeGL(int width, int height) override;\n\nprivate:\n    void initShader();\n    void initTextures();\n    void deInitTextures();\n    void updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride);\n\nprivate:\n    // 视频帧尺寸\n    QSize m_frameSize = { -1, -1 };\n    bool m_needUpdate = false;\n    bool m_textureInited = false;\n\n    // 顶点缓冲对象(Vertex Buffer Objects, VBO)：默认即为VertexBuffer(GL_ARRAY_BUFFER)类型\n    QOpenGLBuffer m_vbo;\n\n    // 着色器程序：编译链接着色器\n    QOpenGLShaderProgram m_shaderProgram;\n\n    // YUV纹理，用于生成纹理贴图\n    GLuint m_texture[3] = { 0 };\n};\n\n#endif // QYUVOPENGLWIDGET_H\n"
  },
  {
    "path": "QtScrcpy/res/Info_Mac.plist.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>zh-Hans</string>\n\t<key>CFBundleExecutable</key>\n\t<string>QtScrcpy</string>\n\t<key>CFBundleGetInfoString</key>\n\t<string>Created by rankun</string>\n\t<key>CFBundleIconFile</key>\n\t<string>QtScrcpy</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>rankun.QtScrcpy</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>QtScrcpy</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>${BUNDLE_VERSION}</string>\n\t<key>CFBundleSupportedPlatforms</key>\n\t<array>\n\t\t<string>MacOSX</string>\n\t</array>\n\t<key>CFBundleVersion</key>\n\t<string>${BUNDLE_VERSION}</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>10.10</string>\n\t<key>NSAppleEventsUsageDescription</key>\n\t<string></string>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>Copyright © 2018-2038 rankun. All rights reserved.</string>\n\t<key>NSMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n\t<key>NSSupportsAutomaticGraphicsSwitching</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "QtScrcpy/res/QtScrcpy.rc",
    "content": "#include \"winres.h\"\r\n\r\nIDI_ICON1       ICON      \"QtScrcpy.ico\"\r\n// GB2312编码的话，在中文系统上打包FileDescription可以显示中文\r\n// 在github action（英文系统）打包后FileDescription是乱码，utf8编码也不行。。\r\nVS_VERSION_INFO VERSIONINFO\r\n FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH\r\n PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH\r\n FILEFLAGSMASK 0x3fL\r\n#ifdef _DEBUG\r\n FILEFLAGS 0x1L\r\n#else\r\n FILEFLAGS 0x0L\r\n#endif\r\n FILEOS 0x40004L\r\n FILETYPE 0x1L\r\n FILESUBTYPE 0x0L\r\nBEGIN\r\n    BLOCK \"StringFileInfo\"\r\n    BEGIN\r\n        BLOCK \"080404b0\"\r\n        BEGIN\r\n            VALUE \"CompanyName\", \"RanKun\"\r\n            VALUE \"FileDescription\", \"Android real-time display control software\"\r\n            VALUE \"FileVersion\", VERSION_RC_STR\r\n            VALUE \"LegalCopyright\", \"Copyright (C) RanKun 2018-2038. All rights reserved.\"\r\n            VALUE \"ProductName\", \"QtScrcpy\"\r\n            VALUE \"ProductVersion\", VERSION_RC_STR\r\n        END\r\n    END\r\n    BLOCK \"VarFileInfo\"\r\n    BEGIN\r\n        VALUE \"Translation\", 0x804, 1200\r\n    END\r\nEND\r\n"
  },
  {
    "path": "QtScrcpy/res/i18n/CMakeLists.txt",
    "content": "﻿# 声明ts文件\nset(QC_TS_FILES \n    ${CMAKE_CURRENT_SOURCE_DIR}/zh_CN.ts \n    ${CMAKE_CURRENT_SOURCE_DIR}/en_US.ts\n    ${CMAKE_CURRENT_SOURCE_DIR}/ja_JP.ts\n)\n# 设置qm文件生成目录\nset_source_files_properties(${QC_TS_FILES} PROPERTIES OUTPUT_LOCATION \"${CMAKE_CURRENT_SOURCE_DIR}\")\n# 引入LinguistTools\nfind_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools REQUIRED)\nfind_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools REQUIRED)\n\n# qt5_create_translation会依次执行 lupdate更新ts、lrelease更新qm\nqt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../.. ${QC_TS_FILES})\n# 自定义目标依赖QM_FILES，否则不会生成qm文件\nadd_custom_target(QC_QM_GENERATOR DEPENDS ${QM_FILES})\n\n# qt5_create_translation的bug：cmake clean的时候会删除翻译好的ts文件，导致翻译丢失\n# （qt官方说qt6没问题，只用qt6的可以考虑qt5_create_translation）\n# 网上查到的CLEAN_NO_CUSTOM办法只能在makefile生成器下生效，解决不了问题\n# https://cmake.org/cmake/help/latest/prop_dir/CLEAN_NO_CUSTOM.html\n# set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM true)\n# 目前唯一的解决办法是每次clean后，都手动在git中恢复一下ts文件\n\n#[[\n总结:\ncmake qt项目下，利用cmake脚本有三种方式处理翻译：\n1. 完全使用qt自带的cmake LinguistTools脚本：qt5_create_translation&qt5_add_translation\n这两个脚本都满足不了需求：\nqt5_add_translation只能根据已有ts文件生成qm文件（lrelease），不能更新ts文件(lupdate)\nqt5_create_translation在cmake clean的时候会删除翻译好的ts文件，导致翻译丢失\n\n2. cmake add_custom_command + cmake LinguistTools脚本（其实qt5_create_translation内部使用的也是add_custom_command）\n例如add_custom_command执行lupdate，配合qt5_add_translation更新qm，\n参考：https://github.com/maratnek/QtFirstProgrammCMake/blob/2c93b59e2ba85ff6ee0e727487e14003381687d3/CMakeLists.txt\n\n3. 完全使用cmake命令来执行lupdate和lrelease\n例如add_custom_command/add_custom_target/execute_process都可以实现执行lupdate和lrelease命令\n\n上面3个方案都有一个共同问题：就是翻译文件处理都是和编译绑定在一起的，每次编译都会检测执行，实际的翻译工作是所有\n编程工作都完成以后，统一执行一次lupdate、翻译、lrelease就可以了，不应该和编译绑定在一起\n所以写两个shell脚本lupdate.sh和lrelease.sh来处理比较合适，其实非常简单：\n1. 更新ts：lupdate -no-obsolete ./QtScrcpy -ts ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts\n2. 手动翻译ts\n3. 发布：lrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts\n\n参考文档\n1. qt知道qt5_create_translation的bug，但是不肯解决，只确定了qt6没问题 https://bugreports.qt.io/browse/QTBUG-96549\n2. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-add-translation.html\n3. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-create-translation.html\n4. execute_process 参考：https://blog.csdn.net/u010255072/article/details/120326833\n5. add_custom_target 参考：https://www.cnblogs.com/apocelipes/p/14355460.html\n"
  },
  {
    "path": "QtScrcpy/res/i18n/en_US.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"en_US\">\n<context>\n    <name>Dialog</name>\n    <message>\n        <source>show</source>\n        <translation>show</translation>\n    </message>\n    <message>\n        <source>quit</source>\n        <translation>quit</translation>\n    </message>\n    <message>\n        <source>original</source>\n        <translation>original</translation>\n    </message>\n    <message>\n        <source>no lock</source>\n        <translation>no lock</translation>\n    </message>\n    <message>\n        <source>Notice</source>\n        <translation>Notice</translation>\n    </message>\n    <message>\n        <source>Hidden here!</source>\n        <translation>Hidden here!</translation>\n    </message>\n    <message>\n        <source>select path</source>\n        <translation>select path</translation>\n    </message>\n    <message>\n        <source>Clear History</source>\n        <translation>Clear History</translation>\n    </message>\n</context>\n<context>\n    <name>QObject</name>\n    <message>\n        <source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>\n        <translation>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</translation>\n    </message>\n    <message>\n        <source>QuickMirror</source>\n        <translation>QuickMirror</translation>\n    </message>\n    <message>\n        <source>If you need more professional batch control mirror software, you can try the following software:</source>\n        <translation>If you need more professional batch control mirror software, you can try the following software:</translation>\n    </message>\n    <message>\n        <source>If you need more professional game keymap mirror software, you can try the following software:</source>\n        <translation>If you need more professional game keymap mirror software, you can try the following software:</translation>\n    </message>\n    <message>\n        <source>QuickAssistant</source>\n        <translation>QuickAssistant</translation>\n    </message>\n    <message>\n        <source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>\n        <translation>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</translation>\n    </message>\n</context>\n<context>\n    <name>ToolForm</name>\n    <message>\n        <source>Tool</source>\n        <translation>Tool</translation>\n    </message>\n    <message>\n        <source>full screen</source>\n        <translation>full screen</translation>\n    </message>\n    <message>\n        <source>expand notify</source>\n        <translation>expand notify</translation>\n    </message>\n    <message>\n        <source>touch switch</source>\n        <translation>touch switch</translation>\n    </message>\n    <message>\n        <source>close screen</source>\n        <translation>close screen</translation>\n    </message>\n    <message>\n        <source>power</source>\n        <translation>power</translation>\n    </message>\n    <message>\n        <source>volume up</source>\n        <translation>volume up</translation>\n    </message>\n    <message>\n        <source>volume down</source>\n        <translation>volume down</translation>\n    </message>\n    <message>\n        <source>app switch</source>\n        <translation>app switch</translation>\n    </message>\n    <message>\n        <source>menu</source>\n        <translation>menu</translation>\n    </message>\n    <message>\n        <source>home</source>\n        <translation>home</translation>\n    </message>\n    <message>\n        <source>return</source>\n        <translation>return</translation>\n    </message>\n    <message>\n        <source>screen shot</source>\n        <translation>screen shot</translation>\n    </message>\n    <message>\n        <source>open screen</source>\n        <translation>open screen</translation>\n    </message>\n    <message>\n        <source>group control</source>\n        <translation>group control</translation>\n    </message>\n</context>\n<context>\n    <name>VideoForm</name>\n    <message>\n        <source>file does not exist</source>\n        <translation>file does not exist</translation>\n    </message>\n</context>\n<context>\n    <name>Widget</name>\n    <message>\n        <source>Wireless</source>\n        <translation>Wireless</translation>\n    </message>\n    <message>\n        <source>wireless connect</source>\n        <translation>wireless connect</translation>\n    </message>\n    <message>\n        <source>wireless disconnect</source>\n        <translation>wireless disconnect</translation>\n    </message>\n    <message>\n        <source>Start Config</source>\n        <translation>Start Config</translation>\n    </message>\n    <message>\n        <source>select path</source>\n        <translation>select path</translation>\n    </message>\n    <message>\n        <source>record format：</source>\n        <translation>record format:</translation>\n    </message>\n    <message>\n        <source>record screen</source>\n        <translation>record screen</translation>\n    </message>\n    <message>\n        <source>frameless</source>\n        <translation>frameless</translation>\n    </message>\n    <message>\n        <source>Use Simple Mode</source>\n        <translatorcomment>Use Simple Mode</translatorcomment>\n        <translation>Use Simple Mode</translation>\n    </message>\n    <message>\n        <source>Simple Mode</source>\n        <translatorcomment>Simple Mode</translatorcomment>\n        <translation>Simple Mode</translation>\n    </message>\n    <message>\n        <source>WIFI Connect</source>\n        <translatorcomment>WIFI Connect</translatorcomment>\n        <translation>WIFI Connect</translation>\n    </message>\n    <message>\n        <source>USB Connect</source>\n        <translatorcomment>USB Connect</translatorcomment>\n        <translation>USB Connect</translation>\n    </message>\n    <message>\n        <source>Double click to connect:</source>\n        <translation>Double click to connect:</translation>\n    </message>\n    <message>\n        <source>lock orientation:</source>\n        <translation>lock orientation:</translation>\n    </message>\n    <message>\n        <source>show fps</source>\n        <translation>show fps</translation>\n    </message>\n    <message>\n        <source>stay awake</source>\n        <translation>stay awake</translation>\n    </message>\n    <message>\n        <source>device name:</source>\n        <translatorcomment>device name:</translatorcomment>\n        <translation>device name:</translation>\n    </message>\n    <message>\n        <source>update name</source>\n        <translatorcomment>update name</translatorcomment>\n        <translation>update name</translation>\n    </message>\n    <message>\n        <source>stop all server</source>\n        <translation>stop all server</translation>\n    </message>\n    <message>\n        <source>adb command:</source>\n        <translation>adb command:</translation>\n    </message>\n    <message>\n        <source>terminate</source>\n        <translation>terminate</translation>\n    </message>\n    <message>\n        <source>execute</source>\n        <translation>execute</translation>\n    </message>\n    <message>\n        <source>clear</source>\n        <translation>clear</translation>\n    </message>\n    <message>\n        <source>reverse connection</source>\n        <translation>reverse connection</translation>\n    </message>\n    <message>\n        <source>background record</source>\n        <translation>background record</translation>\n    </message>\n    <message>\n        <source>screen-off</source>\n        <translation>screen-off</translation>\n    </message>\n    <message>\n        <source>apply</source>\n        <translation>apply</translation>\n    </message>\n    <message>\n        <source>max size:</source>\n        <translation>max size:</translation>\n    </message>\n    <message>\n        <source>always on top</source>\n        <translation>always on top</translation>\n    </message>\n    <message>\n        <source>refresh script</source>\n        <translation>refresh script</translation>\n    </message>\n    <message>\n        <source>get device IP</source>\n        <translation>get device IP</translation>\n    </message>\n    <message>\n        <source>USB line</source>\n        <translation>USB line</translation>\n    </message>\n    <message>\n        <source>stop server</source>\n        <translation>stop server</translation>\n    </message>\n    <message>\n        <source>start server</source>\n        <translation>start server</translation>\n    </message>\n    <message>\n        <source>device serial:</source>\n        <translation>device serial:</translation>\n    </message>\n    <message>\n        <source>bit rate:</source>\n        <translation>bit rate:</translation>\n    </message>\n    <message>\n        <source>start adbd</source>\n        <translation>start adbd</translation>\n    </message>\n    <message>\n        <source>refresh devices</source>\n        <translation>refresh devices</translation>\n    </message>\n    <message>\n        <source>install sndcpy</source>\n        <translation>install sndcpy</translation>\n    </message>\n    <message>\n        <source>start audio</source>\n        <translation>start audio</translation>\n    </message>\n    <message>\n        <source>stop audio</source>\n        <translation>stop audio</translation>\n    </message>\n    <message>\n        <source>auto update</source>\n        <translation>auto update</translation>\n    </message>\n    <message>\n        <source>show toolbar</source>\n        <translation>show toolbar</translation>\n    </message>\n    <message>\n        <source>record save path:</source>\n        <translation>record save path:</translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "QtScrcpy/res/i18n/ja_JP.ts",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<!DOCTYPE TS []>\r\n<TS version=\"2.1\" language=\"ja_JP\">\r\n  <context>\r\n    <name>Dialog</name>\r\n    <message>\r\n      <source>show</source>\r\n      <translation>表示</translation>\r\n    </message>\r\n    <message>\r\n      <source>quit</source>\r\n      <translation>終了</translation>\r\n    </message>\r\n    <message>\r\n      <source>original</source>\r\n      <translation>オリジナル</translation>\r\n    </message>\r\n    <message>\r\n      <source>no lock</source>\r\n      <translation>ロックなし</translation>\r\n    </message>\r\n    <message>\r\n      <source>Notice</source>\r\n      <translation>お知らせ</translation>\r\n    </message>\r\n    <message>\r\n      <source>Hidden here!</source>\r\n      <translation>ここに隠れています！</translation>\r\n    </message>\r\n    <message>\r\n      <source>select path</source>\r\n      <translation>パスを選択</translation>\r\n    </message>\r\n    <message>\r\n      <source>Clear History</source>\r\n      <translation>履歴を消去</translation>\r\n    </message>\r\n  </context>\r\n  <context>\r\n    <name>QObject</name>\r\n    <message>\r\n      <source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>\r\n      <translation>このソフトウェアはオープンソースで完全無料です。自己責任でご利用ください。以下のアドレスからダウンロードできます:</translation>\r\n    </message>\r\n    <message>\r\n      <source>QuickMirror</source>\r\n      <translation>クイックミラー</translation>\r\n    </message>\r\n    <message>\r\n      <source>If you need more professional batch control mirror software, you can try the following software:</source>\r\n      <translation>より高度なバッチ制御が可能なミラーソフトウェアが必要な場合は、次のソフトウェアをお試しください:</translation>\r\n    </message>\r\n    <message>\r\n      <source>If you need more professional game keymap mirror software, you can try the following software:</source>\r\n      <translation>より高度なゲームキーマップが可能なミラーソフトウェアが必要な場合は、次のソフトウェアをお試しください:</translation>\r\n    </message>\r\n    <message>\r\n      <source>QuickAssistant</source>\r\n      <translation>クイックアシスタント</translation>\r\n    </message>\r\n    <message>\r\n      <source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>\r\n      <translation>Telegram で連絡ができます &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</translation>\r\n    </message>\r\n  </context>\r\n  <context>\r\n    <name>ToolForm</name>\r\n    <message>\r\n      <source>Tool</source>\r\n      <translation>ツール</translation>\r\n    </message>\r\n    <message>\r\n      <source>full screen</source>\r\n      <translation>フルスクリーン</translation>\r\n    </message>\r\n    <message>\r\n      <source>expand notify</source>\r\n      <translation>通知を展開</translation>\r\n    </message>\r\n    <message>\r\n      <source>touch switch</source>\r\n      <translation>タッチ切り替え</translation>\r\n    </message>\r\n    <message>\r\n      <source>close screen</source>\r\n      <translation>画面を閉じる</translation>\r\n    </message>\r\n    <message>\r\n      <source>power</source>\r\n      <translation>電源</translation>\r\n    </message>\r\n    <message>\r\n      <source>volume up</source>\r\n      <translation>音量を上げる</translation>\r\n    </message>\r\n    <message>\r\n      <source>volume down</source>\r\n      <translation>音量を下げる</translation>\r\n    </message>\r\n    <message>\r\n      <source>app switch</source>\r\n      <translation>アプリを切り替え</translation>\r\n    </message>\r\n    <message>\r\n      <source>menu</source>\r\n      <translation>メニュー</translation>\r\n    </message>\r\n    <message>\r\n      <source>home</source>\r\n      <translation>ホーム</translation>\r\n    </message>\r\n    <message>\r\n      <source>return</source>\r\n      <translation>戻る</translation>\r\n    </message>\r\n    <message>\r\n      <source>screen shot</source>\r\n      <translation>スクリーンショット</translation>\r\n    </message>\r\n    <message>\r\n      <source>open screen</source>\r\n      <translation>画面を開く</translation>\r\n    </message>\r\n    <message>\r\n      <source>group control</source>\r\n      <translation>グループコントロール</translation>\r\n    </message>\r\n  </context>\r\n  <context>\r\n    <name>VideoForm</name>\r\n    <message>\r\n      <source>file does not exist</source>\r\n      <translation>ファイルが存在しません</translation>\r\n    </message>\r\n  </context>\r\n  <context>\r\n    <name>Widget</name>\r\n    <message>\r\n      <source>Wireless</source>\r\n      <translation>ワイヤレス</translation>\r\n    </message>\r\n    <message>\r\n      <source>wireless connect</source>\r\n      <translation>ワイヤレスで接続</translation>\r\n    </message>\r\n    <message>\r\n      <source>wireless disconnect</source>\r\n      <translation>ワイヤレスを切断</translation>\r\n    </message>\r\n    <message>\r\n      <source>Start Config</source>\r\n      <translation>構成を開始</translation>\r\n    </message>\r\n    <message>\r\n      <source>select path</source>\r\n      <translation>パスを選択</translation>\r\n    </message>\r\n    <message>\r\n      <source>record format：</source>\r\n      <translation>録画の形式:</translation>\r\n    </message>\r\n    <message>\r\n      <source>record screen</source>\r\n      <translation>画面を録画</translation>\r\n    </message>\r\n    <message>\r\n      <source>frameless</source>\r\n      <translation>フレームレス</translation>\r\n    </message>\r\n    <message>\r\n      <source>Use Simple Mode</source>\r\n      <translatorcomment>シンプルモードを使用する</translatorcomment>\r\n      <translation>シンプルモードを使用する</translation>\r\n    </message>\r\n    <message>\r\n      <source>Simple Mode</source>\r\n      <translatorcomment>シンプルモード</translatorcomment>\r\n      <translation>シンプルモード</translation>\r\n    </message>\r\n    <message>\r\n      <source>WIFI Connect</source>\r\n      <translatorcomment>Wi-Fi 接続</translatorcomment>\r\n      <translation>Wi-Fi 接続</translation>\r\n    </message>\r\n    <message>\r\n      <source>USB Connect</source>\r\n      <translatorcomment>USB 接続</translatorcomment>\r\n      <translation>USB 接続</translation>\r\n    </message>\r\n    <message>\r\n      <source>Double click to connect:</source>\r\n      <translation>ダブルクリックで接続:</translation>\r\n    </message>\r\n    <message>\r\n      <source>lock orientation:</source>\r\n      <translation>画面方向をロック:</translation>\r\n    </message>\r\n    <message>\r\n      <source>show fps</source>\r\n      <translation>FPS を表示</translation>\r\n    </message>\r\n    <message>\r\n      <source>stay awake</source>\r\n      <translation>画面を常時点灯</translation>\r\n    </message>\r\n    <message>\r\n      <source>device name:</source>\r\n      <translatorcomment>デバイス名:</translatorcomment>\r\n      <translation>デバイス名:</translation>\r\n    </message>\r\n    <message>\r\n      <source>update name</source>\r\n      <translatorcomment>更新名</translatorcomment>\r\n      <translation>更新名</translation>\r\n    </message>\r\n    <message>\r\n      <source>stop all server</source>\r\n      <translation>すべてのサーバーを停止</translation>\r\n    </message>\r\n    <message>\r\n      <source>adb command:</source>\r\n      <translation>adb コマンド:</translation>\r\n    </message>\r\n    <message>\r\n      <source>terminate</source>\r\n      <translation>停止</translation>\r\n    </message>\r\n    <message>\r\n      <source>execute</source>\r\n      <translation>実行</translation>\r\n    </message>\r\n    <message>\r\n      <source>clear</source>\r\n      <translation>消去</translation>\r\n    </message>\r\n    <message>\r\n      <source>reverse connection</source>\r\n      <translation>リバース接続</translation>\r\n    </message>\r\n    <message>\r\n      <source>background record</source>\r\n      <translation>バックグラウンド録画</translation>\r\n    </message>\r\n    <message>\r\n      <source>screen-off</source>\r\n      <translation>画面を OFF</translation>\r\n    </message>\r\n    <message>\r\n      <source>apply</source>\r\n      <translation>適用</translation>\r\n    </message>\r\n    <message>\r\n      <source>max size:</source>\r\n      <translation>最大サイズ:</translation>\r\n    </message>\r\n    <message>\r\n      <source>always on top</source>\r\n      <translation>常に手前に表示</translation>\r\n    </message>\r\n    <message>\r\n      <source>refresh script</source>\r\n      <translation>スクリプトを更新</translation>\r\n    </message>\r\n    <message>\r\n      <source>get device IP</source>\r\n      <translation>デバイス IP を取得</translation>\r\n    </message>\r\n    <message>\r\n      <source>USB line</source>\r\n      <translation>USB ライン</translation>\r\n    </message>\r\n    <message>\r\n      <source>stop server</source>\r\n      <translation>サーバーを停止</translation>\r\n    </message>\r\n    <message>\r\n      <source>start server</source>\r\n      <translation>サーバーを開始</translation>\r\n    </message>\r\n    <message>\r\n      <source>device serial:</source>\r\n      <translation>デバイスシリアル:</translation>\r\n    </message>\r\n    <message>\r\n      <source>bit rate:</source>\r\n      <translation>ビットレート:</translation>\r\n    </message>\r\n    <message>\r\n      <source>start adbd</source>\r\n      <translation>adbd を開始</translation>\r\n    </message>\r\n    <message>\r\n      <source>refresh devices</source>\r\n      <translation>デバイスを更新</translation>\r\n    </message>\r\n    <message>\r\n      <source>install sndcpy</source>\r\n      <translation>Sndcpy をインストール</translation>\r\n    </message>\r\n    <message>\r\n      <source>start audio</source>\r\n      <translation>オーディオを開始</translation>\r\n    </message>\r\n    <message>\r\n      <source>stop audio</source>\r\n      <translation>オーディオを停止</translation>\r\n    </message>\r\n    <message>\r\n      <source>auto update</source>\r\n      <translation>自動更新</translation>\r\n    </message>\r\n    <message>\r\n      <source>show toolbar</source>\r\n      <translation>ツールバーを表示</translation>\r\n    </message>\r\n    <message>\r\n      <source>record save path:</source>\r\n      <translation>録画の保存先:</translation>\r\n    </message>\r\n  </context>\r\n</TS>"
  },
  {
    "path": "QtScrcpy/res/i18n/ko_KR.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"ko\">\n<context>\n    <name>Dialog</name>\n    <message>\n        <source>show</source>\n        <translation>표시</translation>\n    </message>\n    <message>\n        <source>quit</source>\n        <translation>종료</translation>\n    </message>\n    <message>\n        <source>original</source>\n        <translation>원본</translation>\n    </message>\n    <message>\n        <source>no lock</source>\n        <translation>잠금 없음</translation>\n    </message>\n    <message>\n        <source>Notice</source>\n        <translation>알림</translation>\n    </message>\n    <message>\n        <source>Hidden here!</source>\n        <translation>여기에 숨겨져 있어요!</translation>\n    </message>\n    <message>\n        <source>select path</source>\n        <translation>경로 선택</translation>\n    </message>\n    <message>\n        <source>Clear History</source>\n        <translation>기록 지우기</translation>\n    </message>\n</context>\n<context>\n    <name>QObject</name>\n    <message>\n        <source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>\n        <translation>이 소프트웨어는 완전히 오픈 소스이며 무료입니다. 본인의 위험을 감수하고 사용하세요. 다음 주소로 다운로드할 수 있습니다:</translation>\n    </message>\n    <message>\n        <source>QuickMirror</source>\n        <translation>빠른미러</translation>\n    </message>\n    <message>\n        <source>If you need more professional batch control mirror software, you can try the following software:</source>\n        <translation>더 전문적인 일괄 제어 미러 소프트웨어가 필요하다면 다음 소프트웨어를 사용해 볼 수 있습니다:</translation>\n    </message>\n    <message>\n        <source>If you need more professional game keymap mirror software, you can try the following software:</source>\n        <translation>더 전문적인 게임 키맵 미러 소프트웨어가 필요하다면 다음 소프트웨어를 사용해 보세요:</translation>\n    </message>\n    <message>\n        <source>QuickAssistant</source>\n        <translation>빠른 도우미</translation>\n    </message>\n    <message>\n        <source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>\n        <translation>텔레그램으로 연락주세요 &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</translation>\n    </message>\n</context>\n<context>\n    <name>ToolForm</name>\n    <message>\n        <source>Tool</source>\n        <translation>도구</translation>\n    </message>\n    <message>\n        <source>full screen</source>\n        <translation>전체 화면</translation>\n    </message>\n    <message>\n        <source>expand notify</source>\n        <translation>확장 알림</translation>\n    </message>\n    <message>\n        <source>touch switch</source>\n        <translation>터치 스위치</translation>\n    </message>\n    <message>\n        <source>close screen</source>\n        <translation>화면 닫기</translation>\n    </message>\n    <message>\n        <source>power</source>\n        <translation>전ㅇ</translation>\n    </message>\n    <message>\n        <source>volume up</source>\n        <translation>볼륨 높이기</translation>\n    </message>\n    <message>\n        <source>volume down</source>\n        <translation>볼륨 낮추기</translation>\n    </message>\n    <message>\n        <source>app switch</source>\n        <translation>앱 스위치</translation>\n    </message>\n    <message>\n        <source>menu</source>\n        <translation>메뉴</translation>\n    </message>\n    <message>\n        <source>home</source>\n        <translation>home</translation>\n    </message>\n    <message>\n        <source>return</source>\n        <translation>돌ㅇ가기</translation>\n    </message>\n    <message>\n        <source>screen shot</source>\n        <translation>스크린샷</translation>\n    </message>\n    <message>\n        <source>open screen</source>\n        <translation>화면 열기</translation>\n    </message>\n    <message>\n        <source>group control</source>\n        <translation>그룹 제어</translation>\n    </message>\n</context>\n<context>\n    <name>VideoForm</name>\n    <message>\n        <source>file does not exist</source>\n        <translation>파일이 존재하지 않습니다</translation>\n    </message>\n</context>\n<context>\n    <name>Widget</name>\n    <message>\n        <source>Wireless</source>\n        <translation>무선</translation>\n    </message>\n    <message>\n        <source>wireless connect</source>\n        <translation>무선 연결</translation>\n    </message>\n    <message>\n        <source>wireless disconnect</source>\n        <translation>무선 연결 끊기</translation>\n    </message>\n    <message>\n        <source>Start Config</source>\n        <translation>시작 구성</translation>\n    </message>\n    <message>\n        <source>select path</source>\n        <translation>경로 선택</translation>\n    </message>\n    <message>\n        <source>record format：</source>\n        <translation>기록 형식：</translation>\n    </message>\n    <message>\n        <source>record screen</source>\n        <translation>화면 녹화</translation>\n    </message>\n    <message>\n        <source>frameless</source>\n        <translation>프레임 없음</translation>\n    </message>\n    <message>\n        <source>Use Simple Mode</source>\n        <translatorcomment>Use Simple Mode</translatorcomment>\n        <translation>간단한 모드 사용</translation>\n    </message>\n    <message>\n        <source>Simple Mode</source>\n        <translatorcomment>Simple Mode</translatorcomment>\n        <translation>간단한 모드</translation>\n    </message>\n    <message>\n        <source>WIFI Connect</source>\n        <translatorcomment>WIFI Connect</translatorcomment>\n        <translation>WIFI 연결</translation>\n    </message>\n    <message>\n        <source>USB Connect</source>\n        <translatorcomment>USB Connect</translatorcomment>\n        <translation>USB 연결</translation>\n    </message>\n    <message>\n        <source>Double click to connect:</source>\n        <translation>더블 클릭하여 연결:</translation>\n    </message>\n    <message>\n        <source>lock orientation:</source>\n        <translation>잠금 방향:</translation>\n    </message>\n    <message>\n        <source>show fps</source>\n        <translation>fps 표시</translation>\n    </message>\n    <message>\n        <source>stay awake</source>\n        <translation>깨어 있기</translation>\n    </message>\n    <message>\n        <source>device name:</source>\n        <translatorcomment>device name:</translatorcomment>\n        <translation>장치 이름:</translation>\n    </message>\n    <message>\n        <source>update name</source>\n        <translatorcomment>update name</translatorcomment>\n        <translation>업데이트 이름</translation>\n    </message>\n    <message>\n        <source>stop all server</source>\n        <translation>모든 서버 중지</translation>\n    </message>\n    <message>\n        <source>adb command:</source>\n        <translation>adb 명령:</translation>\n    </message>\n    <message>\n        <source>terminate</source>\n        <translation>끝내기</translation>\n    </message>\n    <message>\n        <source>execute</source>\n        <translation>실행</translation>\n    </message>\n    <message>\n        <source>clear</source>\n        <translation>지우기</translation>\n    </message>\n    <message>\n        <source>reverse connection</source>\n        <translation>역연결</translation>\n    </message>\n    <message>\n        <source>background record</source>\n        <translation>배경 기록</translation>\n    </message>\n    <message>\n        <source>screen-off</source>\n        <translation>화면 끄기</translation>\n    </message>\n    <message>\n        <source>apply</source>\n        <translation>적용</translation>\n    </message>\n    <message>\n        <source>max size:</source>\n        <translation>최대 크기:</translation>\n    </message>\n    <message>\n        <source>always on top</source>\n        <translation>항상 위에</translation>\n    </message>\n    <message>\n        <source>refresh script</source>\n        <translation>스크립트 새로 고침</translation>\n    </message>\n    <message>\n        <source>get device IP</source>\n        <translation>장치 IP 가져오기</translation>\n    </message>\n    <message>\n        <source>USB line</source>\n        <translation>USB 선</translation>\n    </message>\n    <message>\n        <source>stop server</source>\n        <translation>서버 중지</translation>\n    </message>\n    <message>\n        <source>start server</source>\n        <translation>서버 시작</translation>\n    </message>\n    <message>\n        <source>device serial:</source>\n        <translation>장치 직렬:</translation>\n    </message>\n    <message>\n        <source>bit rate:</source>\n        <translation>비트 전송률:</translation>\n    </message>\n    <message>\n        <source>start adbd</source>\n        <translation>adbd 시작</translation>\n    </message>\n    <message>\n        <source>refresh devices</source>\n        <translation>장치 새로 고침</translation>\n    </message>\n    <message>\n        <source>install sndcpy</source>\n        <translation>sndcpy 설치</translation>\n    </message>\n    <message>\n        <source>start audio</source>\n        <translation>오디오 시작</translation>\n    </message>\n    <message>\n        <source>stop audio</source>\n        <translation>오디오 중지</translation>\n    </message>\n    <message>\n        <source>auto update</source>\n        <translation>자동 업데이트</translation>\n    </message>\n    <message>\n        <source>show toolbar</source>\n        <translation>도구 모음 표시</translation>\n    </message>\n    <message>\n        <source>record save path:</source>\n        <translation>기록 저장 경로:</translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "QtScrcpy/res/i18n/zh_CN.ts",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_CN\">\n<context>\n    <name>Dialog</name>\n    <message>\n        <source>show</source>\n        <translation>显示</translation>\n    </message>\n    <message>\n        <source>quit</source>\n        <translation>退出</translation>\n    </message>\n    <message>\n        <source>original</source>\n        <translation>原始</translation>\n    </message>\n    <message>\n        <source>no lock</source>\n        <translation>不锁定</translation>\n    </message>\n    <message>\n        <source>Notice</source>\n        <translation>提示</translation>\n    </message>\n    <message>\n        <source>Hidden here!</source>\n        <translation>安卓录屏程序隐藏在这！</translation>\n    </message>\n    <message>\n        <source>select path</source>\n        <translation>选择路径</translation>\n    </message>\n    <message>\n        <source>Clear History</source>\n        <translation>清理历史</translation>\n    </message>\n</context>\n<context>\n    <name>QObject</name>\n    <message>\n        <source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>\n        <translation>本软件完全开源免费，作者不对使用该软件产生的一切后果负责。你可以在以下地址下载：</translation>\n    </message>\n    <message>\n        <source>QuickMirror</source>\n        <translation>极限投屏</translation>\n    </message>\n    <message>\n        <source>If you need more professional batch control mirror software, you can try the following software:</source>\n        <translation>如果你需要更专业的批量控制投屏软件，你可以尝试下面软件：</translation>\n    </message>\n    <message>\n        <source>If you need more professional game keymap mirror software, you can try the following software:</source>\n        <translation>如果你需要更专业的游戏映射投屏软件，你可以尝试下面软件：</translation>\n    </message>\n    <message>\n        <source>QuickAssistant</source>\n        <translation>极限手游助手</translation>\n    </message>\n    <message>\n        <source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>\n        <translation>你可以通过QQ群联系我 &lt;901736468&gt;</translation>\n    </message>\n</context>\n<context>\n    <name>ToolForm</name>\n    <message>\n        <source>Tool</source>\n        <translation>工具</translation>\n    </message>\n    <message>\n        <source>full screen</source>\n        <translation>全屏</translation>\n    </message>\n    <message>\n        <source>expand notify</source>\n        <translation>下拉通知</translation>\n    </message>\n    <message>\n        <source>touch switch</source>\n        <translation>触摸显示开关</translation>\n    </message>\n    <message>\n        <source>close screen</source>\n        <translation>关闭屏幕</translation>\n    </message>\n    <message>\n        <source>power</source>\n        <translation>电源</translation>\n    </message>\n    <message>\n        <source>volume up</source>\n        <translation>音量加</translation>\n    </message>\n    <message>\n        <source>volume down</source>\n        <translation>音量减</translation>\n    </message>\n    <message>\n        <source>app switch</source>\n        <translation>切换应用</translation>\n    </message>\n    <message>\n        <source>menu</source>\n        <translation>菜单</translation>\n    </message>\n    <message>\n        <source>home</source>\n        <translation>主界面</translation>\n    </message>\n    <message>\n        <source>return</source>\n        <translation>返回</translation>\n    </message>\n    <message>\n        <source>screen shot</source>\n        <translation>截图</translation>\n    </message>\n    <message>\n        <source>open screen</source>\n        <translation>打开屏幕</translation>\n    </message>\n    <message>\n        <source>group control</source>\n        <translation>群控</translation>\n    </message>\n</context>\n<context>\n    <name>VideoForm</name>\n    <message>\n        <source>file does not exist</source>\n        <translation>文件不存在</translation>\n    </message>\n</context>\n<context>\n    <name>Widget</name>\n    <message>\n        <source>Wireless</source>\n        <translation>无线</translation>\n    </message>\n    <message>\n        <source>wireless connect</source>\n        <translation>无线连接</translation>\n    </message>\n    <message>\n        <source>wireless disconnect</source>\n        <translation>无线断开</translation>\n    </message>\n    <message>\n        <source>Start Config</source>\n        <translation>启动配置</translation>\n    </message>\n    <message>\n        <source>select path</source>\n        <translation>选择路径</translation>\n    </message>\n    <message>\n        <source>record format：</source>\n        <translation>录制格式：</translation>\n    </message>\n    <message>\n        <source>record screen</source>\n        <translation>录制屏幕</translation>\n    </message>\n    <message>\n        <source>frameless</source>\n        <translation>无边框</translation>\n    </message>\n    <message>\n        <source>Use Simple Mode</source>\n        <translatorcomment>启用精简模式</translatorcomment>\n        <translation>启用精简模式</translation>\n    </message>\n    <message>\n        <source>Simple Mode</source>\n        <translatorcomment>精简模式</translatorcomment>\n        <translation>精简模式</translation>\n    </message>\n    <message>\n        <source>WIFI Connect</source>\n        <translatorcomment>一键WIFI连接</translatorcomment>\n        <translation>一键WIFI连接</translation>\n    </message>\n    <message>\n        <source>USB Connect</source>\n        <translatorcomment>一键USB连接</translatorcomment>\n        <translation>一键USB连接</translation>\n    </message>\n    <message>\n        <source>Double click to connect:</source>\n        <translation>双击连接：</translation>\n    </message>\n    <message>\n        <source>lock orientation:</source>\n        <translation>锁定方向：</translation>\n    </message>\n    <message>\n        <source>show fps</source>\n        <translation>显示fps</translation>\n    </message>\n    <message>\n        <source>stay awake</source>\n        <translation>保持唤醒</translation>\n    </message>\n    <message>\n        <source>device name:</source>\n        <translatorcomment>设备名称:</translatorcomment>\n        <translation>设备名称:</translation>\n    </message>\n    <message>\n        <source>update name</source>\n        <translatorcomment>更新设置名称</translatorcomment>\n        <translation>更新设置名称</translation>\n    </message>\n    <message>\n        <source>stop all server</source>\n        <translation>停止所有服务</translation>\n    </message>\n    <message>\n        <source>adb command:</source>\n        <translation>adb命令：</translation>\n    </message>\n    <message>\n        <source>terminate</source>\n        <translation>终止</translation>\n    </message>\n    <message>\n        <source>execute</source>\n        <translation>执行</translation>\n    </message>\n    <message>\n        <source>clear</source>\n        <translation>清理</translation>\n    </message>\n    <message>\n        <source>reverse connection</source>\n        <translation>反向连接</translation>\n    </message>\n    <message>\n        <source>background record</source>\n        <translation>后台录制</translation>\n    </message>\n    <message>\n        <source>screen-off</source>\n        <translation>自动息屏</translation>\n    </message>\n    <message>\n        <source>apply</source>\n        <translation>应用脚本</translation>\n    </message>\n    <message>\n        <source>max size:</source>\n        <translation>最大尺寸：</translation>\n    </message>\n    <message>\n        <source>always on top</source>\n        <translation>窗口置顶</translation>\n    </message>\n    <message>\n        <source>refresh script</source>\n        <translation>刷新脚本</translation>\n    </message>\n    <message>\n        <source>get device IP</source>\n        <translation>获取设备IP</translation>\n    </message>\n    <message>\n        <source>USB line</source>\n        <translation>USB线</translation>\n    </message>\n    <message>\n        <source>stop server</source>\n        <translation>停止服务</translation>\n    </message>\n    <message>\n        <source>start server</source>\n        <translation>启动服务</translation>\n    </message>\n    <message>\n        <source>device serial:</source>\n        <translation>设备序列号：</translation>\n    </message>\n    <message>\n        <source>bit rate:</source>\n        <translation>比特率：</translation>\n    </message>\n    <message>\n        <source>start adbd</source>\n        <translation>启动adbd</translation>\n    </message>\n    <message>\n        <source>refresh devices</source>\n        <translation>刷新设备列表</translation>\n    </message>\n    <message>\n        <source>install sndcpy</source>\n        <translation>安装sndcpy</translation>\n    </message>\n    <message>\n        <source>start audio</source>\n        <translation>开始音频</translation>\n    </message>\n    <message>\n        <source>stop audio</source>\n        <translation>停止音频</translation>\n    </message>\n    <message>\n        <source>auto update</source>\n        <translation>自动刷新</translation>\n    </message>\n    <message>\n        <source>show toolbar</source>\n        <translation>显示工具栏</translation>\n    </message>\n    <message>\n        <source>record save path:</source>\n        <translation>录像保存路径</translation>\n    </message>\n</context>\n</TS>\n"
  },
  {
    "path": "QtScrcpy/res/qss/psblack.css",
    "content": "QPalette{background:#444444;}*{outline:0px;color:#DCDCDC;}\n\nQWidget[form=\"true\"],QLabel[frameShape=\"1\"]{\nborder:1px solid #242424;\nborder-radius:0px;\n}\n\nQWidget[form=\"bottom\"]{\nbackground:#484848;\n}\n\nQWidget[form=\"bottom\"] .QFrame{\nborder:1px solid #DCDCDC;\n}\n\nQWidget[form=\"bottom\"] QLabel,QWidget[form=\"title\"] QLabel{\nborder-radius:0px;\ncolor:#DCDCDC;\nbackground:none;\nborder-style:none;\n}\n\nQWidget[form=\"title\"],QWidget[nav=\"left\"],QWidget[nav=\"top\"] QAbstractButton{\nborder-style:none;\nborder-radius:0px;\npadding:5px;\ncolor:#DCDCDC;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQWidget[nav=\"top\"] QAbstractButton:hover,QWidget[nav=\"top\"] QAbstractButton:pressed,QWidget[nav=\"top\"] QAbstractButton:checked{\nborder-style:solid;\nborder-width:0px 0px 2px 0px;\npadding:4px 4px 2px 4px;\nborder-color:#00BB9E;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQWidget[nav=\"left\"] QAbstractButton{\nborder-radius:0px;\ncolor:#DCDCDC;\nbackground:none;\nborder-style:none;\n}\n\nQWidget[nav=\"left\"] QAbstractButton:hover{\ncolor:#FFFFFF;\nbackground-color:#00BB9E;\n}\n\nQWidget[nav=\"left\"] QAbstractButton:checked,QWidget[nav=\"left\"] QAbstractButton:pressed{\ncolor:#DCDCDC;\nborder-style:solid;\nborder-width:0px 0px 0px 2px;\npadding:4px 4px 4px 2px;\nborder-color:#00BB9E;\nbackground-color:#444444;\n}\n\nQWidget[video=\"true\"] QLabel{\ncolor:#DCDCDC;\nborder:1px solid #242424;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQWidget[video=\"true\"] QLabel:focus{\nborder:1px solid #00BB9E;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{\nborder:1px solid #242424;\nborder-radius:3px;\npadding:2px;\nbackground:none;\nselection-background-color:#264F78;\nselection-color:#DCDCDC;\n}\n\nQLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{\nborder:1px solid #242424;\n}\n\nQLineEdit[echoMode=\"2\"]{\nlineedit-password-character:9679;\n}\n\n.QFrame{\nborder:1px solid #242424;\nborder-radius:3px;\n}\n\n.QGroupBox{\nborder:1px solid #242424;\nborder-radius:5px;\nmargin-top:3ex;\n}\n\n.QGroupBox::title{\nsubcontrol-origin:margin;\nposition:relative;\nleft:10px;\n}\n\n.QPushButton,.QToolButton{\nborder-style:none;\nborder:1px solid #242424;\ncolor:#DCDCDC;\npadding:5px;\nmin-height:15px;\nborder-radius:5px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\n.QPushButton:hover,.QToolButton:hover{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\n.QPushButton:pressed,.QToolButton:pressed{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\n.QToolButton::menu-indicator{\nimage:None;\n}\n\nQToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{\nborder-radius:3px;\ncolor:#DCDCDC;\npadding:3px;\nmargin:0px;\nbackground:none;\nborder-style:none;\n}\n\nQToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{\ncolor:#FFFFFF;\nmargin:1px 1px 2px 1px;\nbackground-color:rgba(51,127,209,230);\n}\n\nQPushButton#btnMenu_Close:hover{\ncolor:#FFFFFF;\nmargin:1px 1px 2px 1px;\nbackground-color:rgba(238,0,0,128);\n}\n\nQRadioButton::indicator{\nwidth:15px;\nheight:15px;\n}\n\nQRadioButton::indicator::unchecked{\nimage:url(:/qss/psblack/radiobutton_unchecked.png);\n}\n\nQRadioButton::indicator::unchecked:disabled{\nimage:url(:/qss/psblack/radiobutton_unchecked_disable.png);\n}\n\nQRadioButton::indicator::checked{\nimage:url(:/qss/psblack/radiobutton_checked.png);\n}\n\nQRadioButton::indicator::checked:disabled{\nimage:url(:/qss/psblack/radiobutton_checked_disable.png);\n}\n\nQGroupBox::indicator,QTreeWidget::indicator,QListWidget::indicator{\npadding:0px -3px 0px 3px;\n}\n\nQCheckBox::indicator,QGroupBox::indicator,QTreeWidget::indicator,QListWidget::indicator{\nwidth:13px;\nheight:13px;\n}\n\nQCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeWidget::indicator:unchecked,QListWidget::indicator:unchecked{\nimage:url(:/qss/psblack/checkbox_unchecked.png);\n}\n\nQCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeWidget::indicator:unchecked:disabled,QListWidget::indicator:disabled{\nimage:url(:/qss/psblack/checkbox_unchecked_disable.png);\n}\n\nQCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeWidget::indicator:checked,QListWidget::indicator:checked{\nimage:url(:/qss/psblack/checkbox_checked.png);\n}\n\nQCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeWidget::indicator:checked:disabled,QListWidget::indicator:checked:disabled{\nimage:url(:/qss/psblack/checkbox_checked_disable.png);\n}\n\nQCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeWidget::indicator:indeterminate,QListWidget::indicator:indeterminate{\nimage:url(:/qss/psblack/checkbox_parcial.png);\n}\n\nQCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeWidget::indicator:indeterminate:disabled,QListWidget::indicator:indeterminate:disabled{\nimage:url(:/qss/psblack/checkbox_parcial_disable.png);\n}\n\nQTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{\nimage:url(:/qss/psblack/add_top.png);\nwidth:10px;\nheight:10px;\npadding:2px 5px 0px 0px;\n}\n\nQTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{\nimage:url(:/qss/psblack/add_bottom.png);\nwidth:10px;\nheight:10px;\npadding:0px 5px 2px 0px;\n}\n\nQTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{\ntop:-2px;\n}\n  \nQTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{\nbottom:-2px;\n}\n\nQComboBox::down-arrow,QDateEdit[calendarPopup=\"true\"]::down-arrow,QTimeEdit[calendarPopup=\"true\"]::down-arrow,QDateTimeEdit[calendarPopup=\"true\"]::down-arrow{\nimage:url(:/qss/psblack/add_bottom.png);\nwidth:10px;\nheight:10px;\nright:2px;\n}\n\nQComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{\nsubcontrol-origin:padding;\nsubcontrol-position:top right;\nwidth:15px;\nborder-left-width:0px;\nborder-left-style:solid;\nborder-top-right-radius:3px;\nborder-bottom-right-radius:3px;\nborder-left-color:#242424;\n}\n\nQComboBox::drop-down:on{\ntop:1px;\n}\n\nQMenuBar::item{\ncolor:#DCDCDC;\nbackground-color:#484848;\nmargin:0px;\npadding:3px 10px;\n}\n\nQMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{\ncolor:#DCDCDC;\nbackground-color:#484848;\nborder:1px solid #242424;\nmargin:0px;\n}\n\nQMenu::item{\npadding:3px 20px;\n}\n\nQMenu::indicator{\nwidth:13px;\nheight:13px;\n}\n\nQMenu::item:selected,QMenuBar::item:selected{\ncolor:#DCDCDC;\nborder:0px solid #242424;\nbackground:#646464;\n}\n\nQMenu::separator{\nheight:1px;\nbackground:#242424;\n}\n\nQProgressBar{\nmin-height:10px;\nbackground:#484848;\nborder-radius:5px;\ntext-align:center;\nborder:1px solid #484848;\n}\n\nQProgressBar:chunk{\nborder-radius:5px;\nbackground-color:#242424;\n}\n\nQSlider::groove:horizontal{\nbackground:#484848;\nheight:8px;\nborder-radius:4px;\n}\n\nQSlider::add-page:horizontal{\nbackground:#484848;\nheight:8px;\nborder-radius:4px;\n}\n\nQSlider::sub-page:horizontal{\nbackground:#242424;\nheight:8px;\nborder-radius:4px;\n}\n\nQSlider::handle:horizontal{\nwidth:13px;\nmargin-top:-3px;\nmargin-bottom:-3px;\nborder-radius:6px;\nbackground:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424);\n}\n\nQSlider::groove:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#484848;\n}\n\nQSlider::add-page:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#484848;\n}\n\nQSlider::sub-page:vertical{\nwidth:8px;\nborder-radius:4px;\nbackground:#242424;\n}\n\nQSlider::handle:vertical{\nheight:14px;\nmargin-left:-3px;\nmargin-right:-3px;\nborder-radius:6px;\nbackground:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424);\n}\n\nQScrollBar:horizontal{\nbackground:#484848;\npadding:0px;\nborder-radius:6px;\nmax-height:12px;\n}\n\nQScrollBar::handle:horizontal{\nbackground:#525252;\nmin-width:50px;\nborder-radius:6px;\n}\n\nQScrollBar::handle:horizontal:hover{\nbackground:#242424;\n}\n\nQScrollBar::handle:horizontal:pressed{\nbackground:#242424;\n}\n\nQScrollBar::add-page:horizontal{\nbackground:none;\n}\n\nQScrollBar::sub-page:horizontal{\nbackground:none;\n}\n\nQScrollBar::add-line:horizontal{\nbackground:none;\n}\n\nQScrollBar::sub-line:horizontal{\nbackground:none;\n}\n\nQScrollBar:vertical{\nbackground:#484848;\npadding:0px;\nborder-radius:6px;\nmax-width:12px;\n}\n\nQScrollBar::handle:vertical{\nbackground:#525252;\nmin-height:50px;\nborder-radius:6px;\n}\n\nQScrollBar::handle:vertical:hover{\nbackground:#242424;\n}\n\nQScrollBar::handle:vertical:pressed{\nbackground:#242424;\n}\n\nQScrollBar::add-page:vertical{\nbackground:none;\n}\n\nQScrollBar::sub-page:vertical{\nbackground:none;\n}\n\nQScrollBar::add-line:vertical{\nbackground:none;\n}\n\nQScrollBar::sub-line:vertical{\nbackground:none;\n}\n\nQScrollArea{\nborder:0px;\n}\n\nQTreeView,QListView,QTableView,QTabWidget::pane{\nborder:1px solid #242424;\nselection-background-color:#646464;\nselection-color:#DCDCDC;\nalternate-background-color:#525252;\ngridline-color:#242424;\n}\n\nQTreeView::branch:closed:has-children{\nmargin:4px;\nborder-image:url(:/qss/psblack/branch_open.png);\n}\n\nQTreeView::branch:open:has-children{\nmargin:4px;\nborder-image:url(:/qss/psblack/branch_close.png);\n}\n\nQTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{\nbackground:#444444;\n}\n\nQTableView::item:selected,QListView::item:selected,QTreeView::item:selected{\ncolor:#DCDCDC;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQTableView::item:hover,QListView::item:hover,QTreeView::item:hover{\ncolor:#DCDCDC;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQTableView::item,QListView::item,QTreeView::item{\npadding:1px;\nmargin:0px;\n}\n\nQHeaderView::section,QTableCornerButton:section{\npadding:3px;\nmargin:0px;\ncolor:#DCDCDC;\nborder:1px solid #242424;\nborder-left-width:0px;\nborder-right-width:1px;\nborder-top-width:0px;\nborder-bottom-width:1px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQTabBar::tab{\nborder:1px solid #242424;\ncolor:#DCDCDC;\nmargin:0px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQTabBar::tab:selected,QTabBar::tab:hover{\nborder-style:solid;\nborder-color:#00BB9E;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQTabBar::tab:top,QTabBar::tab:bottom{\npadding:3px 8px 3px 8px;\n}\n\nQTabBar::tab:left,QTabBar::tab:right{\npadding:8px 3px 8px 3px;\n}\n\nQTabBar::tab:top:selected,QTabBar::tab:top:hover{\nborder-width:2px 0px 0px 0px;\n}\n\nQTabBar::tab:right:selected,QTabBar::tab:right:hover{\nborder-width:0px 0px 0px 2px;\n}\n\nQTabBar::tab:bottom:selected,QTabBar::tab:bottom:hover{\nborder-width:0px 0px 2px 0px;\n}\n\nQTabBar::tab:left:selected,QTabBar::tab:left:hover{\nborder-width:0px 2px 0px 0px;\n}\n\nQTabBar::tab:first:top:selected,QTabBar::tab:first:top:hover,QTabBar::tab:first:bottom:selected,QTabBar::tab:first:bottom:hover{\nborder-left-width:1px;\nborder-left-color:#242424;\n}\n\nQTabBar::tab:first:left:selected,QTabBar::tab:first:left:hover,QTabBar::tab:first:right:selected,QTabBar::tab:first:right:hover{\nborder-top-width:1px;\nborder-top-color:#242424;\n}\n\nQTabBar::tab:last:top:selected,QTabBar::tab:last:top:hover,QTabBar::tab:last:bottom:selected,QTabBar::tab:last:bottom:hover{\nborder-right-width:1px;\nborder-right-color:#242424;\n}\n\nQTabBar::tab:last:left:selected,QTabBar::tab:last:left:hover,QTabBar::tab:last:right:selected,QTabBar::tab:last:right:hover{\nborder-bottom-width:1px;\nborder-bottom-color:#242424;\n}\n\nQStatusBar::item{\nborder:0px solid #484848;\nborder-radius:3px;\n}\n\nQToolBox::tab,QGroupBox#gboxDevicePanel,QGroupBox#gboxDeviceTitle,QFrame#gboxDevicePanel,QFrame#gboxDeviceTitle{\npadding:3px;\nborder-radius:5px;\ncolor:#DCDCDC;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQToolTip{\nborder:0px solid #DCDCDC;\npadding:1px;\ncolor:#DCDCDC;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQToolBox::tab:selected{\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);\n}\n\nQPrintPreviewDialog QToolButton{\nborder:0px solid #DCDCDC;\nborder-radius:0px;\nmargin:0px;\npadding:3px;\nbackground:none;\n}\n\nQColorDialog QPushButton,QFileDialog QPushButton{\nmin-width:80px;\n}\n\nQToolButton#qt_calendar_prevmonth{\nicon-size:0px;\nmin-width:20px;\nimage:url(:/qss/psblack/calendar_prevmonth.png);\n}\n\nQToolButton#qt_calendar_nextmonth{\nicon-size:0px;\nmin-width:20px;\nimage:url(:/qss/psblack/calendar_nextmonth.png);\n}\n\nQToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{\nborder:0px solid #DCDCDC;\nborder-radius:3px;\nmargin:3px 3px 3px 3px;\npadding:3px;\nbackground:none;\n}\n\nQToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{\nborder:1px solid #242424;\n}\n\nQCalendarWidget QSpinBox#qt_calendar_yearedit{\nmargin:2px;\n}\n\nQCalendarWidget QToolButton::menu-indicator{\nimage:None;\n}\n\nQCalendarWidget QTableView{\nborder-width:0px;\n}\n\nQCalendarWidget QWidget#qt_calendar_navigationbar{\nborder:1px solid #242424;\nborder-width:1px 1px 0px 1px;\nbackground:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);\n}\n\nQComboBox QAbstractItemView::item{\nmin-height:20px;\nmin-width:10px;\n}\n\nQTableView[model=\"true\"]::item{\npadding:0px;\nmargin:0px;\n}\n\nQTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{\nborder-width:0px;\nborder-radius:0px;\n}\n\nQTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{\nborder-width:0px;\nborder-radius:0px;\n}\n\nQLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{\nbackground:#444444;\n}\n\n*:disabled{\nbackground:#444444;\nborder-color:#484848;\n}\n\nQMessageBox {\nbackground-color:#444444;\ncolor:#DCDCDC;\n}\n\n/*TextColor:#DCDCDC*/\n/*PanelColor:#444444*/\n/*BorderColor:#242424*/\n/*NormalColorStart:#484848*/\n/*NormalColorEnd:#383838*/\n/*DarkColorStart:#646464*/\n/*DarkColorEnd:#525252*/\n/*HighColor:#00BB9E*/\n"
  },
  {
    "path": "QtScrcpy/res/res.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/\">\n        <file>font/fontawesome-webfont.ttf</file>\n        <file>image/videoform/phone-h.png</file>\n        <file>image/videoform/phone-v.png</file>\n        <file>qss/psblack.css</file>\n        <file>qss/psblack/add_bottom.png</file>\n        <file>qss/psblack/add_left.png</file>\n        <file>qss/psblack/add_right.png</file>\n        <file>qss/psblack/add_top.png</file>\n        <file>qss/psblack/branch_close.png</file>\n        <file>qss/psblack/branch_open.png</file>\n        <file>qss/psblack/calendar_nextmonth.png</file>\n        <file>qss/psblack/calendar_prevmonth.png</file>\n        <file>qss/psblack/checkbox_checked.png</file>\n        <file>qss/psblack/checkbox_checked_disable.png</file>\n        <file>qss/psblack/checkbox_parcial.png</file>\n        <file>qss/psblack/checkbox_parcial_disable.png</file>\n        <file>qss/psblack/checkbox_unchecked.png</file>\n        <file>qss/psblack/checkbox_unchecked_disable.png</file>\n        <file>qss/psblack/radiobutton_checked.png</file>\n        <file>qss/psblack/radiobutton_checked_disable.png</file>\n        <file>qss/psblack/radiobutton_unchecked.png</file>\n        <file>qss/psblack/radiobutton_unchecked_disable.png</file>\n        <file>i18n/en_US.qm</file>\n        <file>i18n/zh_CN.qm</file>\n        <file>i18n/ja_JP.qm</file>\n        <file>image/tray/logo.png</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "QtScrcpy/sndcpy/sndcpy.bat",
    "content": "@echo off\n\necho Begin Runing...\nset SNDCPY_PORT=28200\nset SNDCPY_APK=sndcpy.apk\nset ADB=adb.exe\n\nif not \"%1\"==\"\" (\n    set serial=-s %1\n)\nif not \"%2\"==\"\" (\n    set SNDCPY_PORT=%2\n)\n\necho Waiting for device %1...\n%ADB% %serial% wait-for-device || goto :error\necho Find device %1\n\nfor /f \"delims=\" %%i in ('%ADB% %serial% shell pm path com.rom1v.sndcpy') do set sndcpy_installed=%%i\nif \"%sndcpy_installed%\"==\"\" (\n    echo Install %SNDCPY_APK%... \n    %ADB% %serial% uninstall com.rom1v.sndcpy || echo uninstall failed\n    %ADB% %serial% install -t -r -g %SNDCPY_APK% || goto :error\n    echo Install %SNDCPY_APK% success\n)\n\necho Request PROJECT_MEDIA permission...\n%ADB% %serial% shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow\n\necho Forward port %SNDCPY_PORT%...\n%ADB% %serial% forward tcp:%SNDCPY_PORT% localabstract:sndcpy || goto :error\n\necho Start %SNDCPY_APK%...\n%ADB% %serial% shell am start com.rom1v.sndcpy/.MainActivity || goto :error\n\n:check_start\necho Waiting %SNDCPY_APK% start...\n::timeout /T 1 /NOBREAK > nul\n%ADB% %serial% shell sleep 0.1\nfor /f \"delims=\" %%i in (\"%ADB% shell 'ps | grep com.rom1v.sndcpy'\") do set sndcpy_started=%%i\nif \"%sndcpy_started%\"==\"\" (\n    goto :check_start\n)\necho %SNDCPY_APK% started...\n\necho Ready playing...\n::vlc.exe -Idummy --demux rawaud --network-caching=0 --play-and-exit tcp://localhost:%SNDCPY_PORT%\n::ffplay.exe -nodisp -autoexit -probesize 32 -sync ext -f s16le -ar 48k -ac 2 tcp://localhost:%SNDCPY_PORT%\ngoto :EOF\n\n:error\necho Failed with error #%errorlevel%.\nexit /b %errorlevel%\n"
  },
  {
    "path": "QtScrcpy/sndcpy/sndcpy.sh",
    "content": "#!/bin/bash\n\necho Begin Runing...\nSNDCPY_PORT=28200\nSNDCPY_APK=sndcpy.apk\nADB=./adb\n\nserial=\nif [[ $# -ge 2 ]]\nthen\n    serial=\"-s $1\"\n    SNDCPY_PORT=$2\nfi\n\necho \"Waiting for device $1...\"\n$ADB $serial wait-for-device\necho \"Find device $1\"\n\nsndcpy_installed=$($ADB $serial shell pm path com.rom1v.sndcpy)\nif [[ $sndcpy_installed == \"\" ]]; then\n    echo Install $SNDCPY_APK... \n    $ADB $serial uninstall com.rom1v.sndcpy || echo uninstall failed\n    $ADB $serial install -t -r -g $SNDCPY_APK\n    echo Install $SNDCPY_APK success\nfi\n\necho Request PROJECT_MEDIA permission...\n$ADB $serial shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow\n\necho Forward port $SNDCPY_PORT...\n$ADB $serial forward tcp:$SNDCPY_PORT localabstract:sndcpy\n\necho Start $SNDCPY_APK...\n$ADB $serial shell am start com.rom1v.sndcpy/.MainActivity\n\nwhile ((1))\ndo\n    echo Waiting $SNDCPY_APK start...\n    sleep 0.1\n    sndcpy_started=$($ADB shell 'ps | grep com.rom1v.sndcpy')\n    if [[ $sndcpy_started != \"\" ]]; then\n        break\n    fi\ndone\n\necho Ready playing..."
  },
  {
    "path": "QtScrcpy/ui/dialog.cpp",
    "content": "﻿#include <QDebug>\n#include <QFile>\n#include <QFileDialog>\n#include <QKeyEvent>\n#include <QRandomGenerator>\n#include <QTime>\n#include <QTimer>\n\n#include \"config.h\"\n#include \"dialog.h\"\n#include \"ui_dialog.h\"\n#include \"videoform.h\"\n#include \"../groupcontroller/groupcontroller.h\"\n\n#ifdef Q_OS_WIN32\n#include \"../util/winutils.h\"\n#endif\n\nQString s_keyMapPath = \"\";\n\nconst QString &getKeyMapPath()\n{\n    if (s_keyMapPath.isEmpty()) {\n        s_keyMapPath = QString::fromLocal8Bit(qgetenv(\"QTSCRCPY_KEYMAP_PATH\"));\n        QFileInfo fileInfo(s_keyMapPath);\n        if (s_keyMapPath.isEmpty() || !fileInfo.isDir()) {\n            s_keyMapPath = QCoreApplication::applicationDirPath() + \"/keymap\";\n        }\n    }\n    return s_keyMapPath;\n}\n\nDialog::Dialog(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)\n{\n    ui->setupUi(this);\n    initUI();\n\n    updateBootConfig(true);\n\n    on_useSingleModeCheck_clicked();\n    on_updateDevice_clicked();\n\n    connect(&m_autoUpdatetimer, &QTimer::timeout, this, &Dialog::on_updateDevice_clicked);\n    if (ui->autoUpdatecheckBox->isChecked()) {\n        m_autoUpdatetimer.start(5000);\n    }\n\n    connect(&m_adb, &qsc::AdbProcess::adbProcessResult, this, [this](qsc::AdbProcess::ADB_EXEC_RESULT processResult) {\n        QString log = \"\";\n        bool newLine = true;\n        QStringList args = m_adb.arguments();\n\n        switch (processResult) {\n        case qsc::AdbProcess::AER_ERROR_START:\n            break;\n        case qsc::AdbProcess::AER_SUCCESS_START:\n            log = \"adb run\";\n            newLine = false;\n            break;\n        case qsc::AdbProcess::AER_ERROR_EXEC:\n            //log = m_adb.getErrorOut();\n            if (args.contains(\"ifconfig\") && args.contains(\"wlan0\")) {\n                getIPbyIp();\n            }\n            break;\n        case qsc::AdbProcess::AER_ERROR_MISSING_BINARY:\n            log = \"adb not found\";\n            break;\n        case qsc::AdbProcess::AER_SUCCESS_EXEC:\n            //log = m_adb.getStdOut();\n            if (args.contains(\"devices\")) {\n                QStringList devices = m_adb.getDevicesSerialFromStdOut();\n                ui->serialBox->clear();\n                ui->connectedPhoneList->clear();\n                for (auto &item : devices) {\n                    ui->serialBox->addItem(item);\n                    ui->connectedPhoneList->addItem(Config::getInstance().getNickName(item) + \"-\" + item);\n                }\n            } else if (args.contains(\"show\") && args.contains(\"wlan0\")) {\n                QString ip = m_adb.getDeviceIPFromStdOut();\n                if (ip.isEmpty()) {\n                    log = \"ip not find, connect to wifi?\";\n                    break;\n                }\n                ui->deviceIpEdt->setEditText(ip);\n            } else if (args.contains(\"ifconfig\") && args.contains(\"wlan0\")) {\n                QString ip = m_adb.getDeviceIPFromStdOut();\n                if (ip.isEmpty()) {\n                    log = \"ip not find, connect to wifi?\";\n                    break;\n                }\n                ui->deviceIpEdt->setEditText(ip);\n            } else if (args.contains(\"ip -o a\")) {\n                QString ip = m_adb.getDeviceIPByIpFromStdOut();\n                if (ip.isEmpty()) {\n                    log = \"ip not find, connect to wifi?\";\n                    break;\n                }\n                ui->deviceIpEdt->setEditText(ip);\n            }\n            break;\n        }\n        if (!log.isEmpty()) {\n            outLog(log, newLine);\n        }\n    });\n\n    m_hideIcon = new QSystemTrayIcon(this);\n    m_hideIcon->setIcon(QIcon(\":/image/tray/logo.png\"));\n    m_menu = new QMenu(this);\n    m_quit = new QAction(this);\n    m_showWindow = new QAction(this);\n    m_showWindow->setText(tr(\"show\"));\n    m_quit->setText(tr(\"quit\"));\n    m_menu->addAction(m_showWindow);\n    m_menu->addAction(m_quit);\n    m_hideIcon->setContextMenu(m_menu);\n    m_hideIcon->show();\n    connect(m_showWindow, &QAction::triggered, this, &Dialog::show);\n    connect(m_quit, &QAction::triggered, this, [this]() {\n        m_hideIcon->hide();\n        qApp->quit();\n    });\n    connect(m_hideIcon, &QSystemTrayIcon::activated, this, &Dialog::slotActivated);\n\n    connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceConnected, this, &Dialog::onDeviceConnected);\n    connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceDisconnected, this, &Dialog::onDeviceDisconnected);\n}\n\nDialog::~Dialog()\n{\n    qDebug() << \"~Dialog()\";\n    updateBootConfig(false);\n    qsc::IDeviceManage::getInstance().disconnectAllDevice();\n    delete ui;\n}\n\nvoid Dialog::initUI()\n{\n    setAttribute(Qt::WA_DeleteOnClose);\n    //setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint);\n\n    setWindowTitle(Config::getInstance().getTitle());\n#ifdef Q_OS_LINUX\n    // Set window icon (inherits from application icon set in main.cpp)\n    // If application icon was set, this will use it automatically\n    if (!qApp->windowIcon().isNull()) {\n        setWindowIcon(qApp->windowIcon());\n    }\n#endif\n\n#ifdef Q_OS_WIN32\n    WinUtils::setDarkBorderToWindow((HWND)this->winId(), true);\n#endif\n\n    ui->bitRateEdit->setValidator(new QIntValidator(1, 99999, this));\n\n    ui->maxSizeBox->addItem(\"640\");\n    ui->maxSizeBox->addItem(\"720\");\n    ui->maxSizeBox->addItem(\"1080\");\n    ui->maxSizeBox->addItem(\"1280\");\n    ui->maxSizeBox->addItem(\"1920\");\n    ui->maxSizeBox->addItem(tr(\"original\"));\n\n    ui->formatBox->addItem(\"mp4\");\n    ui->formatBox->addItem(\"mkv\");\n\n    ui->lockOrientationBox->addItem(tr(\"no lock\"));\n    ui->lockOrientationBox->addItem(\"0\");\n    ui->lockOrientationBox->addItem(\"90\");\n    ui->lockOrientationBox->addItem(\"180\");\n    ui->lockOrientationBox->addItem(\"270\");\n    ui->lockOrientationBox->setCurrentIndex(0);\n\n    // 加载IP历史记录\n    loadIpHistory();\n\n    // 加载端口历史记录\n    loadPortHistory();\n\n    // 为deviceIpEdt添加右键菜单\n    if (ui->deviceIpEdt->lineEdit()) {\n        ui->deviceIpEdt->lineEdit()->setContextMenuPolicy(Qt::CustomContextMenu);\n        connect(ui->deviceIpEdt->lineEdit(), &QWidget::customContextMenuRequested,\n                this, &Dialog::showIpEditMenu);\n    }\n    \n    // 为devicePortEdt添加右键菜单\n    if (ui->devicePortEdt->lineEdit()) {\n        ui->devicePortEdt->lineEdit()->setContextMenuPolicy(Qt::CustomContextMenu);\n        connect(ui->devicePortEdt->lineEdit(), &QWidget::customContextMenuRequested,\n                this, &Dialog::showPortEditMenu);\n    }\n}\n\nvoid Dialog::updateBootConfig(bool toView)\n{\n    if (toView) {\n        UserBootConfig config = Config::getInstance().getUserBootConfig();\n\n        if (config.bitRate == 0) {\n            ui->bitRateBox->setCurrentText(\"Mbps\");\n        } else if (config.bitRate % 1000000 == 0) {\n            ui->bitRateEdit->setText(QString::number(config.bitRate / 1000000));\n            ui->bitRateBox->setCurrentText(\"Mbps\");\n        } else {\n            ui->bitRateEdit->setText(QString::number(config.bitRate / 1000));\n            ui->bitRateBox->setCurrentText(\"Kbps\");\n        }\n\n        ui->maxSizeBox->setCurrentIndex(config.maxSizeIndex);\n        ui->formatBox->setCurrentIndex(config.recordFormatIndex);\n        ui->recordPathEdt->setText(config.recordPath);\n        ui->lockOrientationBox->setCurrentIndex(config.lockOrientationIndex);\n        ui->framelessCheck->setChecked(config.framelessWindow);\n        ui->recordScreenCheck->setChecked(config.recordScreen);\n        ui->notDisplayCheck->setChecked(config.recordBackground);\n        ui->useReverseCheck->setChecked(config.reverseConnect);\n        ui->fpsCheck->setChecked(config.showFPS);\n        ui->alwaysTopCheck->setChecked(config.windowOnTop);\n        ui->closeScreenCheck->setChecked(config.autoOffScreen);\n        ui->stayAwakeCheck->setChecked(config.keepAlive);\n        ui->useSingleModeCheck->setChecked(config.simpleMode);\n        ui->autoUpdatecheckBox->setChecked(config.autoUpdateDevice);\n        ui->showToolbar->setChecked(config.showToolbar);\n    } else {\n        UserBootConfig config;\n\n        config.bitRate = getBitRate();\n        config.maxSizeIndex = ui->maxSizeBox->currentIndex();\n        config.recordFormatIndex = ui->formatBox->currentIndex();\n        config.recordPath = ui->recordPathEdt->text();\n        config.lockOrientationIndex = ui->lockOrientationBox->currentIndex();\n        config.recordScreen = ui->recordScreenCheck->isChecked();\n        config.recordBackground = ui->notDisplayCheck->isChecked();\n        config.reverseConnect = ui->useReverseCheck->isChecked();\n        config.showFPS = ui->fpsCheck->isChecked();\n        config.windowOnTop = ui->alwaysTopCheck->isChecked();\n        config.autoOffScreen = ui->closeScreenCheck->isChecked();\n        config.framelessWindow = ui->framelessCheck->isChecked();\n        config.keepAlive = ui->stayAwakeCheck->isChecked();\n        config.simpleMode = ui->useSingleModeCheck->isChecked();\n        config.autoUpdateDevice = ui->autoUpdatecheckBox->isChecked();\n        config.showToolbar = ui->showToolbar->isChecked();\n\n        // 保存当前IP到历史记录\n        QString currentIp = ui->deviceIpEdt->currentText().trimmed();\n        if (!currentIp.isEmpty()) {\n            saveIpHistory(currentIp);\n        }\n\n        Config::getInstance().setUserBootConfig(config);\n    }\n}\n\nvoid Dialog::execAdbCmd()\n{\n    if (checkAdbRun()) {\n        return;\n    }\n    QString cmd = ui->adbCommandEdt->text().trimmed();\n    outLog(\"adb \" + cmd, false);\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(\" \", Qt::SkipEmptyParts));\n#else\n    m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(\" \", QString::SkipEmptyParts));\n#endif\n}\n\nvoid Dialog::delayMs(int ms)\n{\n    QTime dieTime = QTime::currentTime().addMSecs(ms);\n\n    while (QTime::currentTime() < dieTime) {\n        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);\n    }\n}\n\nQString Dialog::getGameScript(const QString &fileName)\n{\n    if (fileName.isEmpty()) {\n        return \"\";\n    }\n\n    QFile loadFile(getKeyMapPath() + \"/\" + fileName);\n    if (!loadFile.open(QIODevice::ReadOnly)) {\n        outLog(\"open file failed:\" + fileName, true);\n        return \"\";\n    }\n\n    QString ret = loadFile.readAll();\n    loadFile.close();\n    return ret;\n}\n\nvoid Dialog::slotActivated(QSystemTrayIcon::ActivationReason reason)\n{\n    switch (reason) {\n    case QSystemTrayIcon::Trigger:\n#ifdef Q_OS_WIN32\n        this->show();\n#endif\n        break;\n    default:\n        break;\n    }\n}\n\nvoid Dialog::closeEvent(QCloseEvent *event)\n{\n    this->hide();\n    if (!Config::getInstance().getTrayMessageShown()) {\n        Config::getInstance().setTrayMessageShown(true);\n        m_hideIcon->showMessage(tr(\"Notice\"),\n                                tr(\"Hidden here!\"),\n                                QSystemTrayIcon::Information,\n                                3000);\n    }\n    event->ignore();\n}\n\nvoid Dialog::on_updateDevice_clicked()\n{\n    if (checkAdbRun()) {\n        return;\n    }\n    outLog(\"update devices...\", false);\n    m_adb.execute(\"\", QStringList() << \"devices\");\n}\n\nvoid Dialog::on_startServerBtn_clicked()\n{\n    outLog(\"start server...\", false);\n\n    // this is ok that \"original\" toUshort is 0\n    quint16 videoSize = ui->maxSizeBox->currentText().trimmed().toUShort();\n    qsc::DeviceParams params;\n    params.serial = ui->serialBox->currentText().trimmed();\n    params.maxSize = videoSize;\n    params.bitRate = getBitRate();\n    // on devices with Android >= 10, the capture frame rate can be limited\n    params.maxFps = static_cast<quint32>(Config::getInstance().getMaxFps());\n    params.closeScreen = ui->closeScreenCheck->isChecked();\n    params.useReverse = ui->useReverseCheck->isChecked();\n    params.display = !ui->notDisplayCheck->isChecked();\n    params.renderExpiredFrames = Config::getInstance().getRenderExpiredFrames();\n    if (ui->lockOrientationBox->currentIndex() > 0) {\n        params.captureOrientationLock = 1;\n        params.captureOrientation = (ui->lockOrientationBox->currentIndex() - 1) * 90;\n    }\n    params.stayAwake = ui->stayAwakeCheck->isChecked();\n    params.recordFile = ui->recordScreenCheck->isChecked();\n    params.recordPath = ui->recordPathEdt->text().trimmed();\n    params.recordFileFormat = ui->formatBox->currentText().trimmed();\n    params.serverLocalPath = getServerPath();\n    params.serverRemotePath = Config::getInstance().getServerPath();\n    params.pushFilePath = Config::getInstance().getPushFilePath();\n    params.gameScript = getGameScript(ui->gameBox->currentText());\n    params.logLevel = Config::getInstance().getLogLevel();\n    params.codecOptions = Config::getInstance().getCodecOptions();\n    params.codecName = Config::getInstance().getCodecName();\n    params.scid = QRandomGenerator::global()->bounded(1, 10000) & 0x7FFFFFFF;\n\n    qsc::IDeviceManage::getInstance().connectDevice(params);\n}\n\nvoid Dialog::on_stopServerBtn_clicked()\n{\n    if (qsc::IDeviceManage::getInstance().disconnectDevice(ui->serialBox->currentText().trimmed())) {\n        outLog(\"stop server\");\n    }\n}\n\nvoid Dialog::on_wirelessConnectBtn_clicked()\n{\n    if (checkAdbRun()) {\n        return;\n    }\n    QString addr = ui->deviceIpEdt->currentText().trimmed();\n    if (addr.isEmpty()) {\n        outLog(\"error: device ip is null\", false);\n        return;\n    }\n\n    if (!ui->devicePortEdt->currentText().isEmpty()) {\n        addr += \":\";\n        addr += ui->devicePortEdt->currentText().trimmed();\n    } else if (!ui->devicePortEdt->lineEdit()->placeholderText().isEmpty()) {\n        addr += \":\";\n        addr += ui->devicePortEdt->lineEdit()->placeholderText().trimmed();\n    } else {\n        outLog(\"error: device port is null\", false);\n        return;\n    }\n\n    // 保存IP历史记录 - 只保存IP部分,不包含端口\n    QString ip = addr.split(\":\").first();\n    if (!ip.isEmpty()) {\n        saveIpHistory(ip);\n    }\n    \n    // 保存端口历史记录\n    QString port = addr.split(\":\").last();\n    if (!port.isEmpty() && port != ip) {\n        savePortHistory(port);\n    }\n\n    outLog(\"wireless connect...\", false);\n    QStringList adbArgs;\n    adbArgs << \"connect\";\n    adbArgs << addr;\n    m_adb.execute(\"\", adbArgs);\n}\n\nvoid Dialog::on_startAdbdBtn_clicked()\n{\n    if (checkAdbRun()) {\n        return;\n    }\n    outLog(\"start devices adbd...\", false);\n    // adb tcpip 5555\n    QStringList adbArgs;\n    adbArgs << \"tcpip\";\n    adbArgs << \"5555\";\n    m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);\n}\n\nvoid Dialog::outLog(const QString &log, bool newLine)\n{\n    // avoid sub thread update ui\n    QString backLog = log;\n    QTimer::singleShot(0, this, [this, backLog, newLine]() {\n        ui->outEdit->append(backLog);\n        if (newLine) {\n            ui->outEdit->append(\"<br/>\");\n        }\n    });\n}\n\nbool Dialog::filterLog(const QString &log)\n{\n    if (log.contains(\"app_proces\")) {\n        return true;\n    }\n    if (log.contains(\"Unable to set geometry\")) {\n        return true;\n    }\n    return false;\n}\n\nbool Dialog::checkAdbRun()\n{\n    if (m_adb.isRuning()) {\n        outLog(\"wait for the end of the current command to run\");\n    }\n    return m_adb.isRuning();\n}\n\nvoid Dialog::on_getIPBtn_clicked()\n{\n    if (checkAdbRun()) {\n        return;\n    }\n\n    outLog(\"get ip...\", false);\n    // adb -s P7C0218510000537 shell ifconfig wlan0\n    // or\n    // adb -s P7C0218510000537 shell ip -f inet addr show wlan0\n    QStringList adbArgs;\n#if 0\n    adbArgs << \"shell\";\n    adbArgs << \"ip\";\n    adbArgs << \"-f\";\n    adbArgs << \"inet\";\n    adbArgs << \"addr\";\n    adbArgs << \"show\";\n    adbArgs << \"wlan0\";\n#else\n    adbArgs << \"shell\";\n    adbArgs << \"ifconfig\";\n    adbArgs << \"wlan0\";\n#endif\n    m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);\n}\n\nvoid Dialog::getIPbyIp()\n{\n    if (checkAdbRun()) {\n        return;\n    }\n\n    QStringList adbArgs;\n    adbArgs << \"shell\";\n    adbArgs << \"ip -o a\";\n\n    m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);\n}\n\nvoid Dialog::onDeviceConnected(bool success, const QString &serial, const QString &deviceName, const QSize &size)\n{\n    Q_UNUSED(deviceName);\n    if (!success) {\n        return;\n    }\n    auto videoForm = new VideoForm(ui->framelessCheck->isChecked(), Config::getInstance().getSkin(), ui->showToolbar->isChecked());\n    videoForm->setSerial(serial);\n\n    qsc::IDeviceManage::getInstance().getDevice(serial)->setUserData(static_cast<void*>(videoForm));\n    qsc::IDeviceManage::getInstance().getDevice(serial)->registerDeviceObserver(videoForm);\n\n\n    videoForm->showFPS(ui->fpsCheck->isChecked());\n\n    if (ui->alwaysTopCheck->isChecked()) {\n        videoForm->staysOnTop();\n    }\n\n#ifndef Q_OS_WIN32\n    // must be show before updateShowSize\n    videoForm->show();\n#endif\n    QString name = Config::getInstance().getNickName(serial);\n    if (name.isEmpty()) {\n        name = Config::getInstance().getTitle();\n    }\n    videoForm->setWindowTitle(name + \"-\" + serial);\n    videoForm->updateShowSize(size);\n\n    bool deviceVer = size.height() > size.width();\n    QRect rc = Config::getInstance().getRect(serial);\n    bool rcVer = rc.height() > rc.width();\n    // same width/height rate\n    if (rc.isValid() && (deviceVer == rcVer)) {\n        // mark: resize is for fix setGeometry magneticwidget bug\n        videoForm->resize(rc.size());\n        videoForm->setGeometry(rc);\n    }\n\n#ifdef Q_OS_WIN32\n    // windows是show太早可以看到resize的过程\n    QTimer::singleShot(200, videoForm, [videoForm](){videoForm->show();});\n#endif\n\n    GroupController::instance().addDevice(serial);\n}\n\nvoid Dialog::onDeviceDisconnected(QString serial)\n{\n    GroupController::instance().removeDevice(serial);\n    auto device = qsc::IDeviceManage::getInstance().getDevice(serial);\n    if (!device) {\n        return;\n    }\n    auto data = device->getUserData();\n    if (data) {\n        VideoForm* vf = static_cast<VideoForm*>(data);\n        qsc::IDeviceManage::getInstance().getDevice(serial)->deRegisterDeviceObserver(vf);\n        vf->close();\n        vf->deleteLater();\n    }\n}\n\nvoid Dialog::on_wirelessDisConnectBtn_clicked()\n{\n    if (checkAdbRun()) {\n        return;\n    }\n    QString addr = ui->deviceIpEdt->currentText().trimmed();\n    outLog(\"wireless disconnect...\", false);\n    QStringList adbArgs;\n    adbArgs << \"disconnect\";\n    adbArgs << addr;\n    m_adb.execute(\"\", adbArgs);\n}\n\nvoid Dialog::on_selectRecordPathBtn_clicked()\n{\n    QFileDialog::Options options = QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly;\n    QString directory = QFileDialog::getExistingDirectory(this, tr(\"select path\"), \"\", options);\n    ui->recordPathEdt->setText(directory);\n}\n\nvoid Dialog::on_recordPathEdt_textChanged(const QString &arg1)\n{\n    ui->recordPathEdt->setToolTip(arg1.trimmed());\n    ui->notDisplayCheck->setCheckable(!arg1.trimmed().isEmpty());\n}\n\nvoid Dialog::on_adbCommandBtn_clicked()\n{\n    execAdbCmd();\n}\n\nvoid Dialog::on_stopAdbBtn_clicked()\n{\n    m_adb.kill();\n}\n\nvoid Dialog::on_clearOut_clicked()\n{\n    ui->outEdit->clear();\n}\n\nvoid Dialog::on_stopAllServerBtn_clicked()\n{\n    qsc::IDeviceManage::getInstance().disconnectAllDevice();\n}\n\nvoid Dialog::on_refreshGameScriptBtn_clicked()\n{\n    ui->gameBox->clear();\n    QDir dir(getKeyMapPath());\n    if (!dir.exists()) {\n        outLog(\"keymap directory not find\", true);\n        return;\n    }\n    dir.setFilter(QDir::Files | QDir::NoSymLinks);\n    QFileInfoList list = dir.entryInfoList();\n    QFileInfo fileInfo;\n    int size = list.size();\n    for (int i = 0; i < size; ++i) {\n        fileInfo = list.at(i);\n        ui->gameBox->addItem(fileInfo.fileName());\n    }\n}\n\nvoid Dialog::on_applyScriptBtn_clicked()\n{\n    auto curSerial = ui->serialBox->currentText().trimmed();\n    auto device = qsc::IDeviceManage::getInstance().getDevice(curSerial);\n    if (!device) {\n        return;\n    }\n\n    device->updateScript(getGameScript(ui->gameBox->currentText()));\n}\n\nvoid Dialog::on_recordScreenCheck_clicked(bool checked)\n{\n    if (!checked) {\n        return;\n    }\n\n    QString fileDir(ui->recordPathEdt->text().trimmed());\n    if (fileDir.isEmpty()) {\n        qWarning() << \"please select record save path!!!\";\n        ui->recordScreenCheck->setChecked(false);\n    }\n}\n\nvoid Dialog::on_usbConnectBtn_clicked()\n{\n    on_stopAllServerBtn_clicked();\n    delayMs(200);\n    on_updateDevice_clicked();\n    delayMs(200);\n\n    int firstUsbDevice = findDeviceFromeSerialBox(false);\n    if (-1 == firstUsbDevice) {\n        qWarning() << \"No use device is found!\";\n        return;\n    }\n    ui->serialBox->setCurrentIndex(firstUsbDevice);\n\n    on_startServerBtn_clicked();\n}\n\nint Dialog::findDeviceFromeSerialBox(bool wifi)\n{\n    QString regStr = \"\\\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\:([0-9]|[1-9]\\\\d|[1-9]\\\\d{2}|[1-9]\\\\d{3}|[1-5]\\\\d{4}|6[0-4]\\\\d{3}|65[0-4]\\\\d{2}|655[0-2]\\\\d|6553[0-5])\\\\b\";\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n    QRegExp regIP(regStr);\n#else\n    QRegularExpression regIP(regStr);\n#endif\n    for (int i = 0; i < ui->serialBox->count(); ++i) {\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n        bool isWifi = regIP.exactMatch(ui->serialBox->itemText(i));\n#else\n        bool isWifi = regIP.match(ui->serialBox->itemText(i)).hasMatch();\n#endif\n        bool found = wifi ? isWifi : !isWifi;\n        if (found) {\n            return i;\n        }\n    }\n\n    return -1;\n}\n\nvoid Dialog::on_wifiConnectBtn_clicked()\n{\n    on_stopAllServerBtn_clicked();\n    delayMs(200);\n\n    on_updateDevice_clicked();\n    delayMs(200);\n\n    int firstUsbDevice = findDeviceFromeSerialBox(false);\n    if (-1 == firstUsbDevice) {\n        qWarning() << \"No use device is found!\";\n        return;\n    }\n    ui->serialBox->setCurrentIndex(firstUsbDevice);\n\n    on_getIPBtn_clicked();\n    delayMs(200);\n\n    on_startAdbdBtn_clicked();\n    delayMs(1000);\n\n    on_wirelessConnectBtn_clicked();\n    delayMs(2000);\n\n    on_updateDevice_clicked();\n    delayMs(200);\n\n    int firstWifiDevice = findDeviceFromeSerialBox(true);\n    if (-1 == firstWifiDevice) {\n        qWarning() << \"No wifi device is found!\";\n        return;\n    }\n    ui->serialBox->setCurrentIndex(firstWifiDevice);\n\n    on_startServerBtn_clicked();\n}\n\nvoid Dialog::on_connectedPhoneList_itemDoubleClicked(QListWidgetItem *item)\n{\n    Q_UNUSED(item);\n    ui->serialBox->setCurrentIndex(ui->connectedPhoneList->currentRow());\n    on_startServerBtn_clicked();\n}\n\nvoid Dialog::on_updateNameBtn_clicked()\n{\n    if (ui->serialBox->count() != 0) {\n        if (ui->userNameEdt->text().isEmpty()) {\n            Config::getInstance().setNickName(ui->serialBox->currentText(), \"Phone\");\n        } else {\n            Config::getInstance().setNickName(ui->serialBox->currentText(), ui->userNameEdt->text());\n        }\n\n        on_updateDevice_clicked();\n\n        qDebug() << \"Update OK!\";\n    } else {\n        qWarning() << \"No device is connected!\";\n    }\n}\n\nvoid Dialog::on_useSingleModeCheck_clicked()\n{\n    if (ui->useSingleModeCheck->isChecked()) {\n        ui->rightWidget->hide();\n    } else {\n        ui->rightWidget->show();\n    }\n\n    adjustSize();\n}\n\nvoid Dialog::on_serialBox_currentIndexChanged(const QString &arg1)\n{\n    ui->userNameEdt->setText(Config::getInstance().getNickName(arg1));\n}\n\nquint32 Dialog::getBitRate()\n{\n    return ui->bitRateEdit->text().trimmed().toUInt() *\n            (ui->bitRateBox->currentText() == QString(\"Mbps\") ? 1000000 : 1000);\n}\n\nconst QString &Dialog::getServerPath()\n{\n    static QString serverPath;\n    if (serverPath.isEmpty()) {\n        serverPath = QString::fromLocal8Bit(qgetenv(\"QTSCRCPY_SERVER_PATH\"));\n        QFileInfo fileInfo(serverPath);\n        if (serverPath.isEmpty() || !fileInfo.isFile()) {\n            serverPath = QCoreApplication::applicationDirPath() + \"/scrcpy-server\";\n        }\n    }\n    return serverPath;\n}\n\nvoid Dialog::on_startAudioBtn_clicked()\n{\n    if (ui->serialBox->count() == 0) {\n        qWarning() << \"No device is connected!\";\n        return;\n    }\n\n    m_audioOutput.start(ui->serialBox->currentText(), 28200);\n}\n\nvoid Dialog::on_stopAudioBtn_clicked()\n{\n    m_audioOutput.stop();\n}\n\nvoid Dialog::on_installSndcpyBtn_clicked()\n{\n    if (ui->serialBox->count() == 0) {\n        qWarning() << \"No device is connected!\";\n        return;\n    }\n    m_audioOutput.installonly(ui->serialBox->currentText(), 28200);\n}\n\nvoid Dialog::on_autoUpdatecheckBox_toggled(bool checked)\n{\n    if (checked) {\n        m_autoUpdatetimer.start(5000);\n    } else {\n        m_autoUpdatetimer.stop();\n    }\n}\n\nvoid Dialog::loadIpHistory()\n{\n    QStringList ipList = Config::getInstance().getIpHistory();\n    ui->deviceIpEdt->clear();\n    ui->deviceIpEdt->addItems(ipList);\n    ui->deviceIpEdt->setContentsMargins(0, 0, 0, 0);\n\n    if (ui->deviceIpEdt->lineEdit()) {\n        ui->deviceIpEdt->lineEdit()->setMaxLength(128);\n        ui->deviceIpEdt->lineEdit()->setPlaceholderText(\"192.168.0.1\");\n    }\n}\n\nvoid Dialog::saveIpHistory(const QString &ip)\n{\n    if (ip.isEmpty()) {\n        return;\n    }\n    \n    Config::getInstance().saveIpHistory(ip);\n    \n    // 更新ComboBox\n    loadIpHistory();\n    ui->deviceIpEdt->setCurrentText(ip);\n}\n\nvoid Dialog::showIpEditMenu(const QPoint &pos)\n{\n    QMenu *menu = ui->deviceIpEdt->lineEdit()->createStandardContextMenu();\n    menu->addSeparator();\n    \n    QAction *clearHistoryAction = new QAction(tr(\"Clear History\"), menu);\n    connect(clearHistoryAction, &QAction::triggered, this, [this]() {\n        Config::getInstance().clearIpHistory();\n        loadIpHistory();\n    });\n    \n    menu->addAction(clearHistoryAction);\n    menu->exec(ui->deviceIpEdt->lineEdit()->mapToGlobal(pos));\n    delete menu;\n}\n\nvoid Dialog::loadPortHistory()\n{\n    QStringList portList = Config::getInstance().getPortHistory();\n    ui->devicePortEdt->clear();\n    ui->devicePortEdt->addItems(portList);\n    ui->devicePortEdt->setContentsMargins(0, 0, 0, 0);\n\n    if (ui->devicePortEdt->lineEdit()) {\n        ui->devicePortEdt->lineEdit()->setMaxLength(6);\n        ui->devicePortEdt->lineEdit()->setPlaceholderText(\"5555\");\n    }\n}\n\nvoid Dialog::savePortHistory(const QString &port)\n{\n    if (port.isEmpty()) {\n        return;\n    }\n    \n    Config::getInstance().savePortHistory(port);\n    \n    // 更新ComboBox\n    loadPortHistory();\n    ui->devicePortEdt->setCurrentText(port);\n}\n\nvoid Dialog::showPortEditMenu(const QPoint &pos)\n{\n    QMenu *menu = ui->devicePortEdt->lineEdit()->createStandardContextMenu();\n    menu->addSeparator();\n    \n    QAction *clearHistoryAction = new QAction(tr(\"Clear History\"), menu);\n    connect(clearHistoryAction, &QAction::triggered, this, [this]() {\n        Config::getInstance().clearPortHistory();\n        loadPortHistory();\n    });\n    \n    menu->addAction(clearHistoryAction);\n    menu->exec(ui->devicePortEdt->lineEdit()->mapToGlobal(pos));\n    delete menu;\n}\n"
  },
  {
    "path": "QtScrcpy/ui/dialog.h",
    "content": "﻿#ifndef DIALOG_H\n#define DIALOG_H\n\n#include <QWidget>\n#include <QPointer>\n#include <QMessageBox>\n#include <QMenu>\n#include <QSystemTrayIcon>\n#include <QListWidget>\n#include <QTimer>\n\n\n#include \"adbprocess.h\"\n#include \"../QtScrcpyCore/include/QtScrcpyCore.h\"\n#include \"audio/audiooutput.h\"\n\nnamespace Ui\n{\n    class Widget;\n}\n\nclass QYUVOpenGLWidget;\nclass Dialog : public QWidget\n{\n    Q_OBJECT\n\npublic:\n    explicit Dialog(QWidget *parent = 0);\n    ~Dialog();\n\n    void outLog(const QString &log, bool newLine = true);\n    bool filterLog(const QString &log);\n    void getIPbyIp();\n\nprivate slots:\n    void onDeviceConnected(bool success, const QString& serial, const QString& deviceName, const QSize& size);\n    void onDeviceDisconnected(QString serial);\n\n    void on_updateDevice_clicked();\n    void on_startServerBtn_clicked();\n    void on_stopServerBtn_clicked();\n    void on_wirelessConnectBtn_clicked();\n    void on_startAdbdBtn_clicked();\n    void on_getIPBtn_clicked();\n    void on_wirelessDisConnectBtn_clicked();\n    void on_selectRecordPathBtn_clicked();\n    void on_recordPathEdt_textChanged(const QString &arg1);\n    void on_adbCommandBtn_clicked();\n    void on_stopAdbBtn_clicked();\n    void on_clearOut_clicked();\n    void on_stopAllServerBtn_clicked();\n    void on_refreshGameScriptBtn_clicked();\n    void on_applyScriptBtn_clicked();\n    void on_recordScreenCheck_clicked(bool checked);\n    void on_usbConnectBtn_clicked();\n    void on_wifiConnectBtn_clicked();\n    void on_connectedPhoneList_itemDoubleClicked(QListWidgetItem *item);\n    void on_updateNameBtn_clicked();\n    void on_useSingleModeCheck_clicked();\n    void on_serialBox_currentIndexChanged(const QString &arg1);\n\n    void on_startAudioBtn_clicked();\n\n    void on_stopAudioBtn_clicked();\n\n    void on_installSndcpyBtn_clicked();\n\n    void on_autoUpdatecheckBox_toggled(bool checked);\n\n    void showIpEditMenu(const QPoint &pos);\n\nprivate:\n    bool checkAdbRun();\n    void initUI();\n    void updateBootConfig(bool toView = true);\n    void execAdbCmd();\n    void delayMs(int ms);\n    QString getGameScript(const QString &fileName);\n    void slotActivated(QSystemTrayIcon::ActivationReason reason);\n    int findDeviceFromeSerialBox(bool wifi);\n    quint32 getBitRate();\n    const QString &getServerPath();\n    void loadIpHistory();\n    void saveIpHistory(const QString &ip);\n    void loadPortHistory();\n    void savePortHistory(const QString &port);\n\n    void showPortEditMenu(const QPoint &pos);\n\nprotected:\n    void closeEvent(QCloseEvent *event);\n\nprivate:\n    Ui::Widget *ui;\n    qsc::AdbProcess m_adb;\n    QSystemTrayIcon *m_hideIcon;\n    QMenu *m_menu;\n    QAction *m_showWindow;\n    QAction *m_quit;\n    AudioOutput m_audioOutput;\n    QTimer m_autoUpdatetimer;\n};\n\n#endif // DIALOG_H\n"
  },
  {
    "path": "QtScrcpy/ui/dialog.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>Widget</class>\n <widget class=\"QWidget\" name=\"Widget\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>1293</width>\n    <height>502</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string notr=\"true\">QtScrcpy</string>\n  </property>\n  <layout class=\"QHBoxLayout\" name=\"horizontalLayout_11\">\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"QWidget\" name=\"leftWidget\" native=\"true\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"verticalLayout_5\">\n      <item>\n       <widget class=\"QCheckBox\" name=\"useSingleModeCheck\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"styleSheet\">\n         <string notr=\"true\"/>\n        </property>\n        <property name=\"text\">\n         <string>Use Simple Mode</string>\n        </property>\n        <property name=\"checked\">\n         <bool>false</bool>\n        </property>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QGroupBox\" name=\"simpleGroupBox\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string>Simple Mode</string>\n        </property>\n        <property name=\"checkable\">\n         <bool>false</bool>\n        </property>\n        <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n         <item>\n          <layout class=\"QHBoxLayout\" name=\"horizontalLayout_9\">\n           <item>\n            <widget class=\"QPushButton\" name=\"wifiConnectBtn\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"text\">\n              <string>WIFI Connect</string>\n             </property>\n            </widget>\n           </item>\n           <item>\n            <widget class=\"QPushButton\" name=\"usbConnectBtn\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"text\">\n              <string>USB Connect</string>\n             </property>\n            </widget>\n           </item>\n          </layout>\n         </item>\n         <item>\n          <layout class=\"QHBoxLayout\" name=\"horizontalLayout_13\">\n           <property name=\"topMargin\">\n            <number>0</number>\n           </property>\n           <item>\n            <widget class=\"QLabel\" name=\"label_10\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"text\">\n              <string>Double click to connect:</string>\n             </property>\n            </widget>\n           </item>\n           <item>\n            <widget class=\"QCheckBox\" name=\"autoUpdatecheckBox\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"text\">\n              <string>auto update</string>\n             </property>\n             <property name=\"checked\">\n              <bool>true</bool>\n             </property>\n            </widget>\n           </item>\n          </layout>\n         </item>\n         <item>\n          <widget class=\"QListWidget\" name=\"connectedPhoneList\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n          </widget>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QGroupBox\" name=\"adbGroupBox\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Maximum\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string notr=\"true\">adb</string>\n        </property>\n        <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n         <property name=\"spacing\">\n          <number>3</number>\n         </property>\n         <property name=\"leftMargin\">\n          <number>5</number>\n         </property>\n         <property name=\"topMargin\">\n          <number>5</number>\n         </property>\n         <property name=\"rightMargin\">\n          <number>5</number>\n         </property>\n         <property name=\"bottomMargin\">\n          <number>5</number>\n         </property>\n         <item>\n          <widget class=\"QLabel\" name=\"label_7\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>adb command:</string>\n           </property>\n           <property name=\"buddy\">\n            <cstring>adbCommandEdt</cstring>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QLineEdit\" name=\"adbCommandEdt\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string notr=\"true\">devices</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QPushButton\" name=\"adbCommandBtn\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>execute</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QPushButton\" name=\"stopAdbBtn\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>terminate</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QPushButton\" name=\"clearOut\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>clear</string>\n           </property>\n          </widget>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QTextEdit\" name=\"outEdit\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"focusPolicy\">\n         <enum>Qt::NoFocus</enum>\n        </property>\n        <property name=\"documentTitle\">\n         <string/>\n        </property>\n        <property name=\"readOnly\">\n         <bool>true</bool>\n        </property>\n       </widget>\n      </item>\n     </layout>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QWidget\" name=\"rightWidget\" native=\"true\">\n     <property name=\"sizePolicy\">\n      <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"Preferred\">\n       <horstretch>0</horstretch>\n       <verstretch>0</verstretch>\n      </sizepolicy>\n     </property>\n     <layout class=\"QVBoxLayout\" name=\"verticalLayout_6\">\n      <item>\n       <widget class=\"QGroupBox\" name=\"configGroupBox\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string>Start Config</string>\n        </property>\n        <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n         <property name=\"spacing\">\n          <number>3</number>\n         </property>\n         <property name=\"leftMargin\">\n          <number>5</number>\n         </property>\n         <property name=\"topMargin\">\n          <number>5</number>\n         </property>\n         <property name=\"rightMargin\">\n          <number>5</number>\n         </property>\n         <property name=\"bottomMargin\">\n          <number>5</number>\n         </property>\n         <item>\n          <widget class=\"QWidget\" name=\"configWidget1\" native=\"true\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_5\">\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QLabel\" name=\"label_3\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>bit rate:</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLineEdit\" name=\"bitRateEdit\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string notr=\"true\">2</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"bitRateBox\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"toolTip\">\n               <string/>\n              </property>\n              <property name=\"currentText\">\n               <string notr=\"true\">Mbps</string>\n              </property>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">Mbps</string>\n               </property>\n              </item>\n              <item>\n               <property name=\"text\">\n                <string notr=\"true\">Kbps</string>\n               </property>\n              </item>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLabel\" name=\"label_4\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>max size:</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"maxSizeBox\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"toolTip\">\n               <string/>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QWidget\" name=\"configWidget5\" native=\"true\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_7\">\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QLabel\" name=\"label_6\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>record format：</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"formatBox\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLabel\" name=\"label_8\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>lock orientation:</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"lockOrientationBox\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QWidget\" name=\"configWidget2\" native=\"true\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_6\">\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QLabel\" name=\"label_5\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>record save path:</string>\n              </property>\n              <property name=\"buddy\">\n               <cstring>recordPathEdt</cstring>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QLineEdit\" name=\"recordPathEdt\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"readOnly\">\n               <bool>true</bool>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"selectRecordPathBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>select path</string>\n              </property>\n              <property name=\"autoDefault\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QWidget\" name=\"configWidget4\" native=\"true\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_8\">\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QComboBox\" name=\"gameBox\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"refreshGameScriptBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>refresh script</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"applyScriptBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>apply</string>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QWidget\" name=\"configWidget3\" native=\"true\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <layout class=\"QGridLayout\" name=\"gridLayout\">\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item row=\"0\" column=\"4\">\n             <widget class=\"QCheckBox\" name=\"fpsCheck\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>show fps</string>\n              </property>\n             </widget>\n            </item>\n            <item row=\"0\" column=\"1\">\n             <widget class=\"QCheckBox\" name=\"notDisplayCheck\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>background record</string>\n              </property>\n              <property name=\"checkable\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n            <item row=\"1\" column=\"0\">\n             <widget class=\"QCheckBox\" name=\"alwaysTopCheck\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>always on top</string>\n              </property>\n              <property name=\"checked\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n            <item row=\"0\" column=\"0\">\n             <widget class=\"QCheckBox\" name=\"recordScreenCheck\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>record screen</string>\n              </property>\n             </widget>\n            </item>\n            <item row=\"0\" column=\"3\">\n             <widget class=\"QCheckBox\" name=\"useReverseCheck\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>reverse connection</string>\n              </property>\n              <property name=\"checked\">\n               <bool>true</bool>\n              </property>\n             </widget>\n            </item>\n            <item row=\"1\" column=\"1\">\n             <widget class=\"QCheckBox\" name=\"closeScreenCheck\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>screen-off</string>\n              </property>\n             </widget>\n            </item>\n            <item row=\"1\" column=\"3\">\n             <widget class=\"QCheckBox\" name=\"framelessCheck\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>frameless</string>\n              </property>\n             </widget>\n            </item>\n            <item row=\"1\" column=\"4\">\n             <widget class=\"QCheckBox\" name=\"stayAwakeCheck\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>stay awake</string>\n              </property>\n             </widget>\n            </item>\n            <item row=\"2\" column=\"0\">\n             <widget class=\"QCheckBox\" name=\"showToolbar\">\n              <property name=\"text\">\n               <string>show toolbar</string>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QGroupBox\" name=\"usbGroupBox\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string>USB line</string>\n        </property>\n        <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n         <item>\n          <layout class=\"QHBoxLayout\" name=\"horizontalLayout_10\">\n           <item>\n            <widget class=\"QLabel\" name=\"label_9\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"text\">\n              <string>device name:</string>\n             </property>\n            </widget>\n           </item>\n           <item>\n            <widget class=\"QLineEdit\" name=\"userNameEdt\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"Preferred\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n            </widget>\n           </item>\n           <item>\n            <widget class=\"QPushButton\" name=\"updateNameBtn\">\n             <property name=\"sizePolicy\">\n              <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n               <horstretch>0</horstretch>\n               <verstretch>0</verstretch>\n              </sizepolicy>\n             </property>\n             <property name=\"text\">\n              <string>update name</string>\n             </property>\n             <property name=\"autoDefault\">\n              <bool>false</bool>\n             </property>\n            </widget>\n           </item>\n          </layout>\n         </item>\n         <item>\n          <widget class=\"QWidget\" name=\"usbWidget1\" native=\"true\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QLabel\" name=\"label_2\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>device serial:</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QComboBox\" name=\"serialBox\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"startServerBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>start server</string>\n              </property>\n              <property name=\"autoDefault\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"stopServerBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>stop server</string>\n              </property>\n              <property name=\"autoDefault\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QWidget\" name=\"usbWidget2\" native=\"true\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_4\">\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QPushButton\" name=\"stopAllServerBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>stop all server</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"updateDevice\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>refresh devices</string>\n              </property>\n              <property name=\"autoDefault\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"getIPBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>get device IP</string>\n              </property>\n              <property name=\"autoDefault\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"startAdbdBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>start adbd</string>\n              </property>\n              <property name=\"autoDefault\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QWidget\" name=\"usbWidget3\" native=\"true\">\n           <layout class=\"QHBoxLayout\" name=\"horizontalLayout_12\">\n            <property name=\"leftMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"topMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"rightMargin\">\n             <number>0</number>\n            </property>\n            <property name=\"bottomMargin\">\n             <number>0</number>\n            </property>\n            <item>\n             <widget class=\"QPushButton\" name=\"installSndcpyBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>install sndcpy</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"startAudioBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>start audio</string>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"stopAudioBtn\">\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"text\">\n               <string>stop audio</string>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <widget class=\"QGroupBox\" name=\"wirelessGroupBox\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"title\">\n         <string>Wireless</string>\n        </property>\n        <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n         <property name=\"spacing\">\n          <number>3</number>\n         </property>\n         <property name=\"leftMargin\">\n          <number>5</number>\n         </property>\n         <property name=\"topMargin\">\n          <number>5</number>\n         </property>\n         <property name=\"rightMargin\">\n          <number>5</number>\n         </property>\n         <property name=\"bottomMargin\">\n          <number>5</number>\n         </property>\n         <item>\n          <widget class=\"QComboBox\" name=\"deviceIpEdt\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"MinimumExpanding\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"minimumSize\">\n            <size>\n             <width>200</width>\n             <height>0</height>\n            </size>\n           </property>\n           <property name=\"editable\">\n            <bool>true</bool>\n           </property>\n           <property name=\"currentText\">\n            <string/>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QLabel\" name=\"label\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string notr=\"true\">:</string>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QComboBox\" name=\"devicePortEdt\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Maximum\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"minimumSize\">\n            <size>\n             <width>100</width>\n             <height>0</height>\n            </size>\n           </property>\n           <property name=\"editable\">\n            <bool>true</bool>\n           </property>\n           <property name=\"currentText\">\n            <string/>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QPushButton\" name=\"wirelessConnectBtn\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>wireless connect</string>\n           </property>\n           <property name=\"autoDefault\">\n            <bool>false</bool>\n           </property>\n          </widget>\n         </item>\n         <item>\n          <widget class=\"QPushButton\" name=\"wirelessDisConnectBtn\">\n           <property name=\"sizePolicy\">\n            <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n             <horstretch>0</horstretch>\n             <verstretch>0</verstretch>\n            </sizepolicy>\n           </property>\n           <property name=\"text\">\n            <string>wireless disconnect</string>\n           </property>\n           <property name=\"autoDefault\">\n            <bool>false</bool>\n           </property>\n          </widget>\n         </item>\n        </layout>\n       </widget>\n      </item>\n      <item>\n       <spacer name=\"verticalSpacer\">\n        <property name=\"sizePolicy\">\n         <sizepolicy hsizetype=\"Minimum\" vsizetype=\"Expanding\">\n          <horstretch>0</horstretch>\n          <verstretch>0</verstretch>\n         </sizepolicy>\n        </property>\n        <property name=\"orientation\">\n         <enum>Qt::Vertical</enum>\n        </property>\n        <property name=\"sizeType\">\n         <enum>QSizePolicy::Expanding</enum>\n        </property>\n        <property name=\"sizeHint\" stdset=\"0\">\n         <size>\n          <width>40</width>\n          <height>20</height>\n         </size>\n        </property>\n       </spacer>\n      </item>\n     </layout>\n    </widget>\n   </item>\n  </layout>\n </widget>\n <layoutdefault spacing=\"6\" margin=\"11\"/>\n <tabstops>\n  <tabstop>deviceIpEdt</tabstop>\n  <tabstop>devicePortEdt</tabstop>\n  <tabstop>wirelessConnectBtn</tabstop>\n  <tabstop>wirelessDisConnectBtn</tabstop>\n  <tabstop>adbCommandEdt</tabstop>\n  <tabstop>adbCommandBtn</tabstop>\n  <tabstop>stopAdbBtn</tabstop>\n  <tabstop>clearOut</tabstop>\n </tabstops>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "QtScrcpy/ui/toolform.cpp",
    "content": "#include <QDebug>\n#include <QHideEvent>\n#include <QMouseEvent>\n#include <QShowEvent>\n\n#include \"iconhelper.h\"\n#include \"toolform.h\"\n#include \"ui_toolform.h\"\n#include \"videoform.h\"\n#include \"../groupcontroller/groupcontroller.h\"\n\nToolForm::ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos) : MagneticWidget(adsorbWidget, adsorbPos), ui(new Ui::ToolForm)\n{\n    ui->setupUi(this);\n    setWindowFlags(windowFlags() | Qt::FramelessWindowHint);\n    //setWindowFlags(windowFlags() & ~Qt::WindowMinMaxButtonsHint);\n\n    updateGroupControl();\n\n    initStyle();\n}\n\nToolForm::~ToolForm()\n{\n    delete ui;\n}\n\nvoid ToolForm::setSerial(const QString &serial)\n{\n    m_serial = serial;\n}\n\nbool ToolForm::isHost()\n{\n    return m_isHost;\n}\n\nvoid ToolForm::initStyle()\n{\n    IconHelper::Instance()->SetIcon(ui->fullScreenBtn, QChar(0xf0b2), 15);\n    IconHelper::Instance()->SetIcon(ui->menuBtn, QChar(0xf096), 15);\n    IconHelper::Instance()->SetIcon(ui->homeBtn, QChar(0xf1db), 15);\n    //IconHelper::Instance()->SetIcon(ui->returnBtn, QChar(0xf104), 15);\n    IconHelper::Instance()->SetIcon(ui->returnBtn, QChar(0xf053), 15);\n    IconHelper::Instance()->SetIcon(ui->appSwitchBtn, QChar(0xf24d), 15);\n    IconHelper::Instance()->SetIcon(ui->volumeUpBtn, QChar(0xf028), 15);\n    IconHelper::Instance()->SetIcon(ui->volumeDownBtn, QChar(0xf027), 15);\n    IconHelper::Instance()->SetIcon(ui->openScreenBtn, QChar(0xf06e), 15);\n    IconHelper::Instance()->SetIcon(ui->closeScreenBtn, QChar(0xf070), 15);\n    IconHelper::Instance()->SetIcon(ui->powerBtn, QChar(0xf011), 15);\n    IconHelper::Instance()->SetIcon(ui->expandNotifyBtn, QChar(0xf103), 15);\n    IconHelper::Instance()->SetIcon(ui->screenShotBtn, QChar(0xf0c4), 15);\n    IconHelper::Instance()->SetIcon(ui->touchBtn, QChar(0xf111), 15);\n    IconHelper::Instance()->SetIcon(ui->groupControlBtn, QChar(0xf0c0), 15);\n    IconHelper::Instance()->SetIcon(ui->clipboardBtn, QChar(0xf0c5), 15);\n}\n\nvoid ToolForm::updateGroupControl()\n{\n    if (m_isHost) {\n        ui->groupControlBtn->setStyleSheet(\"color: red\");\n    } else {\n        ui->groupControlBtn->setStyleSheet(\"color: green\");\n    }\n\n    GroupController::instance().updateDeviceState(m_serial);\n}\n\nvoid ToolForm::mousePressEvent(QMouseEvent *event)\n{\n    if (event->button() == Qt::LeftButton) {\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n        m_dragPosition = event->globalPos() - frameGeometry().topLeft();\n#else\n        m_dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();\n#endif\n        event->accept();\n    }\n}\n\nvoid ToolForm::mouseReleaseEvent(QMouseEvent *event)\n{\n    Q_UNUSED(event)\n}\n\nvoid ToolForm::mouseMoveEvent(QMouseEvent *event)\n{\n    if (event->buttons() & Qt::LeftButton) {\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n        move(event->globalPos() - m_dragPosition);\n#else\n        move(event->globalPosition().toPoint() - m_dragPosition);\n#endif\n        event->accept();\n    }\n}\n\nvoid ToolForm::showEvent(QShowEvent *event)\n{\n    Q_UNUSED(event)\n    qDebug() << \"show event\";\n}\n\nvoid ToolForm::hideEvent(QHideEvent *event)\n{\n    Q_UNUSED(event)\n    qDebug() << \"hide event\";\n}\n\nvoid ToolForm::on_fullScreenBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n\n    dynamic_cast<VideoForm*>(parent())->switchFullScreen();\n}\n\nvoid ToolForm::on_returnBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->postGoBack();\n}\n\nvoid ToolForm::on_homeBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->postGoHome();\n}\n\nvoid ToolForm::on_menuBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->postGoMenu();\n}\n\nvoid ToolForm::on_appSwitchBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->postAppSwitch();\n}\n\nvoid ToolForm::on_powerBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->postPower();\n}\n\nvoid ToolForm::on_screenShotBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->screenshot();\n}\n\nvoid ToolForm::on_volumeUpBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->postVolumeUp();\n}\n\nvoid ToolForm::on_volumeDownBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->postVolumeDown();\n}\n\nvoid ToolForm::on_closeScreenBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->setDisplayPower(false);\n}\n\nvoid ToolForm::on_expandNotifyBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->expandNotificationPanel();\n}\n\nvoid ToolForm::on_touchBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n\n    m_showTouch = !m_showTouch;\n    device->showTouch(m_showTouch);\n}\n\nvoid ToolForm::on_groupControlBtn_clicked()\n{\n    m_isHost = !m_isHost;\n    updateGroupControl();\n}\n\nvoid ToolForm::on_openScreenBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->setDisplayPower(true);\n}\n\nvoid ToolForm::on_clipboardBtn_clicked()\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    device->requestDeviceClipboard();\n}"
  },
  {
    "path": "QtScrcpy/ui/toolform.h",
    "content": "#ifndef TOOLFORM_H\n#define TOOLFORM_H\n\n#include <QPointer>\n#include <QWidget>\n\n#include \"../QtScrcpyCore/include/QtScrcpyCore.h\"\n#include \"magneticwidget.h\"\n\nnamespace Ui\n{\n    class ToolForm;\n}\n\nclass Device;\nclass ToolForm : public MagneticWidget\n{\n    Q_OBJECT\n\npublic:\n    explicit ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos);\n    ~ToolForm();\n\n    void setSerial(const QString& serial);\n    bool isHost();\n\nprotected:\n    void mousePressEvent(QMouseEvent *event);\n    void mouseReleaseEvent(QMouseEvent *event);\n    void mouseMoveEvent(QMouseEvent *event);\n\n    void showEvent(QShowEvent *event);\n    void hideEvent(QHideEvent *event);\n\nprivate slots:\n    void on_fullScreenBtn_clicked();\n    void on_returnBtn_clicked();\n    void on_homeBtn_clicked();\n    void on_menuBtn_clicked();\n    void on_appSwitchBtn_clicked();\n    void on_powerBtn_clicked();\n    void on_screenShotBtn_clicked();\n    void on_volumeUpBtn_clicked();\n    void on_volumeDownBtn_clicked();\n    void on_closeScreenBtn_clicked();\n    void on_expandNotifyBtn_clicked();\n    void on_touchBtn_clicked();\n    void on_groupControlBtn_clicked();\n    void on_openScreenBtn_clicked();\n    void on_clipboardBtn_clicked();\n\nprivate:\n    void initStyle();\n    void updateGroupControl();\n\nprivate:\n    Ui::ToolForm *ui;\n    QPoint m_dragPosition;\n    QString m_serial;\n    bool m_showTouch = false;\n    bool m_isHost = false;\n};\n\n#endif // TOOLFORM_H\n"
  },
  {
    "path": "QtScrcpy/ui/toolform.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ToolForm</class>\n <widget class=\"QWidget\" name=\"ToolForm\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>63</width>\n    <height>537</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Tool</string>\n  </property>\n  <property name=\"styleSheet\">\n   <string notr=\"true\"/>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"topMargin\">\n    <number>30</number>\n   </property>\n   <item>\n    <widget class=\"QPushButton\" name=\"groupControlBtn\">\n     <property name=\"toolTip\">\n      <string>group control</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"fullScreenBtn\">\n     <property name=\"toolTip\">\n      <string>full screen</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <spacer name=\"verticalSpacer\">\n     <property name=\"orientation\">\n      <enum>Qt::Vertical</enum>\n     </property>\n     <property name=\"sizeHint\" stdset=\"0\">\n      <size>\n       <width>20</width>\n       <height>40</height>\n      </size>\n     </property>\n    </spacer>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"expandNotifyBtn\">\n     <property name=\"toolTip\">\n      <string>expand notify</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"touchBtn\">\n     <property name=\"toolTip\">\n      <string>touch switch</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"openScreenBtn\">\n     <property name=\"toolTip\">\n      <string>open screen</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"closeScreenBtn\">\n     <property name=\"toolTip\">\n      <string>close screen</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"powerBtn\">\n     <property name=\"toolTip\">\n      <string>power</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"volumeUpBtn\">\n     <property name=\"toolTip\">\n      <string>volume up</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"volumeDownBtn\">\n     <property name=\"toolTip\">\n      <string>volume down</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"appSwitchBtn\">\n     <property name=\"toolTip\">\n      <string>app switch</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"menuBtn\">\n     <property name=\"toolTip\">\n      <string>menu</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"homeBtn\">\n     <property name=\"toolTip\">\n      <string>home</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"returnBtn\">\n     <property name=\"toolTip\">\n      <string>return</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"screenShotBtn\">\n     <property name=\"toolTip\">\n      <string>screen shot</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QPushButton\" name=\"clipboardBtn\">\n     <property name=\"toolTip\">\n      <string>copy clipboard text</string>\n     </property>\n     <property name=\"text\">\n      <string/>\n     </property>\n    </widget>\n    </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "QtScrcpy/ui/videoform.cpp",
    "content": "// #include <QDesktopWidget>\n#include <QFileInfo>\n#include <QLabel>\n#include <QMessageBox>\n#include <QMimeData>\n#include <QMouseEvent>\n#include <QPainter>\n#include <QScreen>\n#include <QShortcut>\n#include <QStyle>\n#include <QStyleOption>\n#include <QTimer>\n#include <QWindow>\n#include <QtWidgets/QHBoxLayout>\n\n#if defined(Q_OS_WIN32)\n#include <Windows.h>\n#endif\n\n#include \"config.h\"\n#include \"iconhelper.h\"\n#include \"qyuvopenglwidget.h\"\n#include \"toolform.h\"\n#include \"mousetap/mousetap.h\"\n#include \"ui_videoform.h\"\n#include \"videoform.h\"\n\nVideoForm::VideoForm(bool framelessWindow, bool skin, bool showToolbar, QWidget *parent) : QWidget(parent), ui(new Ui::videoForm), m_skin(skin)\n{\n    ui->setupUi(this);\n    initUI();\n    installShortcut();\n    updateShowSize(size());\n    bool vertical = size().height() > size().width();\n    this->show_toolbar = showToolbar;\n    if (m_skin) {\n        updateStyleSheet(vertical);\n    }\n    if (framelessWindow) {\n        setWindowFlags(windowFlags() | Qt::FramelessWindowHint);\n    }\n}\n\nVideoForm::~VideoForm()\n{\n    delete ui;\n}\n\nvoid VideoForm::initUI()\n{\n    if (m_skin) {\n        QPixmap phone;\n        if (phone.load(\":/res/phone.png\")) {\n            m_widthHeightRatio = 1.0f * phone.width() / phone.height();\n        }\n\n#ifndef Q_OS_OSX\n        // mac下去掉标题栏影响showfullscreen\n        // 去掉标题栏\n        setWindowFlags(windowFlags() | Qt::FramelessWindowHint);\n        // 根据图片构造异形窗口\n        setAttribute(Qt::WA_TranslucentBackground);\n#endif\n    }\n\n    m_videoWidget = new QYUVOpenGLWidget();\n    m_videoWidget->hide();\n    ui->keepRatioWidget->setWidget(m_videoWidget);\n    ui->keepRatioWidget->setWidthHeightRatio(m_widthHeightRatio);\n\n    m_fpsLabel = new QLabel(m_videoWidget);\n    QFont ft;\n    ft.setPointSize(15);\n    ft.setWeight(QFont::Light);\n    ft.setBold(true);\n    m_fpsLabel->setFont(ft);\n    m_fpsLabel->move(5, 15);\n    m_fpsLabel->setMinimumWidth(100);\n    m_fpsLabel->setStyleSheet(R\"(QLabel {color: #00FF00;})\");\n\n    setMouseTracking(true);\n    m_videoWidget->setMouseTracking(true);\n    ui->keepRatioWidget->setMouseTracking(true);\n}\n\nQRect VideoForm::getGrabCursorRect()\n{\n    QRect rc;\n#if defined(Q_OS_WIN32)\n    rc = QRect(ui->keepRatioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());\n    // high dpi support\n    rc.setTopLeft(rc.topLeft() * m_videoWidget->devicePixelRatioF());\n    rc.setBottomRight(rc.bottomRight() * m_videoWidget->devicePixelRatioF());\n\n    rc.setX(rc.x() + 10);\n    rc.setY(rc.y() + 10);\n    rc.setWidth(rc.width() - 20);\n    rc.setHeight(rc.height() - 20);\n#elif defined(Q_OS_OSX)\n    rc = m_videoWidget->geometry();\n    rc.setTopLeft(ui->keepRatioWidget->mapToGlobal(rc.topLeft()));\n    rc.setBottomRight(ui->keepRatioWidget->mapToGlobal(rc.bottomRight()));\n\n    rc.setX(rc.x() + 10);\n    rc.setY(rc.y() + 10);\n    rc.setWidth(rc.width() - 20);\n    rc.setHeight(rc.height() - 20);\n#elif defined(Q_OS_LINUX)\n    rc = QRect(ui->keepRatioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());\n    // high dpi support -- taken from the WIN32 section and untested\n    rc.setTopLeft(rc.topLeft() * m_videoWidget->devicePixelRatioF());\n    rc.setBottomRight(rc.bottomRight() * m_videoWidget->devicePixelRatioF());\n\n    rc.setX(rc.x() + 10);\n    rc.setY(rc.y() + 10);\n    rc.setWidth(rc.width() - 20);\n    rc.setHeight(rc.height() - 20);\n#endif\n    return rc;\n}\n\nconst QSize &VideoForm::frameSize()\n{\n    return m_frameSize;\n}\n\nvoid VideoForm::resizeSquare()\n{\n    QRect screenRect = getScreenRect();\n    if (screenRect.isEmpty()) {\n        qWarning() << \"getScreenRect is empty\";\n        return;\n    }\n    resize(screenRect.height(), screenRect.height());\n}\n\nvoid VideoForm::removeBlackRect()\n{\n    resize(ui->keepRatioWidget->goodSize());\n}\n\nvoid VideoForm::showFPS(bool show)\n{\n    if (!m_fpsLabel) {\n        return;\n    }\n    m_fpsLabel->setVisible(show);\n}\n\nvoid VideoForm::updateRender(int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV, int linesizeY, int linesizeU, int linesizeV)\n{\n    if (m_videoWidget->isHidden()) {\n        if (m_loadingWidget) {\n            m_loadingWidget->close();\n        }\n        m_videoWidget->show();\n    }\n\n    updateShowSize(QSize(width, height));\n    m_videoWidget->setFrameSize(QSize(width, height));\n    m_videoWidget->updateTextures(dataY, dataU, dataV, linesizeY, linesizeU, linesizeV);\n}\n\nvoid VideoForm::setSerial(const QString &serial)\n{\n    m_serial = serial;\n}\n\nvoid VideoForm::showToolForm(bool show)\n{\n    if (!m_toolForm) {\n        m_toolForm = new ToolForm(this, ToolForm::AP_OUTSIDE_RIGHT);\n        m_toolForm->setSerial(m_serial);\n    }\n    m_toolForm->move(pos().x() + geometry().width(), pos().y() + 30);\n    m_toolForm->setVisible(show);\n}\n\nvoid VideoForm::moveCenter()\n{\n    QRect screenRect = getScreenRect();\n    if (screenRect.isEmpty()) {\n        qWarning() << \"getScreenRect is empty\";\n        return;\n    }\n    // 窗口居中\n    move(screenRect.center() - QRect(0, 0, size().width(), size().height()).center());\n}\n\nvoid VideoForm::installShortcut()\n{\n    QShortcut *shortcut = nullptr;\n\n    // switchFullScreen\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+f\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        switchFullScreen();\n    });\n\n    // resizeSquare\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+g\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() { resizeSquare(); });\n\n    // removeBlackRect\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+w\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() { removeBlackRect(); });\n\n    // postGoHome\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+h\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        device->postGoHome();\n    });\n\n    // postGoBack\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+b\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        device->postGoBack();\n    });\n\n    // postAppSwitch\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+s\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->postAppSwitch();\n    });\n\n    // postGoMenu\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+m\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        device->postGoMenu();\n    });\n\n    // postVolumeUp\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+up\"), this);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->postVolumeUp();\n    });\n\n    // postVolumeDown\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+down\"), this);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->postVolumeDown();\n    });\n\n    // postPower\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+p\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->postPower();\n    });\n\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+o\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->setDisplayPower(false);\n    });\n\n    // expandNotificationPanel\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+n\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->expandNotificationPanel();\n    });\n\n    // collapsePanel\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+Shift+n\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->collapsePanel();\n    });\n\n    // copy\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+c\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->postCopy();\n    });\n\n    // cut\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+x\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->postCut();\n    });\n\n    // clipboardPaste\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+v\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->setDeviceClipboard();\n    });\n\n    // setDeviceClipboard\n    shortcut = new QShortcut(QKeySequence(\"Ctrl+Shift+v\"), this);\n    shortcut->setAutoRepeat(false);\n    connect(shortcut, &QShortcut::activated, this, [this]() {\n        auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n        if (!device) {\n            return;\n        }\n        emit device->clipboardPaste();\n    });\n}\n\nQRect VideoForm::getScreenRect()\n{\n    QRect screenRect;\n    QScreen *screen = QGuiApplication::primaryScreen();\n    QWidget *win = window();\n    if (win) {\n        QWindow *winHandle = win->windowHandle();\n        if (winHandle) {\n            screen = winHandle->screen();\n        }\n    }\n\n    if (screen) {\n        screenRect = screen->availableGeometry();\n    }\n    return screenRect;\n}\n\nvoid VideoForm::updateStyleSheet(bool vertical)\n{\n    if (vertical) {\n        setStyleSheet(R\"(\n                 #videoForm {\n                     border-image: url(:/image/videoform/phone-v.png) 150px 65px 85px 65px;\n                     border-width: 150px 65px 85px 65px;\n                 }\n                 )\");\n    } else {\n        setStyleSheet(R\"(\n                 #videoForm {\n                     border-image: url(:/image/videoform/phone-h.png) 65px 85px 65px 150px;\n                     border-width: 65px 85px 65px 150px;\n                 }\n                 )\");\n    }\n    layout()->setContentsMargins(getMargins(vertical));\n}\n\nQMargins VideoForm::getMargins(bool vertical)\n{\n    QMargins margins;\n    if (vertical) {\n        margins = QMargins(10, 68, 12, 62);\n    } else {\n        margins = QMargins(68, 12, 62, 10);\n    }\n    return margins;\n}\n\nvoid VideoForm::updateShowSize(const QSize &newSize)\n{\n    if (m_frameSize != newSize) {\n        m_frameSize = newSize;\n\n        m_widthHeightRatio = 1.0f * newSize.width() / newSize.height();\n        ui->keepRatioWidget->setWidthHeightRatio(m_widthHeightRatio);\n\n        bool vertical = m_widthHeightRatio < 1.0f ? true : false;\n        QSize showSize = newSize;\n        QRect screenRect = getScreenRect();\n        if (screenRect.isEmpty()) {\n            qWarning() << \"getScreenRect is empty\";\n            return;\n        }\n        if (vertical) {\n            showSize.setHeight(qMin(newSize.height(), screenRect.height() - 200));\n            showSize.setWidth(showSize.height() * m_widthHeightRatio);\n        } else {\n            showSize.setWidth(qMin(newSize.width(), screenRect.width() / 2));\n            showSize.setHeight(showSize.width() / m_widthHeightRatio);\n        }\n\n        if (isFullScreen() && qsc::IDeviceManage::getInstance().getDevice(m_serial)) {\n            switchFullScreen();\n        }\n\n        if (isMaximized()) {\n            showNormal();\n        }\n\n        if (m_skin) {\n            QMargins m = getMargins(vertical);\n            showSize.setWidth(showSize.width() + m.left() + m.right());\n            showSize.setHeight(showSize.height() + m.top() + m.bottom());\n        }\n\n        if (showSize != size()) {\n            resize(showSize);\n            if (m_skin) {\n                updateStyleSheet(vertical);\n            }\n            moveCenter();\n        }\n    }\n}\n\nvoid VideoForm::switchFullScreen()\n{\n    if (isFullScreen()) {\n        // 横屏全屏铺满全屏，恢复时，恢复保持宽高比\n        if (m_widthHeightRatio > 1.0f) {\n            ui->keepRatioWidget->setWidthHeightRatio(m_widthHeightRatio);\n        }\n\n        showNormal();\n        // back to normal size.\n        resize(m_normalSize);\n        // fullscreen window will move (0,0). qt bug?\n        move(m_fullScreenBeforePos);\n\n#ifdef Q_OS_OSX\n        //setWindowFlags(windowFlags() | Qt::FramelessWindowHint);\n        //show();\n#endif\n        if (m_skin) {\n            updateStyleSheet(m_frameSize.height() > m_frameSize.width());\n        }\n        showToolForm(this->show_toolbar);\n#ifdef Q_OS_WIN32\n        ::SetThreadExecutionState(ES_CONTINUOUS);\n#endif\n    } else {\n        // 横屏全屏铺满全屏，不保持宽高比\n        if (m_widthHeightRatio > 1.0f) {\n            ui->keepRatioWidget->setWidthHeightRatio(-1.0f);\n        }\n\n        // record current size before fullscreen, it will be used to rollback size after exit fullscreen.\n        m_normalSize = size();\n\n        m_fullScreenBeforePos = pos();\n        // 这种临时增加标题栏再全屏的方案会导致收不到mousemove事件，导致setmousetrack失效\n        // mac fullscreen must show title bar\n#ifdef Q_OS_OSX\n        //setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);\n#endif\n        showToolForm(false);\n        if (m_skin) {\n            layout()->setContentsMargins(0, 0, 0, 0);\n        }\n        showFullScreen();\n\n        // 全屏状态禁止电脑休眠、息屏\n#ifdef Q_OS_WIN32\n        ::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);\n#endif\n    }\n}\n\nbool VideoForm::isHost()\n{\n    if (!m_toolForm) {\n        return false;\n    }\n    return m_toolForm->isHost();\n}\n\nvoid VideoForm::updateFPS(quint32 fps)\n{\n    //qDebug() << \"FPS:\" << fps;\n    if (!m_fpsLabel) {\n        return;\n    }\n    m_fpsLabel->setText(QString(\"FPS:%1\").arg(fps));\n}\n\nvoid VideoForm::grabCursor(bool grab)\n{\n    QRect rc = getGrabCursorRect();\n    MouseTap::getInstance()->enableMouseEventTap(rc, grab);\n}\n\nvoid VideoForm::onFrame(int width, int height, uint8_t *dataY, uint8_t *dataU, uint8_t *dataV, int linesizeY, int linesizeU, int linesizeV)\n{\n    updateRender(width, height, dataY, dataU, dataV, linesizeY, linesizeU, linesizeV);\n}\n\nvoid VideoForm::staysOnTop(bool top)\n{\n    bool needShow = false;\n    if (isVisible()) {\n        needShow = true;\n    }\n    setWindowFlag(Qt::WindowStaysOnTopHint, top);\n    if (m_toolForm) {\n        m_toolForm->setWindowFlag(Qt::WindowStaysOnTopHint, top);\n    }\n    if (needShow) {\n        show();\n    }\n}\n\nvoid VideoForm::mousePressEvent(QMouseEvent *event)\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (event->button() == Qt::MiddleButton) {\n        if (device && !device->isCurrentCustomKeymap()) {\n            device->postGoHome();\n            return;\n        }\n    }\n\n    if (event->button() == Qt::RightButton) {\n        if (device && !device->isCurrentCustomKeymap()) {\n            device->postGoBack();\n            return;\n        }\n    }\n\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n        QPointF localPos = event->localPos();\n        QPointF globalPos = event->globalPos();\n#else\n        QPointF localPos = event->position();\n        QPointF globalPos = event->globalPosition();\n#endif\n\n    if (m_videoWidget->geometry().contains(event->pos())) {\n        if (!device) {\n            return;\n        }\n        QPointF mappedPos = m_videoWidget->mapFrom(this, localPos.toPoint());\n        QMouseEvent newEvent(event->type(), mappedPos, globalPos, event->button(), event->buttons(), event->modifiers());\n        emit device->mouseEvent(&newEvent, m_videoWidget->frameSize(), m_videoWidget->size());\n\n        // debug keymap pos\n        if (event->button() == Qt::LeftButton) {\n            qreal x = localPos.x() / m_videoWidget->size().width();\n            qreal y = localPos.y() / m_videoWidget->size().height();\n            QString posTip = QString(R\"(\"pos\": {\"x\": %1, \"y\": %2})\").arg(x).arg(y);\n            qInfo() << posTip.toStdString().c_str();\n        }\n    } else {\n        if (event->button() == Qt::LeftButton) {\n            m_dragPosition = globalPos.toPoint() - frameGeometry().topLeft();\n            event->accept();\n        }\n    }\n}\n\nvoid VideoForm::mouseReleaseEvent(QMouseEvent *event)\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (m_dragPosition.isNull()) {\n        if (!device) {\n            return;\n        }\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n        QPointF localPos = event->localPos();\n        QPointF globalPos = event->globalPos();\n#else\n        QPointF localPos = event->position();\n        QPointF globalPos = event->globalPosition();\n#endif\n        // local check\n        QPointF local = m_videoWidget->mapFrom(this, localPos.toPoint());\n        if (local.x() < 0) {\n            local.setX(0);\n        }\n        if (local.x() > m_videoWidget->width()) {\n            local.setX(m_videoWidget->width());\n        }\n        if (local.y() < 0) {\n            local.setY(0);\n        }\n        if (local.y() > m_videoWidget->height()) {\n            local.setY(m_videoWidget->height());\n        }\n        QMouseEvent newEvent(event->type(), local, globalPos, event->button(), event->buttons(), event->modifiers());\n        emit device->mouseEvent(&newEvent, m_videoWidget->frameSize(), m_videoWidget->size());\n    } else {\n        m_dragPosition = QPoint(0, 0);\n    }\n}\n\nvoid VideoForm::mouseMoveEvent(QMouseEvent *event)\n{\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n        QPointF localPos = event->localPos();\n        QPointF globalPos = event->globalPos();\n#else\n        QPointF localPos = event->position();\n        QPointF globalPos = event->globalPosition();\n#endif\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (m_videoWidget->geometry().contains(event->pos())) {\n        if (!device) {\n            return;\n        }\n        QPointF mappedPos = m_videoWidget->mapFrom(this, localPos.toPoint());\n        QMouseEvent newEvent(event->type(), mappedPos, globalPos, event->button(), event->buttons(), event->modifiers());\n        emit device->mouseEvent(&newEvent, m_videoWidget->frameSize(), m_videoWidget->size());\n    } else if (!m_dragPosition.isNull()) {\n        if (event->buttons() & Qt::LeftButton) {\n            move(globalPos.toPoint() - m_dragPosition);\n            event->accept();\n        }\n    }\n}\n\nvoid VideoForm::mouseDoubleClickEvent(QMouseEvent *event)\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (event->button() == Qt::LeftButton && !m_videoWidget->geometry().contains(event->pos())) {\n        if (!isMaximized()) {\n            removeBlackRect();\n        }\n    }\n\n    if (event->button() == Qt::RightButton && device && !device->isCurrentCustomKeymap()) {\n        emit device->postBackOrScreenOn(event->type() == QEvent::MouseButtonPress);\n    }\n\n    if (m_videoWidget->geometry().contains(event->pos())) {\n        if (!device) {\n            return;\n        }\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n        QPointF localPos = event->localPos();\n        QPointF globalPos = event->globalPos();\n#else\n        QPointF localPos = event->position();\n        QPointF globalPos = event->globalPosition();\n#endif\n        QPointF mappedPos = m_videoWidget->mapFrom(this, localPos.toPoint());\n        QMouseEvent newEvent(event->type(), mappedPos, globalPos, event->button(), event->buttons(), event->modifiers());\n        emit device->mouseEvent(&newEvent, m_videoWidget->frameSize(), m_videoWidget->size());\n    }\n}\n\nvoid VideoForm::wheelEvent(QWheelEvent *event)\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)\n    if (m_videoWidget->geometry().contains(event->position().toPoint())) {\n        if (!device) {\n            return;\n        }\n        QPointF pos = m_videoWidget->mapFrom(this, event->position().toPoint());\n        QWheelEvent wheelEvent(\n            pos, event->globalPosition(), event->pixelDelta(), event->angleDelta(), event->buttons(), event->modifiers(), event->phase(), event->inverted());\n#else\n    if (m_videoWidget->geometry().contains(event->pos())) {\n        if (!device) {\n            return;\n        }\n        QPointF pos = m_videoWidget->mapFrom(this, event->pos());\n\n        QWheelEvent wheelEvent(\n            pos, event->globalPosF(), event->pixelDelta(), event->angleDelta(), event->delta(), event->orientation(),\n            event->buttons(), event->modifiers(), event->phase(), event->source(), event->inverted());\n#endif\n        emit device->wheelEvent(&wheelEvent, m_videoWidget->frameSize(), m_videoWidget->size());\n    }\n}\n\nvoid VideoForm::keyPressEvent(QKeyEvent *event)\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    if (Qt::Key_Escape == event->key() && !event->isAutoRepeat() && isFullScreen()) {\n        switchFullScreen();\n    }\n\n    emit device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());\n}\n\nvoid VideoForm::keyReleaseEvent(QKeyEvent *event)\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    emit device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());\n}\n\nvoid VideoForm::paintEvent(QPaintEvent *paint)\n{\n    Q_UNUSED(paint)\n    QStyleOption opt;\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n    opt.init(this);\n#else\n    opt.initFrom(this);\n#endif\n    QPainter p(this);\n    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);\n}\n\nvoid VideoForm::showEvent(QShowEvent *event)\n{\n    Q_UNUSED(event)\n    if (!isFullScreen() && this->show_toolbar) {\n        QTimer::singleShot(500, this, [this](){\n            showToolForm(this->show_toolbar);\n        });\n    }\n}\n\nvoid VideoForm::resizeEvent(QResizeEvent *event)\n{\n    Q_UNUSED(event)\n    QSize goodSize = ui->keepRatioWidget->goodSize();\n    if (goodSize.isEmpty()) {\n        return;\n    }\n    QSize curSize = size();\n    // 限制VideoForm尺寸不能小于keepRatioWidget good size\n    if (m_widthHeightRatio > 1.0f) {\n        // hor\n        if (curSize.height() <= goodSize.height()) {\n            setMinimumHeight(goodSize.height());\n        } else {\n            setMinimumHeight(0);\n        }\n    } else {\n        // ver\n        if (curSize.width() <= goodSize.width()) {\n            setMinimumWidth(goodSize.width());\n        } else {\n            setMinimumWidth(0);\n        }\n    }\n}\n\nvoid VideoForm::closeEvent(QCloseEvent *event)\n{\n    Q_UNUSED(event)\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    Config::getInstance().setRect(device->getSerial(), geometry());\n    device->disconnectDevice();\n}\n\nvoid VideoForm::dragEnterEvent(QDragEnterEvent *event)\n{\n    event->acceptProposedAction();\n}\n\nvoid VideoForm::dragMoveEvent(QDragMoveEvent *event)\n{\n    Q_UNUSED(event)\n}\n\nvoid VideoForm::dragLeaveEvent(QDragLeaveEvent *event)\n{\n    Q_UNUSED(event)\n}\n\nvoid VideoForm::dropEvent(QDropEvent *event)\n{\n    auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);\n    if (!device) {\n        return;\n    }\n    const QMimeData *qm = event->mimeData();\n    QList<QUrl> urls = qm->urls();\n\n    for (const QUrl &url : urls) {\n        QString file = url.toLocalFile();\n        QFileInfo fileInfo(file);\n\n        if (!fileInfo.exists()) {\n            QMessageBox::warning(this, \"QtScrcpy\", tr(\"file does not exist\"), QMessageBox::Ok);\n            continue;\n        }\n\n        if (fileInfo.isFile() && fileInfo.suffix() == \"apk\") {\n            emit device->installApkRequest(file);\n            continue;\n        }\n        emit device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());\n    }\n}\n"
  },
  {
    "path": "QtScrcpy/ui/videoform.h",
    "content": "#ifndef VIDEOFORM_H\n#define VIDEOFORM_H\n\n#include <QPointer>\n#include <QWidget>\n\n#include \"../QtScrcpyCore/include/QtScrcpyCore.h\"\n\nnamespace Ui\n{\n    class videoForm;\n}\n\nclass ToolForm;\nclass FileHandler;\nclass QYUVOpenGLWidget;\nclass QLabel;\nclass VideoForm : public QWidget, public qsc::DeviceObserver\n{\n    Q_OBJECT\npublic:\n    explicit VideoForm(bool framelessWindow = false, bool skin = true, bool showToolBar = true, QWidget *parent = 0);\n    ~VideoForm();\n\n    void staysOnTop(bool top = true);\n    void updateShowSize(const QSize &newSize);\n    void updateRender(int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV, int linesizeY, int linesizeU, int linesizeV);\n    void setSerial(const QString& serial);\n    QRect getGrabCursorRect();\n    const QSize &frameSize();\n    void resizeSquare();\n    void removeBlackRect();\n    void showFPS(bool show);\n    void switchFullScreen();\n    bool isHost();\n\nprivate:\n    void onFrame(int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV,\n                 int linesizeY, int linesizeU, int linesizeV) override;\n    void updateFPS(quint32 fps) override;\n    void grabCursor(bool grab) override;\n\n    void updateStyleSheet(bool vertical);\n    QMargins getMargins(bool vertical);\n    void initUI();\n\n    void showToolForm(bool show = true);\n    void moveCenter();\n    void installShortcut();\n    QRect getScreenRect();\n\nprotected:\n    void mousePressEvent(QMouseEvent *event) override;\n    void mouseReleaseEvent(QMouseEvent *event) override;\n    void mouseMoveEvent(QMouseEvent *event) override;\n    void mouseDoubleClickEvent(QMouseEvent *event) override;\n    void wheelEvent(QWheelEvent *event) override;\n    void keyPressEvent(QKeyEvent *event) override;\n    void keyReleaseEvent(QKeyEvent *event) override;\n\n    void paintEvent(QPaintEvent *) override;\n    void showEvent(QShowEvent *event) override;\n    void resizeEvent(QResizeEvent *event) override;\n    void closeEvent(QCloseEvent *event) override;\n\n    void dragEnterEvent(QDragEnterEvent *event) override;\n    void dragMoveEvent(QDragMoveEvent *event) override;\n    void dragLeaveEvent(QDragLeaveEvent *event) override;\n    void dropEvent(QDropEvent *event) override;\n\nprivate:\n    // ui\n    Ui::videoForm *ui;\n    QPointer<ToolForm> m_toolForm;\n    QPointer<QWidget> m_loadingWidget;\n    QPointer<QYUVOpenGLWidget> m_videoWidget;\n    QPointer<QLabel> m_fpsLabel;\n\n    //inside member\n    QSize m_frameSize;\n    QSize m_normalSize;\n    QPoint m_dragPosition;\n    float m_widthHeightRatio = 0.5f;\n    bool m_skin = true;\n    QPoint m_fullScreenBeforePos;\n    QString m_serial;\n\n    //Whether to display the toolbar when connecting a device.\n    bool show_toolbar = true;\n};\n\n#endif // VIDEOFORM_H\n"
  },
  {
    "path": "QtScrcpy/ui/videoform.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>videoForm</class>\n <widget class=\"QWidget\" name=\"videoForm\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>400</width>\n    <height>800</height>\n   </rect>\n  </property>\n  <property name=\"acceptDrops\">\n   <bool>true</bool>\n  </property>\n  <property name=\"windowTitle\">\n   <string/>\n  </property>\n  <property name=\"styleSheet\">\n   <string notr=\"true\">#videoForm {\n\tborder-image: url(:/res/phone-v.png) 150px 142px 85px 142px;\n\tborder-width: 150px 142px 85px 142px;\n}</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <property name=\"spacing\">\n    <number>0</number>\n   </property>\n   <property name=\"leftMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"topMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"rightMargin\">\n    <number>0</number>\n   </property>\n   <property name=\"bottomMargin\">\n    <number>0</number>\n   </property>\n   <item>\n    <widget class=\"KeepRatioWidget\" name=\"keepRatioWidget\" native=\"true\"/>\n   </item>\n  </layout>\n </widget>\n <customwidgets>\n  <customwidget>\n   <class>KeepRatioWidget</class>\n   <extends>QWidget</extends>\n   <header location=\"global\">keepratiowidget.h</header>\n   <container>1</container>\n  </customwidget>\n </customwidgets>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "QtScrcpy/uibase/keepratiowidget.cpp",
    "content": "#include <QResizeEvent>\n#include <cmath>\n\n#include \"keepratiowidget.h\"\n\nKeepRatioWidget::KeepRatioWidget(QWidget *parent) : QWidget(parent) {}\n\nKeepRatioWidget::~KeepRatioWidget() {}\n\nvoid KeepRatioWidget::setWidget(QWidget *w)\n{\n    if (!w) {\n        return;\n    }\n    w->setParent(this);\n    m_subWidget = w;\n}\n\nvoid KeepRatioWidget::setWidthHeightRatio(float widthHeightRatio)\n{\n    if (fabs(m_widthHeightRatio - widthHeightRatio) < 0.000001f) {\n        return;\n    }\n    m_widthHeightRatio = widthHeightRatio;\n    adjustSubWidget();\n}\n\nconst QSize KeepRatioWidget::goodSize()\n{\n    if (!m_subWidget || m_widthHeightRatio < 0.0f) {\n        return QSize();\n    }\n    return m_subWidget->size();\n}\n\nvoid KeepRatioWidget::resizeEvent(QResizeEvent *event)\n{\n    Q_UNUSED(event)\n    adjustSubWidget();\n}\n\nvoid KeepRatioWidget::adjustSubWidget()\n{\n    if (!m_subWidget) {\n        return;\n    }\n\n    QSize curSize = size();\n    QPoint pos(0, 0);\n    int width = 0;\n    int height = 0;\n    if (m_widthHeightRatio > 1.0f) {\n        // base width\n        width = curSize.width();\n        height = curSize.width() / m_widthHeightRatio;\n        pos.setY((curSize.height() - height) / 2);\n    } else if (m_widthHeightRatio > 0.0f) {\n        // base height\n        height = curSize.height();\n        width = curSize.height() * m_widthHeightRatio;\n        pos.setX((curSize.width() - width) / 2);\n    } else {\n        // full widget\n        height = curSize.height();\n        width = curSize.width();\n    }\n    m_subWidget->setGeometry(pos.x(), pos.y(), width, height);\n}\n"
  },
  {
    "path": "QtScrcpy/uibase/keepratiowidget.h",
    "content": "#ifndef KEEPRATIOWIDGET_H\n#define KEEPRATIOWIDGET_H\n\n#include <QPointer>\n#include <QWidget>\n\nclass KeepRatioWidget : public QWidget\n{\n    Q_OBJECT\npublic:\n    explicit KeepRatioWidget(QWidget *parent = nullptr);\n    ~KeepRatioWidget();\n\n    void setWidget(QWidget *w);\n    void setWidthHeightRatio(float widthHeightRatio);\n    const QSize goodSize();\n\nprotected:\n    void resizeEvent(QResizeEvent *event);\n    void adjustSubWidget();\n\nprivate:\n    float m_widthHeightRatio = -1.0f;\n    QPointer<QWidget> m_subWidget;\n    QSize m_goodSize;\n};\n\n#endif // KEEPRATIOWIDGET_H\n"
  },
  {
    "path": "QtScrcpy/uibase/magneticwidget.cpp",
    "content": "#include <QDebug>\n#include <QMoveEvent>\n#include <QStyle>\n\n#include \"magneticwidget.h\"\n\nMagneticWidget::MagneticWidget(QWidget *adsorbWidget, AdsorbPositions adsorbPos) : QWidget(Q_NULLPTR), m_adsorbPos(adsorbPos), m_adsorbWidget(adsorbWidget)\n{\n    Q_ASSERT(m_adsorbWidget);\n    setParent(m_adsorbWidget);\n    setWindowFlags(windowFlags() | Qt::Tool);\n    m_adsorbWidgetSize = m_adsorbWidget->size();\n\n    m_adsorbWidget->installEventFilter(this);\n}\n\nMagneticWidget::~MagneticWidget()\n{\n    if (m_adsorbWidget) {\n        m_adsorbWidget->removeEventFilter(this);\n    }\n}\n\nbool MagneticWidget::isAdsorbed()\n{\n    return m_adsorbed;\n}\n\nbool MagneticWidget::eventFilter(QObject *watched, QEvent *event)\n{\n    if (watched != m_adsorbWidget || !event) {\n        return false;\n    }\n    // 始终记录adsorbWidget最新size\n    if (QEvent::Resize == event->type()) {\n        m_adsorbWidgetSize = m_adsorbWidget->size();\n    }\n    if (m_adsorbed && QEvent::Move == event->type()) {\n        move(m_adsorbWidget->pos() - m_relativePos);\n    }\n    if (m_adsorbed && (QEvent::Show == event->type() || QEvent::FocusIn == event->type())) {\n        show();\n        raise();\n    }\n    if (m_adsorbed && QEvent::Resize == event->type()) {\n        QRect parentRect;\n        QRect targetRect;\n        getGeometry(parentRect, targetRect);\n        QPoint pos(parentRect.left(), parentRect.top());\n        switch (m_curAdsorbPosition) {\n        case AP_OUTSIDE_LEFT:\n            pos.setX(pos.x() - width());\n            pos.setY(pos.y() - m_relativePos.y());\n            break;\n        case AP_OUTSIDE_RIGHT:\n            pos.setX(pos.x() + m_adsorbWidgetSize.width());\n            pos.setY(pos.y() - m_relativePos.y());\n            break;\n        case AP_OUTSIDE_TOP:\n            pos.setX(pos.x() - m_relativePos.x());\n            pos.setY(pos.y() - targetRect.height());\n            break;\n        case AP_OUTSIDE_BOTTOM:\n            pos.setX(pos.x() - m_relativePos.x());\n            pos.setY(pos.y() + parentRect.height());\n            break;\n        case AP_INSIDE_LEFT:\n            pos.setY(pos.y() - m_relativePos.y());\n            break;\n        case AP_INSIDE_RIGHT:\n            pos.setX(parentRect.right() - targetRect.width());\n            pos.setY(pos.y() - m_relativePos.y());\n            break;\n        case AP_INSIDE_TOP:\n            pos.setX(pos.x() - m_relativePos.x());\n            break;\n        case AP_INSIDE_BOTTOM:\n            pos.setX(pos.x() - m_relativePos.x());\n            pos.setY(parentRect.bottom() - targetRect.height());\n            break;\n        default:\n            break;\n        }\n        move(pos);\n    }\n    return false;\n}\n\nvoid MagneticWidget::moveEvent(QMoveEvent *event)\n{\n    Q_UNUSED(event)\n    if (!m_adsorbWidget) {\n        return;\n    }\n\n    QRect parentRect;\n    QRect targetRect;\n    getGeometry(parentRect, targetRect);\n\n    int parentLeft = parentRect.left();\n    int parentRight = parentRect.right();\n    int parentTop = parentRect.top();\n    int parentBottom = parentRect.bottom();\n    int targetLeft = targetRect.left();\n    int targetRight = targetRect.right();\n    int targetTop = targetRect.top();\n    int targetBottom = targetRect.bottom();\n\n    QPoint finalPosition = pos();\n    int adsorbDistance = 30;\n\n    m_adsorbed = false;\n\n    if (m_adsorbPos & AP_INSIDE_LEFT && parentRect.intersects(targetRect) && qAbs(parentLeft - targetLeft) < adsorbDistance) {\n        finalPosition.setX(parentLeft);\n        m_adsorbed |= true;\n        m_curAdsorbPosition = AP_INSIDE_LEFT;\n    }\n\n    if (m_adsorbPos & AP_OUTSIDE_RIGHT && parentRect.intersects(targetRect.translated(-adsorbDistance, 0)) && qAbs(parentRight - targetLeft) < adsorbDistance) {\n        finalPosition.setX(parentRight);\n        m_adsorbed |= true;\n        m_curAdsorbPosition = AP_OUTSIDE_RIGHT;\n    }\n\n    if (m_adsorbPos & AP_OUTSIDE_LEFT && parentRect.intersects(targetRect.translated(adsorbDistance, 0)) && qAbs(parentLeft - targetRight) < adsorbDistance) {\n        finalPosition.setX(parentLeft - targetRect.width());\n        m_adsorbed |= true;\n        m_curAdsorbPosition = AP_OUTSIDE_LEFT;\n    }\n\n    if (m_adsorbPos & AP_INSIDE_RIGHT && parentRect.intersects(targetRect) && qAbs(parentRight - targetRight) < adsorbDistance) {\n        finalPosition.setX(parentRight - targetRect.width());\n        m_adsorbed |= true;\n        m_curAdsorbPosition = AP_INSIDE_RIGHT;\n    }\n\n    if (m_adsorbPos & AP_INSIDE_TOP && parentRect.intersects(targetRect) && qAbs(parentTop - targetTop) < adsorbDistance) {\n        finalPosition.setY(parentTop);\n        m_adsorbed |= true;\n        m_curAdsorbPosition = AP_INSIDE_TOP;\n    }\n\n    if (m_adsorbPos & AP_OUTSIDE_TOP && parentRect.intersects(targetRect.translated(0, adsorbDistance)) && qAbs(parentTop - targetBottom) < adsorbDistance) {\n        finalPosition.setY(parentTop - targetRect.height());\n        m_adsorbed |= true;\n        m_curAdsorbPosition = AP_OUTSIDE_TOP;\n    }\n\n    if (m_adsorbPos & AP_OUTSIDE_BOTTOM && parentRect.intersects(targetRect.translated(0, -adsorbDistance))\n        && qAbs(parentBottom - targetTop) < adsorbDistance) {\n        finalPosition.setY(parentBottom);\n        m_adsorbed |= true;\n        m_curAdsorbPosition = AP_OUTSIDE_BOTTOM;\n    }\n\n    if (m_adsorbPos & AP_INSIDE_BOTTOM && parentRect.intersects(targetRect) && qAbs(parentBottom - targetBottom) < adsorbDistance) {\n        finalPosition.setY(parentBottom - targetRect.height());\n        m_adsorbed |= true;\n        m_curAdsorbPosition = AP_INSIDE_BOTTOM;\n    }\n\n    if (m_adsorbed) {\n        m_relativePos = m_adsorbWidget->pos() - pos();\n    }\n\n    move(finalPosition);\n}\n\nvoid MagneticWidget::getGeometry(QRect &relativeWidgetRect, QRect &targetWidgetRect)\n{\n    relativeWidgetRect.setTopLeft(m_adsorbWidget->pos());\n    relativeWidgetRect.setWidth(m_adsorbWidgetSize.width());\n    relativeWidgetRect.setHeight(m_adsorbWidgetSize.height());\n\n    targetWidgetRect.setTopLeft(pos());\n    targetWidgetRect.setWidth(width());\n    targetWidgetRect.setHeight(height());\n}\n"
  },
  {
    "path": "QtScrcpy/uibase/magneticwidget.h",
    "content": "#ifndef MAGNETICWIDGET_H\n#define MAGNETICWIDGET_H\n\n#include <QPointer>\n#include <QWidget>\n\n/*\n * a magnetic widget\n * window title bar support not good\n*/\n\nclass MagneticWidget : public QWidget\n{\n    Q_OBJECT\n\npublic:\n    enum AdsorbPosition\n    {\n        AP_OUTSIDE_LEFT = 0x01,   // 吸附外部左边框\n        AP_OUTSIDE_TOP = 0x02,    // 吸附外部上边框\n        AP_OUTSIDE_RIGHT = 0x04,  // 吸附外部右边框\n        AP_OUTSIDE_BOTTOM = 0x08, // 吸附外部下边框\n        AP_INSIDE_LEFT = 0x10,    // 吸附内部左边框\n        AP_INSIDE_TOP = 0x20,     // 吸附内部上边框\n        AP_INSIDE_RIGHT = 0x40,   // 吸附内部右边框\n        AP_INSIDE_BOTTOM = 0x80,  // 吸附内部下边框\n        AP_ALL = 0xFF,            // 全吸附\n    };\n    Q_DECLARE_FLAGS(AdsorbPositions, AdsorbPosition)\n\npublic:\n    explicit MagneticWidget(QWidget *adsorbWidget, AdsorbPositions adsorbPos = AP_ALL);\n    ~MagneticWidget();\n\n    bool isAdsorbed();\n\nprotected:\n    bool eventFilter(QObject *watched, QEvent *event) override;\n    void moveEvent(QMoveEvent *event) override;\n\nprivate:\n    void getGeometry(QRect &relativeWidgetRect, QRect &targetWidgetRect);\n\nprivate:\n    AdsorbPositions m_adsorbPos = AP_ALL;\n    QPoint m_relativePos;\n    bool m_adsorbed = false;\n    QPointer<QWidget> m_adsorbWidget;\n    // 单独记录adsorbWidgetSize，因为Widget setGeometry的时候，会先收到Move事件，后收到Resize事件，\n    // 但是收到Move事件时Widget的size()已经是setGeometry指定的size了\n    QSize m_adsorbWidgetSize;\n    AdsorbPosition m_curAdsorbPosition;\n};\n\nQ_DECLARE_OPERATORS_FOR_FLAGS(MagneticWidget::AdsorbPositions)\n#endif // MAGNETICWIDGET_H\n"
  },
  {
    "path": "QtScrcpy/util/config.cpp",
    "content": "﻿#include <QCoreApplication>\n#include <QFileInfo>\n#include <QSettings>\n#include <QDebug>\n\n#include \"config.h\"\n#ifdef Q_OS_OSX\n#include \"path.h\"\n#endif\n\n#define GROUP_COMMON \"common\"\n\n// config\n#define COMMON_LANGUAGE_KEY \"Language\"\n#define COMMON_LANGUAGE_DEF \"Auto\"\n\n#define COMMON_TITLE_KEY \"WindowTitle\"\n#define COMMON_TITLE_DEF QCoreApplication::applicationName()\n\n#define COMMON_PUSHFILE_KEY \"PushFilePath\"\n#define COMMON_PUSHFILE_DEF \"/sdcard/\"\n\n#define COMMON_SERVER_PATH_KEY \"ServerPath\"\n#define COMMON_SERVER_PATH_DEF \"/data/local/tmp/scrcpy-server.jar\"\n\n#define COMMON_MAX_FPS_KEY \"MaxFps\"\n#define COMMON_MAX_FPS_DEF 0\n\n#define COMMON_DESKTOP_OPENGL_KEY \"UseDesktopOpenGL\"\n#define COMMON_DESKTOP_OPENGL_DEF -1\n\n#define COMMON_SKIN_KEY \"UseSkin\"\n#define COMMON_SKIN_DEF 1\n\n#define COMMON_RENDER_EXPIRED_FRAMES_KEY \"RenderExpiredFrames\"\n#define COMMON_RENDER_EXPIRED_FRAMES_DEF 0\n\n#define COMMON_ADB_PATH_KEY \"AdbPath\"\n#define COMMON_ADB_PATH_DEF \"\"\n\n#define COMMON_LOG_LEVEL_KEY \"LogLevel\"\n#define COMMON_LOG_LEVEL_DEF \"info\"\n\n#define COMMON_CODEC_OPTIONS_KEY \"CodecOptions\"\n#define COMMON_CODEC_OPTIONS_DEF \"\"\n\n#define COMMON_CODEC_NAME_KEY \"CodecName\"\n#define COMMON_CODEC_NAME_DEF \"\"\n\n// user config\n#define COMMON_RECORD_KEY \"RecordPath\"\n#define COMMON_RECORD_DEF \"\"\n\n#define COMMON_BITRATE_KEY \"BitRate\"\n#define COMMON_BITRATE_DEF 2000000\n\n#define COMMON_MAX_SIZE_INDEX_KEY \"MaxSizeIndex\"\n#define COMMON_MAX_SIZE_INDEX_DEF 2\n\n#define COMMON_RECORD_FORMAT_INDEX_KEY \"RecordFormatIndex\"\n#define COMMON_RECORD_FORMAT_INDEX_DEF 0\n\n#define COMMON_LOCK_ORIENTATION_INDEX_KEY \"LockDirectionIndex\"\n#define COMMON_LOCK_ORIENTATION_INDEX_DEF 0\n\n#define COMMON_RECORD_SCREEN_KEY \"RecordScreen\"\n#define COMMON_RECORD_SCREEN_DEF false\n\n#define COMMON_RECORD_BACKGROUD_KEY \"RecordBackGround\"\n#define COMMON_RECORD_BACKGROUD_DEF false\n\n#define COMMON_REVERSE_CONNECT_KEY \"ReverseConnect\"\n#define COMMON_REVERSE_CONNECT_DEF true\n\n#define COMMON_SHOW_FPS_KEY \"ShowFPS\"\n#define COMMON_SHOW_FPS_DEF false\n\n#define COMMON_WINDOW_ON_TOP_KEY \"WindowOnTop\"\n#define COMMON_WINDOW_ON_TOP_DEF false\n\n#define COMMON_AUTO_OFF_SCREEN_KEY \"AutoOffScreen\"\n#define COMMON_AUTO_OFF_SCREEN_DEF false\n\n#define COMMON_FRAMELESS_WINDOW_KEY \"FramelessWindow\"\n#define COMMON_FRAMELESS_WINDOW_DEF false\n\n#define COMMON_KEEP_ALIVE_KEY \"KeepAlive\"\n#define COMMON_KEEP_ALIVE_DEF false\n\n#define COMMON_SIMPLE_MODE_KEY \"SimpleMode\"\n#define COMMON_SIMPLE_MODE_DEF false\n\n#define COMMON_AUTO_UPDATE_DEVICE_KEY \"AutoUpdateDevice\"\n#define COMMON_AUTO_UPDATE_DEVICE_DEF true\n\n#define COMMON_TRAY_MESSAGE_SHOWN_KEY \"TrayMessageShown\"\n#define COMMON_TRAY_MESSAGE_SHOWN_DEF false\n\n#define COMMON_SHOW_TOOLBAR_KEY \"showToolbar\"\n#define COMMON_SHOW_TOOLBAR_DEF true\n\n// device config\n#define SERIAL_WINDOW_RECT_KEY_X \"WindowRectX\"\n#define SERIAL_WINDOW_RECT_KEY_Y \"WindowRectY\"\n#define SERIAL_WINDOW_RECT_KEY_W \"WindowRectW\"\n#define SERIAL_WINDOW_RECT_KEY_H \"WindowRectH\"\n#define SERIAL_WINDOW_RECT_KEY_DEF -1\n#define SERIAL_NICK_NAME_KEY \"NickName\"\n#define SERIAL_NICK_NAME_DEF \"Phone\"\n\n// IP history\n#define IP_HISTORY_KEY \"IpHistory\"\n#define IP_HISTORY_DEF \"\"\n#define IP_HISTORY_MAX 10\n\n// Port history  \n#define PORT_HISTORY_KEY \"PortHistory\"\n#define PORT_HISTORY_DEF \"\"\n#define PORT_HISTORY_MAX 10\n\nQString Config::s_configPath = \"\";\n\nConfig::Config(QObject *parent) : QObject(parent)\n{\n    m_settings = new QSettings(getConfigPath() + \"/config.ini\", QSettings::IniFormat);\n    m_userData = new QSettings(getConfigPath() + \"/userdata.ini\", QSettings::IniFormat);\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n    m_settings->setIniCodec(\"UTF-8\");\n    m_userData->setIniCodec(\"UTF-8\");\n#endif\n\n    qDebug()<<m_userData->childGroups();\n}\n\nConfig &Config::getInstance()\n{\n    static Config config;\n    return config;\n}\n\nconst QString &Config::getConfigPath()\n{\n    if (s_configPath.isEmpty()) {\n        s_configPath = QString::fromLocal8Bit(qgetenv(\"QTSCRCPY_CONFIG_PATH\"));\n        QFileInfo fileInfo(s_configPath);\n        if (s_configPath.isEmpty() || !fileInfo.isDir()) {\n            // default application dir\n            // mac系统当从finder打开app时，默认工作目录不再是可执行程序的目录了，而是\"/\"\n            // 而Qt的获取工作目录的api都依赖QCoreApplication的初始化，所以使用mac api获取当前目录\n#ifdef Q_OS_OSX\n            // get */QtScrcpy.app path\n            s_configPath = Path::GetCurrentPath();\n            s_configPath += \"/Contents/MacOS/config\";\n#else\n            s_configPath = \"config\";\n#endif\n        }\n    }\n    return s_configPath;\n}\n\nvoid Config::setUserBootConfig(const UserBootConfig &config)\n{\n    m_userData->beginGroup(GROUP_COMMON);\n    m_userData->setValue(COMMON_RECORD_KEY, config.recordPath);\n    m_userData->setValue(COMMON_BITRATE_KEY, config.bitRate);\n    m_userData->setValue(COMMON_MAX_SIZE_INDEX_KEY, config.maxSizeIndex);\n    m_userData->setValue(COMMON_RECORD_FORMAT_INDEX_KEY, config.recordFormatIndex);\n    m_userData->setValue(COMMON_FRAMELESS_WINDOW_KEY, config.framelessWindow);\n    m_userData->setValue(COMMON_LOCK_ORIENTATION_INDEX_KEY, config.lockOrientationIndex);\n    m_userData->setValue(COMMON_RECORD_SCREEN_KEY, config.recordScreen);\n    m_userData->setValue(COMMON_RECORD_BACKGROUD_KEY, config.recordBackground);\n    m_userData->setValue(COMMON_REVERSE_CONNECT_KEY, config.reverseConnect);\n    m_userData->setValue(COMMON_SHOW_FPS_KEY, config.showFPS);\n    m_userData->setValue(COMMON_WINDOW_ON_TOP_KEY, config.windowOnTop);\n    m_userData->setValue(COMMON_AUTO_OFF_SCREEN_KEY, config.autoOffScreen);\n    m_userData->setValue(COMMON_KEEP_ALIVE_KEY, config.keepAlive);\n    m_userData->setValue(COMMON_SIMPLE_MODE_KEY, config.simpleMode);\n    m_userData->setValue(COMMON_AUTO_UPDATE_DEVICE_KEY, config.autoUpdateDevice);\n    m_userData->setValue(COMMON_SHOW_TOOLBAR_KEY, config.showToolbar);\n    m_userData->endGroup();\n    m_userData->sync();\n}\n\nUserBootConfig Config::getUserBootConfig()\n{\n    UserBootConfig config;\n    m_userData->beginGroup(GROUP_COMMON);\n    config.recordPath = m_userData->value(COMMON_RECORD_KEY, COMMON_RECORD_DEF).toString();\n    config.bitRate = m_userData->value(COMMON_BITRATE_KEY, COMMON_BITRATE_DEF).toUInt();\n    config.maxSizeIndex = m_userData->value(COMMON_MAX_SIZE_INDEX_KEY, COMMON_MAX_SIZE_INDEX_DEF).toInt();\n    config.recordFormatIndex = m_userData->value(COMMON_RECORD_FORMAT_INDEX_KEY, COMMON_RECORD_FORMAT_INDEX_DEF).toInt();\n    config.lockOrientationIndex = m_userData->value(COMMON_LOCK_ORIENTATION_INDEX_KEY, COMMON_LOCK_ORIENTATION_INDEX_DEF).toInt();\n    config.framelessWindow = m_userData->value(COMMON_FRAMELESS_WINDOW_KEY, COMMON_FRAMELESS_WINDOW_DEF).toBool();\n    config.recordScreen = m_userData->value(COMMON_RECORD_SCREEN_KEY, COMMON_RECORD_SCREEN_DEF).toBool();\n    config.recordBackground = m_userData->value(COMMON_RECORD_BACKGROUD_KEY, COMMON_RECORD_BACKGROUD_DEF).toBool();\n    config.reverseConnect = m_userData->value(COMMON_REVERSE_CONNECT_KEY, COMMON_REVERSE_CONNECT_DEF).toBool();\n    config.showFPS = m_userData->value(COMMON_SHOW_FPS_KEY, COMMON_SHOW_FPS_DEF).toBool();\n    config.windowOnTop = m_userData->value(COMMON_WINDOW_ON_TOP_KEY, COMMON_WINDOW_ON_TOP_DEF).toBool();\n    config.autoOffScreen = m_userData->value(COMMON_AUTO_OFF_SCREEN_KEY, COMMON_AUTO_OFF_SCREEN_DEF).toBool();\n    config.keepAlive = m_userData->value(COMMON_KEEP_ALIVE_KEY, COMMON_KEEP_ALIVE_DEF).toBool();\n    config.simpleMode = m_userData->value(COMMON_SIMPLE_MODE_KEY, COMMON_SIMPLE_MODE_DEF).toBool();\n    config.autoUpdateDevice = m_userData->value(COMMON_AUTO_UPDATE_DEVICE_KEY, COMMON_AUTO_UPDATE_DEVICE_DEF).toBool();\n    config.showToolbar =m_userData->value(COMMON_SHOW_TOOLBAR_KEY,COMMON_SHOW_TOOLBAR_DEF).toBool();\n    m_userData->endGroup();\n    return config;\n}\n\nvoid Config::setTrayMessageShown(bool shown)\n{\n    m_userData->beginGroup(GROUP_COMMON);\n    m_userData->setValue(COMMON_TRAY_MESSAGE_SHOWN_KEY, shown);\n    m_userData->endGroup();\n    m_userData->sync();\n}\n\nbool Config::getTrayMessageShown()\n{\n    bool shown;\n    m_userData->beginGroup(GROUP_COMMON);\n    shown = m_userData->value(COMMON_TRAY_MESSAGE_SHOWN_KEY, COMMON_TRAY_MESSAGE_SHOWN_DEF).toBool();\n    m_userData->endGroup();\n    return shown;\n}\n\nvoid Config::setRect(const QString &serial, const QRect &rc)\n{\n    m_userData->beginGroup(serial);\n    m_userData->setValue(SERIAL_WINDOW_RECT_KEY_X, rc.left());\n    m_userData->setValue(SERIAL_WINDOW_RECT_KEY_Y, rc.top());\n    m_userData->setValue(SERIAL_WINDOW_RECT_KEY_W, rc.width());\n    m_userData->setValue(SERIAL_WINDOW_RECT_KEY_H, rc.height());\n    m_userData->endGroup();\n    m_userData->sync();\n}\n\nQRect Config::getRect(const QString &serial)\n{\n    QRect rc;\n    m_userData->beginGroup(serial);\n    rc.setX(m_userData->value(SERIAL_WINDOW_RECT_KEY_X, SERIAL_WINDOW_RECT_KEY_DEF).toInt());\n    rc.setY(m_userData->value(SERIAL_WINDOW_RECT_KEY_Y, SERIAL_WINDOW_RECT_KEY_DEF).toInt());\n    rc.setWidth(m_userData->value(SERIAL_WINDOW_RECT_KEY_W, SERIAL_WINDOW_RECT_KEY_DEF).toInt());\n    rc.setHeight(m_userData->value(SERIAL_WINDOW_RECT_KEY_H, SERIAL_WINDOW_RECT_KEY_DEF).toInt());\n    m_userData->endGroup();\n    return rc;\n}\n\nvoid Config::setNickName(const QString &serial, const QString &name)\n{\n    m_userData->beginGroup(serial);\n    m_userData->setValue(SERIAL_NICK_NAME_KEY, name);\n    m_userData->endGroup();\n    m_userData->sync();\n}\n\nQString Config::getNickName(const QString &serial)\n{\n    QString name;\n    m_userData->beginGroup(serial);\n    name = m_userData->value(SERIAL_NICK_NAME_KEY, SERIAL_NICK_NAME_DEF).toString();\n    m_userData->endGroup();\n    return name;\n}\n\nint Config::getMaxFps()\n{\n    int fps = 0;\n    m_settings->beginGroup(GROUP_COMMON);\n    fps = m_settings->value(COMMON_MAX_FPS_KEY, COMMON_MAX_FPS_DEF).toInt();\n    m_settings->endGroup();\n    return fps;\n}\n\nint Config::getDesktopOpenGL()\n{\n    int opengl = 0;\n    m_settings->beginGroup(GROUP_COMMON);\n    opengl = m_settings->value(COMMON_DESKTOP_OPENGL_KEY, COMMON_DESKTOP_OPENGL_DEF).toInt();\n    m_settings->endGroup();\n    return opengl;\n}\n\nint Config::getSkin()\n{\n    // force disable skin\n    return 0;\n    int skin = 1;\n    m_settings->beginGroup(GROUP_COMMON);\n    skin = m_settings->value(COMMON_SKIN_KEY, COMMON_SKIN_DEF).toInt();\n    m_settings->endGroup();\n    return skin;\n}\n\nint Config::getRenderExpiredFrames()\n{\n    int renderExpiredFrames = 1;\n    m_settings->beginGroup(GROUP_COMMON);\n    renderExpiredFrames = m_settings->value(COMMON_RENDER_EXPIRED_FRAMES_KEY, COMMON_RENDER_EXPIRED_FRAMES_DEF).toInt();\n    m_settings->endGroup();\n    return renderExpiredFrames;\n}\n\nQString Config::getPushFilePath()\n{\n    QString pushFile;\n    m_settings->beginGroup(GROUP_COMMON);\n    pushFile = m_settings->value(COMMON_PUSHFILE_KEY, COMMON_PUSHFILE_DEF).toString();\n    m_settings->endGroup();\n    return pushFile;\n}\n\nQString Config::getServerPath()\n{\n    QString serverPath;\n    m_settings->beginGroup(GROUP_COMMON);\n    serverPath = m_settings->value(COMMON_SERVER_PATH_KEY, COMMON_SERVER_PATH_DEF).toString();\n    m_settings->endGroup();\n    return serverPath;\n}\n\nQString Config::getAdbPath()\n{\n    QString adbPath;\n    m_settings->beginGroup(GROUP_COMMON);\n    adbPath = m_settings->value(COMMON_ADB_PATH_KEY, COMMON_ADB_PATH_DEF).toString();\n    m_settings->endGroup();\n    return adbPath;\n}\n\nQString Config::getLogLevel()\n{\n    QString logLevel;\n    m_settings->beginGroup(GROUP_COMMON);\n    logLevel = m_settings->value(COMMON_LOG_LEVEL_KEY, COMMON_LOG_LEVEL_DEF).toString();\n    m_settings->endGroup();\n    return logLevel;\n}\n\nQString Config::getCodecOptions()\n{\n    QString codecOptions;\n    m_settings->beginGroup(GROUP_COMMON);\n    codecOptions = m_settings->value(COMMON_CODEC_OPTIONS_KEY, COMMON_CODEC_OPTIONS_DEF).toString();\n    m_settings->endGroup();\n    return codecOptions;\n}\n\nQString Config::getCodecName()\n{\n    QString codecName;\n    m_settings->beginGroup(GROUP_COMMON);\n    codecName = m_settings->value(COMMON_CODEC_NAME_KEY, COMMON_CODEC_NAME_DEF).toString();\n    m_settings->endGroup();\n    return codecName;\n}\n\nQStringList Config::getConnectedGroups()\n{\n    return m_userData->childGroups();\n}\n\nvoid Config::deleteGroup(const QString &serial)\n{\n    m_userData->remove(serial);\n}\n\nQString Config::getLanguage()\n{\n    QString language;\n    m_settings->beginGroup(GROUP_COMMON);\n    language = m_settings->value(COMMON_LANGUAGE_KEY, COMMON_LANGUAGE_DEF).toString();\n    m_settings->endGroup();\n    return language;\n}\n\nQString Config::getTitle()\n{\n    QString title;\n    m_settings->beginGroup(GROUP_COMMON);\n    title = m_settings->value(COMMON_TITLE_KEY, COMMON_TITLE_DEF).toString();\n    m_settings->endGroup();\n    return title;\n}\n\nvoid Config::saveIpHistory(const QString &ip)\n{\n    QStringList ipList = getIpHistory();\n    \n    // 移除已存在的相同IP（避免重复）\n    ipList.removeAll(ip);\n    \n    // 将新IP添加到开头\n    ipList.prepend(ip);\n    \n    // 限制历史记录数量\n    while (ipList.size() > IP_HISTORY_MAX) {\n        ipList.removeLast();\n    }\n    \n    m_userData->setValue(IP_HISTORY_KEY, ipList);\n    m_userData->sync();\n}\n\nQStringList Config::getIpHistory()\n{\n    QStringList ipList = m_userData->value(IP_HISTORY_KEY, IP_HISTORY_DEF).toStringList();\n    ipList.removeAll(\"\");\n    return ipList;\n}\n\nvoid Config::clearIpHistory()\n{\n    m_userData->remove(IP_HISTORY_KEY);\n    m_userData->sync();\n}\n\nvoid Config::savePortHistory(const QString &port)\n{\n    QStringList portList = getPortHistory();\n    \n    // 移除已存在的相同Port（避免重复）\n    portList.removeAll(port);\n    \n    // 将新Port添加到开头\n    portList.prepend(port);\n    \n    // 限制历史记录数量\n    while (portList.size() > PORT_HISTORY_MAX) {\n        portList.removeLast();\n    }\n    \n    m_userData->setValue(PORT_HISTORY_KEY, portList);\n    m_userData->sync();\n}\n\nQStringList Config::getPortHistory()\n{\n    QStringList portList = m_userData->value(PORT_HISTORY_KEY, PORT_HISTORY_DEF).toStringList();\n    portList.removeAll(\"\");\n    return portList;\n}\n\nvoid Config::clearPortHistory()\n{\n    m_userData->remove(PORT_HISTORY_KEY);\n    m_userData->sync();\n}\n"
  },
  {
    "path": "QtScrcpy/util/config.h",
    "content": "﻿#ifndef CONFIG_H\n#define CONFIG_H\n\n#include <QObject>\n#include <QPointer>\n#include <QRect>\n\nstruct UserBootConfig\n{\n    QString recordPath = \"\";\n    quint32 bitRate = 2000000;\n    int maxSizeIndex = 0;\n    int recordFormatIndex = 0;\n    int lockOrientationIndex = 0;\n    bool recordScreen     = false;\n    bool recordBackground = false;\n    bool reverseConnect   = true;\n    bool showFPS          = false;\n    bool windowOnTop      = false;\n    bool autoOffScreen    = false;\n    bool framelessWindow  = false;\n    bool keepAlive        = false;\n    bool simpleMode       = false;\n    bool autoUpdateDevice = true;\n    bool showToolbar      = true;\n};\n\nclass QSettings;\nclass Config : public QObject\n{\n    Q_OBJECT\npublic:\n\n    static Config &getInstance();\n\n    // config\n    QString getLanguage();\n    QString getTitle();\n    int getMaxFps();\n    int getDesktopOpenGL();\n    int getSkin();\n    int getRenderExpiredFrames();\n    QString getPushFilePath();\n    QString getServerPath();\n    QString getAdbPath();\n    QString getLogLevel();\n    QString getCodecOptions();\n    QString getCodecName();\n    QStringList getConnectedGroups();\n\n    // user data:common\n    void setUserBootConfig(const UserBootConfig &config);\n    UserBootConfig getUserBootConfig();\n    void setTrayMessageShown(bool shown);\n    bool getTrayMessageShown();\n\n    // user data:device\n    void setNickName(const QString &serial, const QString &name);\n    QString getNickName(const QString &serial);\n    void setRect(const QString &serial, const QRect &rc);\n    QRect getRect(const QString &serial);\n\n    void deleteGroup(const QString &serial);\n\n    // IP history methods\n    void saveIpHistory(const QString &ip);\n    QStringList getIpHistory(); \n    void clearIpHistory();\n\n    // Port history methods\n    void savePortHistory(const QString &port);\n    QStringList getPortHistory(); \n    void clearPortHistory();\n\nprivate:\n    explicit Config(QObject *parent = nullptr);\n    const QString &getConfigPath();\n\nprivate:\n    static QString s_configPath;\n    QPointer<QSettings> m_settings;\n    QPointer<QSettings> m_userData;\n};\n\n#endif // CONFIG_H\n"
  },
  {
    "path": "QtScrcpy/util/mousetap/cocoamousetap.h",
    "content": "#ifndef COCOAMOUSETAP_H\n#define COCOAMOUSETAP_H\n#include <QSemaphore>\n#include <QThread>\n\n#include \"mousetap.h\"\n\nstruct MouseEventTapData;\nclass QWidget;\nclass CocoaMouseTap\n    : public MouseTap\n    , public QThread\n{\npublic:\n    CocoaMouseTap(QObject *parent = Q_NULLPTR);\n    virtual ~CocoaMouseTap();\n\n    void initMouseEventTap() override;\n    void quitMouseEventTap() override;\n    void enableMouseEventTap(QRect rc, bool enabled) override;\n\nprotected:\n    void run() override;\n\nprivate:\n    MouseEventTapData *m_tapData = Q_NULLPTR;\n    QSemaphore m_runloopStartedSemaphore;\n};\n\n#endif // COCOAMOUSETAP_H\n"
  },
  {
    "path": "QtScrcpy/util/mousetap/cocoamousetap.mm",
    "content": "#import <Cocoa/Cocoa.h>\n#include <QDebug>\n\n#include \"cocoamousetap.h\"\n\nstatic const CGEventMask movementEventsMask =\n      CGEventMaskBit(kCGEventLeftMouseDragged)\n    | CGEventMaskBit(kCGEventRightMouseDragged)\n    | CGEventMaskBit(kCGEventMouseMoved);\n\nstatic const CGEventMask allGrabbedEventsMask =\n      CGEventMaskBit(kCGEventLeftMouseDown)    | CGEventMaskBit(kCGEventLeftMouseUp)\n    | CGEventMaskBit(kCGEventRightMouseDown)   | CGEventMaskBit(kCGEventRightMouseUp)\n    | CGEventMaskBit(kCGEventOtherMouseDown)   | CGEventMaskBit(kCGEventOtherMouseUp)\n    | CGEventMaskBit(kCGEventLeftMouseDragged) | CGEventMaskBit(kCGEventRightMouseDragged)\n    | CGEventMaskBit(kCGEventMouseMoved);\n\ntypedef struct MouseEventTapData{\n    CFMachPortRef tap = Q_NULLPTR;\n    CFRunLoopRef runloop = Q_NULLPTR;\n    CFRunLoopSourceRef runloopSource = Q_NULLPTR;\n    QRect rc;\n} MouseEventTapData;\n\nstatic CGEventRef Cocoa_MouseTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon)\n{\n    Q_UNUSED(proxy);\n    MouseEventTapData *tapdata = (MouseEventTapData*)refcon;\n    switch (type) {\n        case kCGEventTapDisabledByTimeout:\n            {\n                CGEventTapEnable(tapdata->tap, true);\n                return nullptr;\n            }\n        case kCGEventTapDisabledByUserInput:\n            {\n                return nullptr;\n            }\n        default:\n            break;\n    }\n\n\n    if (tapdata->rc.isEmpty()) {\n        return event;\n    }\n\n    NSRect limitWindowRect = NSMakeRect(tapdata->rc.left(), tapdata->rc.top(),\n                                   tapdata->rc.width(), tapdata->rc.height());\n    // check rect samll than limit rect\n    NSRect checkWindowRect = NSMakeRect(limitWindowRect.origin.x + 10, limitWindowRect.origin.y + 10,\n                            limitWindowRect.size.width - 10, limitWindowRect.size.height - 10);\n    /* This is in CGs global screenspace coordinate system, which has a\n     * flipped Y.\n     */\n    CGPoint eventLocation = CGEventGetLocation(event);\n    if (!NSMouseInRect(NSPointFromCGPoint(eventLocation), checkWindowRect, NO)) {\n        if (eventLocation.x <= NSMinX(limitWindowRect)) {\n            eventLocation.x = NSMinX(limitWindowRect) + 1.0;\n        } else if (eventLocation.x >= NSMaxX(limitWindowRect)) {\n            eventLocation.x = NSMaxX(limitWindowRect) - 1.0;\n        }\n\n        if (eventLocation.y <= NSMinY(limitWindowRect)) {\n            eventLocation.y = NSMinY(limitWindowRect) + 1.0;\n        } else if (eventLocation.y >= NSMaxY(limitWindowRect)) {\n            eventLocation.y = NSMaxY(limitWindowRect) - 1.0;\n        }\n\n        CGWarpMouseCursorPosition(eventLocation);\n        CGAssociateMouseAndMouseCursorPosition(YES);\n\n        if ((CGEventMaskBit(type) & movementEventsMask) == 0) {\n            /* For click events, we just constrain the event to the window, so\n             * no other app receives the click event. We can't due the same to\n             * movement events, since they mean that our warp cursor above\n             * behaves strangely.\n             */\n            CGEventSetLocation(event, eventLocation);\n        }\n    }\n\n    return event;\n}\n\nstatic void SemaphorePostCallback(CFRunLoopTimerRef timer, void *info)\n{\n    Q_UNUSED(timer);\n    QSemaphore *runloopStartedSemaphore = (QSemaphore *)info;\n    if (runloopStartedSemaphore) {\n        runloopStartedSemaphore->release();\n    }\n}\n\nCocoaMouseTap::CocoaMouseTap(QObject *parent)\n    : QThread(parent)\n{\n    m_tapData = new MouseEventTapData;\n}\n\nCocoaMouseTap::~CocoaMouseTap()\n{\n    if (m_tapData) {\n        delete m_tapData;\n        m_tapData = Q_NULLPTR;\n    }\n}\n\nvoid CocoaMouseTap::initMouseEventTap()\n{\n    if (!m_tapData) {\n        return;\n    }\n\n    m_tapData->tap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap,\n                                      kCGEventTapOptionDefault, allGrabbedEventsMask,\n                                      &Cocoa_MouseTapCallback, m_tapData);\n    if (!m_tapData->tap) {\n        return;\n    }\n    /* Tap starts disabled, until app requests mouse grab */\n    CGEventTapEnable(m_tapData->tap, false);\n    start();\n}\n\nvoid CocoaMouseTap::quitMouseEventTap()\n{\n    bool status;\n    if (m_tapData == Q_NULLPTR || m_tapData->tap == Q_NULLPTR) {\n        /* event tap was already cleaned up (possibly due to CGEventTapCreate\n         * returning null.)\n         */\n        return;\n    }\n\n    /* Ensure that the runloop has been started first.\n     * TODO: Move this to InitMouseEventTap, check for error conditions that can\n     * happen in Cocoa_MouseTapThread, and fall back to the non-EventTap way of\n     * grabbing the mouse if it fails to Init.\n     */\n    status = m_runloopStartedSemaphore.tryAcquire(1, 5000);\n    if (status) {\n        /* Then stop it, which will cause Cocoa_MouseTapThread to return. */\n        CFRunLoopStop(m_tapData->runloop);\n        /* And then wait for Cocoa_MouseTapThread to finish cleaning up. It\n         * releases some of the pointers in tapdata. */\n        wait();\n    }\n}\n\nvoid CocoaMouseTap::enableMouseEventTap(QRect rc, bool enabled)\n{\n    if (m_tapData && m_tapData->tap)\n    {\n        enabled ? m_tapData->rc = rc : m_tapData->rc = QRect();\n        CGEventTapEnable(m_tapData->tap, enabled);\n    }\n}\n\nvoid CocoaMouseTap::run()\n{\n    /* Tap was created on main thread but we own it now. */\n    CFMachPortRef eventTap = m_tapData->tap;\n    if (eventTap) {\n        /* Try to create a runloop source we can schedule. */\n        CFRunLoopSourceRef runloopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);\n        if  (runloopSource) {\n            m_tapData->runloopSource = runloopSource;\n        } else {\n            CFRelease(eventTap);\n            m_runloopStartedSemaphore.release();\n            /* TODO: Both here and in the return below, set some state in\n             * tapdata to indicate that initialization failed, which we should\n             * check in InitMouseEventTap, after we move the semaphore check\n             * from Quit to Init.\n             */\n            return;\n        }\n    } else {\n        m_runloopStartedSemaphore.release();\n        return;\n    }\n\n    m_tapData->runloop = CFRunLoopGetCurrent();\n    CFRunLoopAddSource(m_tapData->runloop, m_tapData->runloopSource, kCFRunLoopCommonModes);\n    CFRunLoopTimerContext context{};\n    context.info = &m_runloopStartedSemaphore;\n    /* We signal the runloop started semaphore *after* the run loop has started, indicating it's safe to CFRunLoopStop it. */\n    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, &SemaphorePostCallback, &context);\n    CFRunLoopAddTimer(m_tapData->runloop, timer, kCFRunLoopCommonModes);\n    CFRelease(timer);\n\n    /* Run the event loop to handle events in the event tap. */\n    CFRunLoopRun();\n    /* Make sure this is signaled so that SDL_QuitMouseEventTap knows it can safely SDL_WaitThread for us. */\n    if (m_runloopStartedSemaphore.available() < 1) {\n        m_runloopStartedSemaphore.release();\n    }\n    CFRunLoopRemoveSource(m_tapData->runloop, m_tapData->runloopSource, kCFRunLoopCommonModes);\n\n    /* Clean up. */\n    CGEventTapEnable(m_tapData->tap, false);\n    CFRelease(m_tapData->runloopSource);\n    CFRelease(m_tapData->tap);\n    m_tapData->runloopSource = Q_NULLPTR;\n    m_tapData->tap = Q_NULLPTR;\n\n    return;\n}\n"
  },
  {
    "path": "QtScrcpy/util/mousetap/mousetap.cpp",
    "content": "#include <QtGlobal>\n\n#include \"mousetap.h\"\n#ifdef Q_OS_WIN32\n#include \"winmousetap.h\"\n#endif\n#ifdef Q_OS_OSX\n#include \"cocoamousetap.h\"\n#endif\n#ifdef Q_OS_LINUX\n#include \"xmousetap.h\"\n#endif\n\nMouseTap *MouseTap::s_instance = Q_NULLPTR;\nMouseTap *MouseTap::getInstance()\n{\n    if (s_instance == Q_NULLPTR) {\n#ifdef Q_OS_WIN32\n        s_instance = new WinMouseTap();\n#endif\n#ifdef Q_OS_OSX\n        s_instance = new CocoaMouseTap();\n#endif\n#ifdef Q_OS_LINUX\n        s_instance = new XMouseTap();\n#endif\n    }\n    return s_instance;\n}\n"
  },
  {
    "path": "QtScrcpy/util/mousetap/mousetap.h",
    "content": "#ifndef MOUSETAP_H\n#define MOUSETAP_H\n#include <QRect>\n\nclass QWidget;\nclass MouseTap\n{\npublic:\n    static MouseTap *getInstance();\n    virtual void initMouseEventTap() = 0;\n    virtual void quitMouseEventTap() = 0;\n    // rc base global screenspace coordinate system, which has a flipped Y.\n    virtual void enableMouseEventTap(QRect rc, bool enabled) = 0;\n\nprivate:\n    static MouseTap *s_instance;\n};\n#endif // MOUSETAP_H\n"
  },
  {
    "path": "QtScrcpy/util/mousetap/winmousetap.cpp",
    "content": "#include <QDebug>\n#include <QWidget>\n#include <Windows.h>\n\n#include \"winmousetap.h\"\n\nWinMouseTap::WinMouseTap() {}\n\nWinMouseTap::~WinMouseTap() {}\n\nvoid WinMouseTap::initMouseEventTap() {}\n\nvoid WinMouseTap::quitMouseEventTap() {}\n\nvoid WinMouseTap::enableMouseEventTap(QRect rc, bool enabled)\n{\n    if (enabled && rc.isEmpty()) {\n        return;\n    }\n    if (enabled) {\n        RECT mainRect;\n        mainRect.left = (LONG)rc.left();\n        mainRect.right = (LONG)rc.right();\n        mainRect.top = (LONG)rc.top();\n        mainRect.bottom = (LONG)rc.bottom();\n        ClipCursor(&mainRect);\n    } else {\n        ClipCursor(Q_NULLPTR);\n    }\n}\n"
  },
  {
    "path": "QtScrcpy/util/mousetap/winmousetap.h",
    "content": "#ifndef WINMOUSETAP_H\n#define WINMOUSETAP_H\n\n#include \"mousetap.h\"\n\nclass WinMouseTap : public MouseTap\n{\npublic:\n    WinMouseTap();\n    virtual ~WinMouseTap();\n\n    void initMouseEventTap() override;\n    void quitMouseEventTap() override;\n    void enableMouseEventTap(QRect rc, bool enabled) override;\n};\n\n#endif // WINMOUSETAP_H\n"
  },
  {
    "path": "QtScrcpy/util/mousetap/xmousetap.cpp",
    "content": "#include <QtGlobal>\n\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n#include <QtX11Extras/QX11Info>\n#else\n#include <QtGui/private/qtx11extras_p.h>\n#endif\n\n#include <xcb/xproto.h>\n#include <stdlib.h>\n#include <stdint.h>\n\n#include \"xmousetap.h\"\n\nXMouseTap::XMouseTap() {}\n\nXMouseTap::~XMouseTap() {}\n\nvoid XMouseTap::initMouseEventTap() {}\n\nvoid XMouseTap::quitMouseEventTap() {}\n\nstatic void find_grab_window_recursive(xcb_connection_t *dpy, xcb_window_t window,\n        QRect rc, int16_t offset_x, int16_t offset_y,\n        xcb_window_t *grab_window, uint32_t *grab_window_size) {\n    xcb_query_tree_cookie_t tree_cookie;\n    xcb_query_tree_reply_t *tree;\n    tree_cookie = xcb_query_tree(dpy, window);\n    tree = xcb_query_tree_reply(dpy, tree_cookie, NULL);\n\n    xcb_window_t *children = xcb_query_tree_children(tree);\n    for (int i = 0; i < xcb_query_tree_children_length(tree); i++) {\n        xcb_get_geometry_cookie_t gg_cookie;\n        xcb_get_geometry_reply_t *gg;\n        gg_cookie = xcb_get_geometry(dpy, children[i]);\n        gg = xcb_get_geometry_reply(dpy, gg_cookie, NULL);\n\n        if (gg->x + offset_x <= rc.left() && gg->x + offset_x + gg->width >= rc.right() &&\n                gg->y + offset_y <= rc.top() && gg->y + offset_y + gg->height >= rc.bottom()) {\n            if (!*grab_window || gg->width * gg->height <= *grab_window_size) {\n                *grab_window = children[i];\n                *grab_window_size = gg->width * gg->height;\n            }\n        }\n\n        find_grab_window_recursive(dpy, children[i], rc,\n                gg->x + offset_x, gg->y + offset_y,\n                grab_window, grab_window_size);\n\n        free(gg);\n    }\n\n    free(tree);\n}\n\nvoid XMouseTap::enableMouseEventTap(QRect rc, bool enabled) {\n    if (enabled && rc.isEmpty()) {\n        return;\n    }\n\n    xcb_connection_t *dpy = QX11Info::connection();\n\n    if (enabled) {\n        // We grab the top-most smallest window\n        xcb_window_t grab_window = 0;\n        uint32_t grab_window_size = 0;\n\n        find_grab_window_recursive(dpy, QX11Info::appRootWindow(QX11Info::appScreen()),\n                rc, 0, 0, &grab_window, &grab_window_size);\n\n        if (grab_window) {\n            xcb_grab_pointer_cookie_t grab_cookie;\n            xcb_grab_pointer_reply_t *grab;\n            grab_cookie = xcb_grab_pointer(dpy, /* owner_events = */ 1,\n                    grab_window, /* event_mask = */ 0,\n                    XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,\n                    grab_window, XCB_NONE, XCB_CURRENT_TIME);\n            grab = xcb_grab_pointer_reply(dpy, grab_cookie, NULL);\n\n            free(grab);\n        }\n    } else {\n        xcb_void_cookie_t ungrab_cookie;\n        xcb_generic_error_t *error;\n        ungrab_cookie = xcb_ungrab_pointer_checked(dpy, XCB_CURRENT_TIME);\n        error = xcb_request_check(dpy, ungrab_cookie);\n\n        free(error);\n    }\n}\n"
  },
  {
    "path": "QtScrcpy/util/mousetap/xmousetap.h",
    "content": "#ifndef XMOUSETAP_H\n#define XMOUSETAP_H\n\n#include \"mousetap.h\"\n\nclass XMouseTap : public MouseTap\n{\npublic:\n    XMouseTap();\n    virtual ~XMouseTap();\n\n    void initMouseEventTap() override;\n    void quitMouseEventTap() override;\n    void enableMouseEventTap(QRect rc, bool enabled) override;\n};\n\n#endif // XMOUSETAP_H\n"
  },
  {
    "path": "QtScrcpy/util/path.h",
    "content": "#pragma once\n\nclass Path {\npublic:\n    static const char* GetCurrentPath();\n};\n"
  },
  {
    "path": "QtScrcpy/util/path.mm",
    "content": "#include \"path.h\"\n\n#import <Cocoa/Cocoa.h>\n\nconst char* Path::GetCurrentPath() {\n    return [[[NSBundle mainBundle] bundlePath] UTF8String];\n}\n"
  },
  {
    "path": "QtScrcpy/util/winutils.cpp",
    "content": "#include <QDebug>\n#include <Windows.h>\n#include <dwmapi.h>\n#pragma comment(lib, \"dwmapi\")\n\n#include \"winutils.h\"\n\nenum : WORD\n{\n    DwmwaUseImmersiveDarkMode = 20,\n    DwmwaUseImmersiveDarkModeBefore20h1 = 19\n};\n\nWinUtils::WinUtils(){};\n\nWinUtils::~WinUtils(){};\n\n// Set dark border to window\n// Reference: qt/qtbase.git/tree/src/plugins/platforms/windows/qwindowswindow.cpp\nbool WinUtils::setDarkBorderToWindow(const HWND &hwnd, const bool &d)\n{\n    const BOOL darkBorder = d ? TRUE : FALSE;\n    const bool ok = SUCCEEDED(DwmSetWindowAttribute(hwnd, DwmwaUseImmersiveDarkMode, &darkBorder, sizeof(darkBorder)))\n                    || SUCCEEDED(DwmSetWindowAttribute(hwnd, DwmwaUseImmersiveDarkModeBefore20h1, &darkBorder, sizeof(darkBorder)));\n    if (!ok)\n        qWarning(\"%s: Unable to set dark window border.\", __FUNCTION__);\n    return ok;\n}\n"
  },
  {
    "path": "QtScrcpy/util/winutils.h",
    "content": "#ifndef WINUTILS_H\n#define WINUTILS_H\n\n#include <QApplication>\n#include <Windows.h>\n\nclass WinUtils\n{\npublic:\n    WinUtils();\n    ~WinUtils();\n\n    static bool setDarkBorderToWindow(const HWND &hwnd, const bool &d);\n};\n\n#endif // WINUTILS_H\n"
  },
  {
    "path": "README.md",
    "content": "# QtScrcpy \n\n[![Financial Contributors to Open Collective](https://opencollective.com/QtScrcpy/all/badge.svg?label=financial+contributors)](https://opencollective.com/QtScrcpy)\n![Windows](https://github.com/barry-ran/QtScrcpy/workflows/Windows/badge.svg)\n![MacOS](https://github.com/barry-ran/QtScrcpy/workflows/MacOS/badge.svg)\n![Ubuntu](https://github.com/barry-ran/QtScrcpy/workflows/Ubuntu/badge.svg)\n\n![license](https://img.shields.io/badge/license-Apache2.0-blue.svg)\n![release](https://img.shields.io/github/v/release/barry-ran/QtScrcpy.svg)\n![star](https://img.shields.io/github/stars/barry-ran/QtScrcpy.svg)\n\n[中文用户？点我查看中文介绍](README_zh.md)\n\nQtScrcpy supports displaying and controlling Android devices via USB or over network. It does NOT require root privileges.\n\nIt supports three major platforms: GNU/Linux, Windows and macOS.\n\nIt focuses on:\n\n - **lightness** (displays only the device screen)\n - **performance** (30~60 fps)\n - **quality** (1920×1080 or above)\n - **low latency** ([35~70ms][lowlatency])\n - **low startup time** (only about 1 second to display the first frame)\n - **non-intrusiveness** (nothing will be installed on the device)\n\n[lowlatency]: https://github.com/Genymobile/scrcpy/pull/646\n\n![win](screenshot/win-en.png)\n\n![mac](screenshot/mac-en.png)\n\n![linux](screenshot/linux-en.png)\n\n## The author has developed a more professional screen casting software called `QuickMirror`\nQuickMirror function&features:\n- Equipment screen casting&control: batch screen casting, individual control, batch control\n- Group management\n- WiFi screen mirroring/OTG screen mirroring\n- Adb shell shortcut command\n- File transfer, apk installation\n- Multiple screen mirroring: In OTG mirroring mode, with low resolution and smoothness settings, a single computer can manage 500+phones simultaneously\n- Low latency: USB screen mirroring 1080p latency is within 30ms, which is lower than all screen mirroring software on the market in terms of latency at the same resolution and smoothness\n- Low CPU usage: pure C++development, high-performance GPU video rendering\n- High resolution: adjustable, maximum support for native resolution of Android terminals\n- Perfect Chinese input: Supports Xianyu app, supports Samsung phones\n- The free version can cast up to 10 screens, with unlimited functionality (except for automatic screen mirroring)\n- QuickMirror tutorial: https://lrbnfell4p.feishu.cn/docx/EMkvdfIvDowy3UxsXUCcpPV8nDh\n- QuickMirror Telegram communication group: https://t.me/+EnQNmb47C_liYmRl\n- Preview of QuickMirror Interface:\n![quickmirror](docs/image/quickmirror.png)\n\n## Mapping Keys\nYou can write your script to map keyboard and mouse actions to touches and clicks of the mobile phone according to your needs. [Here](docs/KeyMapDes.md) are the script writing rules.\n\nScript for TikTok and some other games are provided by default. Once enabled, you can play the game with your keyboard and mouse. The default key mapping for PUBG Mobile is as follows:\n\n![game](screenshot/game.png)\n\nInstruction for adding new customized mapping files.\n\n- Write a customized script and put it in the `keymap` directory\n- Click `refresh script` to show it\n- Select your script\n- Connect to your phone, start service and click `apply`\n- Press `~` key (the SwitchKey in the key map script) to switch to custom mapping mode\n- Press the ~ key again to switch back to normal mode\n- (For games such as PUBG Mobile) If you want to move vehicles with the STEER_WHEEL keys, you need to set the move mode to `single rocker mode`.\n\nIf you don't know how to manually write mapping rules, you can also use the `QuickAssistant` developed by the author\nQuickAssistant Features&Functions:\n- Play Android mobile games smoothly through keyboard and mouse\n- Interface based editing of key mapping script\n- Support pausing the computer screen and using only keyboard and mouse operations\n- Screenshot&Recording of Mobile Screen\n- Simple batch control\n- Android 11+supports playing mobile audio on computers (under development...)\n- Mobile app installation free\n- Fast and instant connection\n- Low latency: USB screen mirroring 1080p latency is within 30ms, which is lower than all screen mirroring software on the market in terms of latency at the same resolution and smoothness\n- Low CPU usage: pure C++development, high-performance GPU video rendering\n- High resolution: adjustable, maximum support for native resolution of Android terminals\n- Telegram Group：https://t.me/+Ylf_5V_rDCMyODQ1\n- [QuickAssistant](https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh)\n\n## Group control\nYou can control all your phones at the same time.\n\n![group-control-demo](docs/image/group-control.gif)\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=barry-ran/QtScrcpy&type=Date)](https://star-history.com/#barry-ran/QtScrcpy&Date)\n\n## Thanks\n\nQtScrcpy is based on [Genymobile](https://github.com/Genymobile)'s [scrcpy](https://github.com/Genymobile/scrcpy) project. Thanks a lot!\n\nThe difference between QtScrcpy and the original scrcpy is as follows:\n\nkey points|scrcpy|QtScrcpy\n--|:--:|:--:\nui|sdl|qt\nvideo encode|ffmpeg|ffmpeg\nvideo render|sdl|opengl\ncross-platform|self implemented|provided by Qt\nlanguage|C|C++\nstyle|sync|async\nkeymap|no custom keymap|support custom keymap\nbuild|meson+gradle|qmake or CMake\n\n- It's very easy to customize your GUI with Qt\n- Asynchronous programming of Qt-based signal slot mechanism improves performance\n- Easy to learn\n- Add support for multi-touch\n\n\n## Learn\n\nIf you are interested in it and want to learn how it works but do not know how to get started, you can choose to purchase my recorded video lessons.\nIt details the development architecture and the development process of the entire software and helps you develop QtScrcpy from scratch.\n\nCourse introduction：[https://blog.csdn.net/rankun1/article/details/87970523](https://blog.csdn.net/rankun1/article/details/87970523)\n\nYou can join Telegram Group for QtScrcpy and exchange ideas with like-minded friends.：\n\nTelegram Group：https://t.me/+EnQNmb47C_liYmRl\n\n\n## Requirements\nAndroid API >= 21 (Android 5.0).\n\nMake sure you have enabled [ADB debugging][enable-adb] on your device(s).\n\n[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling\n\n\n## Download\n\n[gitee-download]: https://gitee.com/Barryda/QtScrcpy/releases\n[github-download]: https://github.com/barry-ran/QtScrcpy/releases\n\n### Windows\nOn Windows, for simplicity, prebuilt archives with all the dependencies (including ADB) are available at Releases:\n\n - [`QtScrcpy`][github-download]\n\nor you can [build it yourself](#Build)\n\n### Mac OS\nOn Mac OS, for simplicity, prebuilt archives with all the dependencies (including ADB) are available at Releases:\n\n- [`QtScrcpy`][github-download]\n\nor you can [build it yourself](#Build)\n\n### Linux\nFor Arch Linux Users, you can use AUR to install: `yay -Syu qtscrcpy` (may be outdated; maintainer: [yochananmarqos](https://aur.archlinux.org/account/yochananmarqos))\n\nFor users in other distros, you can use the prebuilt archives from Releases:\n\n- [`QtScrcpy`][github-download]\n\nor you can get it at [GitHub Actions](https://github.com/barry-ran/QtScrcpy/actions/workflows/ubuntu.yml), in branch `dev` and download the latest artifact.\n\nor you can [build it yourself](#Build) (not recommended, get it in Actions if you can)\n\n## Run\nConnect to your Android device on your computer, then run the program and click `USB connect` or `WiFi connect`\n\n### Wireless connection steps (ensure that the mobile phone and PC are on the same LAN):\n1. Enable USB debugging in developer options on the Android device\n2. Connect the Android device to the computer via USB\n3. Click update device, and you will see that the device number is updated\n4. Click get device IP\n5. Click start adbd\n6. Click wireless connect\n7. Click update device again, and another device with an IP address will be found. Select this device.\n8. Click start service\n\n\nNote: it is not necessary to keep your Android device connected via USB after you start adbd.\n\n## Interface button introduction：\n\n- Start config: function parameter settings before starting the service    \n\n    You can set the bit rate, resolution, recording format, and video save path of the locally recorded video.\n\n    - Background record: the Android device screen is not displayed after starting the service. It is recorded in the background.\n    - Always on top: the video window for Android devices will be kept on the top\n    - Close screen: automatically turn off the Android device screen to save power after starting the service\n    - Reverse connection: service startup mode. You can uncheck it if you experience connection failure with a message `more than one device`\n    \n- Refresh devices: Refresh the currently connected device\n- Start service: connect to the Android device\n- Stop service: disconnect from the Android device\n- Stop all services: disconnect all connected Android devices\n- Get device IP: Get the IP address of the Android device and update it to the \"Wireless\" area for the ease of wireless connection setting.\n- Start adbd: Start the adbd service of the Android device. You must start it before the wireless connection.\n- Wireless connect: Connect to Android devices wirelessly\n- Wireless disconnect: Disconnect wirelessly connected Android devices\n- adb command: execute customized ADB commands (blocking commands are not supported now, such as a shell)\n\n\n## The main function\n- Display Android device screens in real-time\n- Real-time mouse and keyboard control of Android devices\n- Screen recording\n- Screenshot to png\n- Wireless connection\n- Supports multiple device connections\n- Full-screen display\n- Display on the top\n- Install apk: drag and drop apk to the video window to install\n- Transfer files: Drag files to the video window to send files to Android devices\n- Background recording: record only, no display interface\n- Copy-paste\n\n    It is possible to synchronize clipboards between the computer and the device, in\n    both directions:\n\n    - `Ctrl + c` copies the device clipboard to the computer clipboard;\n    - `Ctrl + Shift + v` copies the computer clipboard to the device clipboard;\n    - `Ctrl + v` _pastes_ the computer clipboard as a sequence of text events (non-ASCII characters does not yet work).\n- Group control\n- Sync device speaker sound to the computer (based on [sndcpy](https://github.com/rom1v/sndcpy), Android 10+ only)\n\n## Shortcuts\n\n | Action                                 |   Shortcut (Windows)          |   Shortcut (macOS)\n | -------------------------------------- |:----------------------------- |:-----------------------------\n | Switch fullscreen mode                 | `Ctrl`+`f`                    | `Cmd`+`f`\n | Resize window to 1:1 (pixel-perfect)   | `Ctrl`+`g`                    | `Cmd`+`g`\n | Resize window to remove black borders  | `Ctrl`+`w` \\| _Double-click¹_ | `Cmd`+`w`  \\| _Double-click¹_\n | Click on `HOME`                        | `Ctrl`+`h` \\| _Middle-click_  | `Ctrl`+`h` \\| _Middle-click_\n | Click on `BACK`                        | `Ctrl`+`b` \\| _Right-click²_  | `Cmd`+`b`  \\| _Right-click²_\n | Click on `APP_SWITCH`                  | `Ctrl`+`s`                    | `Cmd`+`s`\n | Click on `MENU`                        | `Ctrl`+`m`                    | `Ctrl`+`m`\n | Click on `VOLUME_UP`                   | `Ctrl`+`↑` _(up)_             | `Cmd`+`↑` _(up)_\n | Click on `VOLUME_DOWN`                 | `Ctrl`+`↓` _(down)_           | `Cmd`+`↓` _(down)_\n | Click on `POWER`                       | `Ctrl`+`p`                    | `Cmd`+`p`\n | Power on                               | _Right-click²_                | _Right-click²_\n | Turn device screen off (keep mirroring)| `Ctrl`+`o`                    | `Cmd`+`o`\n | Expand notification panel              | `Ctrl`+`n`                    | `Cmd`+`n`\n | Collapse notification panel            | `Ctrl`+`Shift`+`n`            | `Cmd`+`Shift`+`n`\n | Copy to clipboard³                     | `Ctrl`+`c`                    | `Cmd`+`c`\n | Cut to clipboard³                      | `Ctrl`+`x`                    | `Cmd`+`x`\n | Synchronize clipboards and paste³      | `Ctrl`+`v`                    | `Cmd`+`v`\n | Inject computer clipboard text         | `Ctrl`+`Shift`+`v`            | `Cmd`+`Shift`+`v`\n\n_¹Double-click on black borders to remove them._  \n\n_²Right-click turns the screen on if it was off, presses BACK otherwise._\n\n_³Only on Android >= 7._\n\n## TODO\n[TODO](docs/TODO.md)\n\n## FAQ\n[FAQ](docs/FAQ.md)\n\n## DEVELOP\n[DEVELOP](docs/DEVELOP.md)\n\nEveryone is welcome to maintain this project and contribute your own code, but please follow these requirements:\n1. Please open PRs to the dev branch instead of the master branch\n2. Please rebase the original project before opening PRs\n3. Please submit PRs on the principle of \"small amounts, many times\" (one PR for a change is recommended)\n4. Please keep the code style consistent with the existing style.\n\n## Why develop QtScrcpy?\nThere are several reasons listed below according to importance (high to low).\n1. In the process of learning Qt, I need a real project to try.\n2. I have some background skills in audio and video and I am interested in them.\n3. I have some Android development skills. But I have used it for a long time. I want to consolidate it.\n4. I found scrcpy and decided to re-make it with the new technology stack (C++ + Qt + Opengl + FFmpeg).\n\n\n## Build\nAll the dependencies are provided and it is easy to compile.\n\n### QtScrcpy\n#### Non-Arch Linux Users\n1. Set up the Qt development environment with the official Qt installer or third-party tools such as [aqt](https://github.com/miurahr/aqtinstall) on the target platform.\n   Qt version bigger than 5.12 is required. (use MSVC 2019 on Windows)\n2. Clone the project with `git clone --recurse-submodules git@github.com:barry-ran/QtScrcpy.git`\n3. For Windows, open CMakeLists.txt with QtCreator and compile Release\n4. For Linux, directly run `./ci/linux/build_for_linux.sh \"Release\"`\nNote: compiled artifacts are located at `output/x64/Release`\n\n#### Arch Linux Users\n1. Install packages: `base-devel cmake qt5-base qt5-multimedia qt5-x11extras` (`qtcreator` is recommended)\n2. Clone the project with `git clone --recurse-submodules git@github.com:barry-ran/QtScrcpy.git`\n3. Run `./ci/linux/build_for_linux.sh \"Release\"`\n\n### Scrcpy-Server\n1. Set up Android development environment on the target platform\n2. Open server project in project root with Android Studio\n3. The first time you open it, if you do not have the corresponding version of Gradle, you will be prompted to find Gradle, whether to upgrade Gradle or create it. Select Cancel. After cancelling, you will be prompted to select the location of existing Gradle. Cancel it too and it will download automatically.\n4. After compiling the apk, rename it to scrcpy-server and replace QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server.\n\n## Licence\nSince it is based on scrcpy, it uses the same license as scrcpy\n\n    Copyright (C) 2025 Rankun\n    \n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n    \n        http://www.apache.org/licenses/LICENSE-2.0\n    \n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n## About the author\n\n[Barry CSDN](https://blog.csdn.net/rankun1)\n\nAn ordinary programmer, working mainly in C++ for desktop client development, graduated from Shandong for more than a year of steel simulation education software, and later moved to Shanghai to work in security, online education-related fields, familiar with audio and video. I have an understanding of audio and video fields such as voice calls, live education, video conferencing and other related solutions. I also have experience in Android, Linux servers and other kinds of development.\n\n## Contributors\n\n### Code Contributors\n\nThis project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].\n<a href=\"https://github.com/barry-ran/QtScrcpy/graphs/contributors\"><img src=\"https://opencollective.com/QtScrcpy/contributors.svg?width=890&button=false\" /></a>\n\n### Financial Contributors\n\nBecome a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/QtScrcpy/contribute)]\n\n#### Individuals\n\n<a href=\"https://opencollective.com/QtScrcpy\"><img src=\"https://opencollective.com/QtScrcpy/individuals.svg?width=890\"></a>\n\n#### Organizations\n\nSupport this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/QtScrcpy/contribute)]\n\n<a href=\"https://opencollective.com/QtScrcpy/organization/0/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/0/avatar.svg\"></a>\n<a href=\"https://opencollective.com/QtScrcpy/organization/1/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/1/avatar.svg\"></a>\n<a href=\"https://opencollective.com/QtScrcpy/organization/2/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/2/avatar.svg\"></a>\n<a href=\"https://opencollective.com/QtScrcpy/organization/3/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/3/avatar.svg\"></a>\n<a href=\"https://opencollective.com/QtScrcpy/organization/4/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/4/avatar.svg\"></a>\n<a href=\"https://opencollective.com/QtScrcpy/organization/5/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/5/avatar.svg\"></a>\n<a href=\"https://opencollective.com/QtScrcpy/organization/6/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/6/avatar.svg\"></a>\n<a href=\"https://opencollective.com/QtScrcpy/organization/7/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/7/avatar.svg\"></a>\n<a href=\"https://opencollective.com/QtScrcpy/organization/8/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/8/avatar.svg\"></a>\n<a href=\"https://opencollective.com/QtScrcpy/organization/9/website\"><img src=\"https://opencollective.com/QtScrcpy/organization/9/avatar.svg\"></a>\n"
  },
  {
    "path": "README_zh.md",
    "content": "# QtScrcpy\n\n![Windows](https://github.com/barry-ran/QtScrcpy/workflows/Windows/badge.svg)\n![MacOS](https://github.com/barry-ran/QtScrcpy/workflows/MacOS/badge.svg)\n![Ubuntu](https://github.com/barry-ran/QtScrcpy/workflows/Ubuntu/badge.svg)\n\n![license](https://img.shields.io/badge/license-Apache2.0-blue.svg)\n![release](https://img.shields.io/github/v/release/barry-ran/QtScrcpy.svg)\n![star](https://img.shields.io/github/stars/barry-ran/QtScrcpy.svg)\n![star](https://gitcode.com/barry-ran/QtScrcpy/star/badge.svg)\n\n[Speaks English? Click me for English introduction.](README.md)\n\nQtScrcpy 可以通过 USB / 网络连接Android设备，并进行显示和控制。无需root权限。\n\n同时支持 GNU/Linux ，Windows 和 MacOS 三大主流桌面平台。\n\n它专注于:\n\n - **精致** (仅显示设备屏幕)\n - **性能** (30~60fps)\n - **质量** (1920×1080以上)\n - **低延迟** ([35~70ms][低延迟])\n - **快速启动** (1s 内就可以看到第一帧图像)\n - **非侵入性** (不在设备上安装任何软件)\n\n[低延迟]: https://github.com/Genymobile/scrcpy/pull/646\n\n![win](screenshot/win-zh.png)\n\n![mac](screenshot/mac-zh.png)\n\n![linux](screenshot/linux-zh.png)\n\n## 作者开发了更加专业的投屏软件`极限投屏`\n极限投屏功能&特点：\n- 设备投屏&控制：批量投屏、单个控制、批量控制\n- 分组管理\n- wifi投屏/OTG投屏\n- adb shell快捷指令\n- 文件传输、apk安装\n- 投屏数量多：在OTG投屏模式，设置分辨率和流畅度为低的情况下，单台电脑可以同时管理500+台手机\n- 低延迟：usb投屏1080p延迟在30ms以内，在相同分辨率流畅度情况下，比市面上所有投屏软件延迟都低\n- cpu占用率低：纯C++开发，高性能GPU视频渲染\n- 高分辨率：可调节，最大支持安卓终端的原生分辨率\n- 完美中文输入：支持闲鱼app，支持三星手机\n- 免费版最多投屏10台，功能无限制(除了自动重新投屏)\n- 极限投屏使用教程：https://lrbnfell4p.feishu.cn/docx/QRMhd9nImorAGgxVLlmczxSdnYf\n- 极限投屏qq交流群：822464342\n- 极限投屏界面预览：\n![quickmirror](docs/image/quickmirror.png)\n\n## 自定义按键映射\n可以根据需要，自己编写脚本将键盘按键映射为手机的触摸点击，编写规则在[这里](docs/KeyMapDes_zh.md)。\n\n默认自带了针对和平精英手游和抖音进行键鼠映射的映射脚本，开启平精英手游后可以用键鼠像玩端游一样玩和平精英手游，开启抖音映射以后可以使用上下左右方向键模拟上下左右滑动，你也可以按照[编写规则](docs/KeyMapDes_zh.md)编写其他游戏的映射文件，默认按键映射如下：\n\n![game](screenshot/game.png)\n\n自定义按键映射操作方法如下：\n- 编写自定义脚本放入 keymap 目录\n- 点击刷新脚本，确保脚本可以被检测到\n- 选择需要的脚本\n- 连接手机并启动服务之后，点击应用脚本\n- 按`~`（即脚本中定义的 SwitchKey）键切换为自定义映射模式即可启用\n- 再次按~键切换为正常控制模式\n- （对于和平精英等游戏）若想使用方向盘控制载具，记得在载具设置中设置为单摇杆模式\n\n如果不会自己手写映射规则，也可以去使用作者开发的`极限手游助手`\n极限手游助手功能&特点：\n- 通过键盘鼠标畅玩安卓手机游戏\n- 按键映射脚本界面化编辑\n- 支持暂停电脑端画面，只使用键鼠操作\n- 截图&录制手机画面\n- 简单批量控制\n- 安卓11+支持电脑播放手机音频（开发中...）\n- 手机端免安装App\n- 极速秒连接\n- 低延迟：usb投屏1080p延迟在30ms以内，在相同分辨率流畅度情况下，比市面上所有投屏软件延迟都低\n- cpu占用率低：纯C++开发，高性能GPU视频渲染\n- 高分辨率：可调节，最大支持安卓终端的原生分辨率\n- [QQ交流群：901736468](https://qm.qq.com/q/wRJJaWLWc8)\n- [极限手游助手说明文档](https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh)\n\n## 批量操作\n你可以同时控制所有的手机\n\n## Star历史\n\n[![Star History Chart](https://api.star-history.com/svg?repos=barry-ran/QtScrcpy&type=Date)](https://star-history.com/#barry-ran/QtScrcpy&Date)\n\n![gc](docs/image/group-control.gif)\n\n## 感谢\n\n基于[Genymobile](https://github.com/Genymobile)的[scrcpy](https://github.com/Genymobile/scrcpy)项目进行复刻，重构，非常感谢。\n\n## 比较\nQtScrcpy 和 Scrcpy 区别如下：\n关键点|scrcpy|QtScrcpy\n--|:--:|:--:\n界面|sdl|qt\n视频解码|ffmpeg|ffmpeg\n视频渲染|sdl|opengl\n跨平台基础设施|自己封装|Qt\n编程语言|C|C++\n编程方式|同步|异步\n按键映射|不支持自定义|支持自定义按键映射\n编译方式|Meson+Gradle|CMake\n\n- 使用Qt可以非常容易的定制自己的界面\n- 基于Qt的信号槽机制的异步编程提高性能\n- 方便新手学习\n- 增加多点触控支持\n\n\n## 学习它\n如果你对它感兴趣，想学习它的实现原理而又感觉无从下手，可以选择购买我录制的视频课程，\n里面详细介绍了整个软件的开发架构以及开发流程，带你从无到有的开发 QtScrcpy：\n\n课程介绍：[https://blog.csdn.net/rankun1/article/details/87970523](https://blog.csdn.net/rankun1/article/details/87970523)\n\n或者你也可以加入我的 QtScrcpy QQ 群，和志同道合的朋友一块互相交流技术：\n\nQQ群号：901736468\n\n\n## 要求\nAndroid 部分至少需要 API 21（Android 5.0）。\n\n您要确保在 Android 设备上[启用adb调试][enable-adb]。\n\n[enable-adb]: https://developer.android.com/studio/command-line/adb.html#Enabling\n\n\n## 下载\n\n[gitee-download]: https://gitee.com/Barryda/QtScrcpy/releases\n[github-download]: https://github.com/barry-ran/QtScrcpy/releases\n\n### Windows\n\nWindows 平台，你可以直接使用我编译好的可执行程序:\n\n - [国内下载][gitee-download]\n - [国外下载][github-download]\n\n你也可以[自己编译](##编译)\n\n### Mac OS\n\nMac OS 平台，你可以直接使用我编译好的可执行程序:\n\n- [国内下载][gitee-download]\n- [国外下载][github-download]\n\n你也可以[自己编译](##编译)\n\n### Linux\n\n对于 Arch Linux 用户，可以使用 AUR 安装：`yay -Syu qtscrcpy`（可能版本并非最新；维护者：[yochananmarqos](https://aur.archlinux.org/account/yochananmarqos)）\n\n其他发行版的用户可以直接使用我编译好的可执行程序:\n\n- [国外下载][github-download]\n\n你也可以从 [GitHub Actions](https://github.com/UjhhgtgTeams/QtScrcpy/actions/workflows/ubuntu.yml) 获取最新的自动编译好的软件\n\n当然，你也可以[自己编译](##编译)（不推荐，需要准备环境）\n\n目前只在 Ubuntu 和 Arch Linux 上测试过编译过程\n\n## 运行\n在你的电脑上接入Android设备，然后运行程序，点击 `一键USB连接` 或者 `一键WIFI连接`\n\n### 无线连接步骤\n1. 将手机和电脑连接到同一局域网\n2. 安卓手机端在开发者选项中打开 USB 调试\n3. 通过 USB 连接安卓手机到电脑\n4. 点击刷新设备，会看到有设备号更新出来\n5. 点击获取设备 IP\n6. 点击启动 adbd\n7. 无线连接\n8. 再次点击刷新设备，发现多出了一个 IP 地址开头的设备，选择这个设备\n9. 启动服务\n\n备注：启动 adbd 以后无需继续连接 USB 线，以后连接断开都不再需要，除非 adbd 停止运行\n\n## 界面解释\n\n- 启动配置：启动服务前的功能参数设置    \n\n    分别可以设置本地录制视频的比特率、分辨率、录制格式、录像保存路径等。\n\n    - 仅后台录制：启动服务不显示界面，只录制 Android 设备屏幕\n    - 窗口置顶：Android 设备显示窗口置顶\n    - 自动息屏：启动服务以后，自动关闭 Android 设备屏幕以节省电量\n    - 使用 Reverse：服务启动模式，出现服务启动失败报错 \"more than one device\" 可以去掉这个勾选尝试连接\n    \n- 刷新设备列表：刷新当前连接的设备\n- 启动服务：连接到 Android 设备\n- 停止服务：断开与 Android 设备的连接\n- 停止所有服务：断开所有已连接的 Android 设备\n- 获取设备ip：获取到 Android 设备的 IP 地址，更新到无线区域中，方便进行无线连接\n- 启动adbd：启动 Android 设备的 adbd 服务，无线连接之前，必须要启动\n- 无线连接：使用无线方式连接 Android 设备\n- 无线断开：断开无线方式连接的 Android 设备\n- 命令行：执行自定义 adb 命令（目前不支持阻塞命令，例如shell）\n\n\n## 功能\n- 实时显示 Android 设备屏幕\n- 实时键鼠控制Android设备\n- 屏幕录制\n- 截图\n- 无线连接\n- 多设备连接与批量操作\n- 全屏显示\n- 窗口置顶\n- 安装 apk：拖拽apk到显示窗口即可安装\n- 传输文件：拖拽文件到显示窗口即可发送文件到 Android 设备\n- 后台录制：只录制屏幕，不显示界面\n- 剪贴板同步:\n    在计算机和设备之间同步剪贴板：\n    - `Ctrl + c`将设备剪贴板复制到计算机剪贴板；\n    - `Ctrl + Shift + v`将计算机剪贴板复制到设备剪贴板；\n    - `Ctrl + v` 将计算机剪贴板作为一系列文本事件发送到设备（不支持非ASCII字符）\n- 同步设备扬声器声音到电脑（基于[sndcpy](https://github.com/rom1v/sndcpy)，仅支持安卓10级以上，目前不推荐使用，可使用蓝牙连接替代）\n\n## 快捷键\n\n | 功能                                   |   快捷键(Windows)              |   快捷键 (macOS)\n | -------------------------------------- |:----------------------------- |:-----------------------------\n | 切换全屏                               | `Ctrl`+`f`                     | `Cmd`+`f`\n | 调整窗口大小为 1:1                      | `Ctrl`+`g`                    | `Cmd`+`g`\n | 调整窗口大小去除黑边                    | `Ctrl`+`w` \\| _左键双击_       | `Cmd`+`w`  \\| _左键双击_\n | 点击 `主页`                            | `Ctrl`+`h` \\| _点击鼠标中键_    | `Ctrl`+`h` \\| _点击鼠标中键_\n | 点击 `BACK`                            | `Ctrl`+`b` \\| _右键双击_       | `Cmd`+`b`  \\| _右键双击_\n | 点击 `APP_SWITCH`                      | `Ctrl`+`s`                    | `Cmd`+`s`\n | 点击 `MENU`                            | `Ctrl`+`m`                    | `Ctrl`+`m`\n | 点击 `VOLUME_UP`                       | `Ctrl`+`↑` _(上)_             | `Cmd`+`↑` _(上)_\n | 点击 `VOLUME_DOWN`                     | `Ctrl`+`↓` _(下)_             | `Cmd`+`↓` _(下)_\n | 点击 `POWER`                           | `Ctrl`+`p`                    | `Cmd`+`p`\n | 打开电源                               | _右键双击_                     | _右键双击_\n | 关闭屏幕 (保持投屏)                     | `Ctrl`+`o`                    | `Cmd`+`o`\n | 打开下拉菜单                           | `Ctrl`+`n`                    | `Cmd`+`n`\n | 关闭下拉菜单                           | `Ctrl`+`Shift`+`n`            | `Cmd`+`Shift`+`n`\n | 复制到剪切板                           | `Ctrl`+`c`                    | `Cmd`+`c`\n | 剪切到剪切板                           | `Ctrl`+`x`                    | `Cmd`+`x`\n | 同步剪切板并粘贴                       | `Ctrl`+`v`                    | `Cmd`+`v`\n | 注入电脑剪切板文本                     | `Ctrl`+`Shift`+`v`            | `Cmd`+`Shift`+`v`\n\n鼠标左键双击黑色区域可以去除黑色区域\n\n如果电源关闭，鼠标右键双击打开电源；如果电源开启，鼠标右键双击相当于返回\n\n## TODO\n[后期计划](docs/TODO.md)\n\n## FAQ\n[常见问题说明](docs/FAQ.md)\n\n## 开发者\n[开发相关](docs/DEVELOP.md)\n\n欢迎大家一起维护这个项目，贡献自己的代码，不过请遵循以下几点要求：\n1. PR 请推向 dev 分支，不要推向 master 分支\n2. 提交 PR 之前请先变基原项目\n3. PR 请以少量多次的原则提交（即一个功能点提交一个 PR）\n4. 代码风格请保持和原有风格一致\n\n## 为什么开发 QtScrcpy？\n综合起来有以下几个原因，比重从大到小排列：\n1. 学习Qt的过程中需要一个项目实战一下\n2. 本身具有音视频相关技能，对音视频很感兴趣\n3. 本身具有 Android 开发技能，好久没用有点生疏，需要巩固一下\n4. 发现了 Scrcpy，决定用新的技术栈（C++ + Qt + Opengl + FFmpeg）进行复刻\n\n\n## 编译\n尽量提供了所有依赖资源，方便傻瓜式编译。\n\n### QtScrcpy\n#### 非  Arch Linux\n1. 使用官方 Qt Installer 或非官方工具（如 [aqt](https://github.com/miurahr/aqtinstall)）在目标平台上搭建Qt开发环境。\n需要 5.12 以上版本 Qt（在 Windows 上使用 MSVC 2019）\n2. 克隆该项目：`git clone --recurse-submodules git@github.com:barry-ran/QtScrcpy.git`\n3. Windows 使用 QtCreator 打开项目下 CMakeLists.txt 并编译 Release\n4. Linux 用终端执行  `./ci/linux/build_for_linux.sh \"Release\"`\n注：编译结果位于 `output/x64/Release` 中\n\n#### Arch Linux\n1. 安装以下包：`qt5-base qt5-multimedia qt5-x11extras`（推荐安装 `qtcreator`）\n2. 克隆该项目：`git clone --recurse-submodules git@github.com:barry-ran/QtScrcpy.git`\n3. 用终端执行  `./ci/linux/build_for_linux.sh \"Release\"`\n注：编译结果位于 `output/x64/Release` 中\n\n### Scrcpy-Server\n1. 目标平台上搭建 Android 开发环境\n2. 使用 Android Studio 打开项目根目录中的 server\n3. 第一次打开时，如果你没有对应版本的 Gradle，Studio 会提示找不到 Gradle，是否升级 Gradle 并创建，选择取消，取消后会提示选择 Gradle 的位置，同样取消即可。Studio 会随后自动下载。\n4. 按需编辑代码\n5. 编译出 apk 以后改名为 scrcpy-server 并替换 `third_party/scrcpy-server` 即可\n\n## Licence\n由于是复刻的 Scrcpy，尊重它的 Licence\n\n    Copyright (C) 2025 Rankun\n    \n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n    \n        http://www.apache.org/licenses/LICENSE-2.0\n    \n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\n## 关于作者\n\n[Barry 的 CSDN](https://blog.csdn.net/rankun1)\n\n一枚普通的程序员，工作中主要使用 C++ 进行桌面客户端开发，一毕业在山东做过一年多钢铁仿真教育软件，后来转战上海先后从事安防，在线教育相关领域工作，对音视频比较熟悉，对音视频领域如语音通话，直播教育，视频会议等相关解决方案有所了解。同时具有Android，Linux服务器等开发经验。\n"
  },
  {
    "path": "backup/myconfig.sh",
    "content": "./configure --disable-everything --disable-x86asm --prefix=../ffmpeg_build \\\n\t--enable-shared --enable-static \\\n\t--enable-decoder=h264 --enable-parser=h264 --enable-demuxer=h264 \\\n\t--enable-muxer=mp4 --enable-protocol=file\n"
  },
  {
    "path": "ci/generate-version.py",
    "content": "import sys\nimport os\n\nif __name__ == '__main__':\n    p = os.popen('git rev-list --tags --max-count=1')\n    commit = p.read()\n    p.close()\n\n    p = os.popen('git describe --tags ' + commit)\n    tag = p.read()\n    p.close()\n\n    # print('get tag:', tag)\n\n    version = str(tag[1:])\n    version_file = os.path.abspath(os.path.join(os.path.dirname(__file__), \"../QtScrcpy/appversion\"))\n    file=open(version_file, 'w')\n    file.write(version)\n    file.close()\n    sys.exit(0)"
  },
  {
    "path": "ci/linux/build_for_linux.sh",
    "content": "echo ---------------------------------------------------------------\necho Check \\& Set Environment Variables\necho ---------------------------------------------------------------\n\n# Get Qt path\n# ENV_QT_PATH example: /home/barry/Qt5.9.6/5.9.6\necho Current ENV_QT_PATH: $ENV_QT_PATH\necho Current directory: $(pwd)\n# Set variables\nqt_cmake_path=$ENV_QT_PATH/gcc_64/lib/cmake/Qt5\nqt_gcc_path=$ENV_QT_PATH/gcc_64\nexport PATH=$qt_gcc_path/bin:$PATH\n\n# Remember working directory\nold_cd=$(pwd)\n\n# Set working dir to the script's path (go up two levels from ci/linux/ to project root)\ncd $(dirname \"$0\")/../../\n\necho\necho\necho ---------------------------------------------------------------\necho Check Build Parameters\necho ---------------------------------------------------------------\necho Possible build modes: Debug/Release/MinSizeRel/RelWithDebInfo\n\nbuild_mode=\"$1\"\nif [[ $build_mode != \"Release\" && $build_mode != \"Debug\" && $build_mode != \"MinSizeRel\" && $build_mode != \"RelWithDebInfo\" ]]; then\n    echo \"error: unknown build mode, exiting......\"\n    exit 1\nfi\n\necho Current build mode: $build_mode\n\necho\necho\necho ---------------------------------------------------------------\necho CMake Build Begins\necho ---------------------------------------------------------------\n\n# Remove output folder\noutput_path=./output\nif [ -d \"$output_path\" ]; then\n    rm -rf $output_path\nfi\n\ncmake_params=\"-DCMAKE_PREFIX_PATH=$qt_cmake_path -DCMAKE_BUILD_TYPE=$build_mode\"\ncmake $cmake_params .\nif [ $? -ne 0 ] ;then\n    echo \"error: CMake failed, exiting......\"\n    exit 1\nfi\n\ncmake --build . --config \"$build_mode\" -j8\nif [ $? -ne 0 ] ;then\n    echo \"error: CMake build failed, exiting......\"\n    exit 1\nfi\n\necho\necho\necho ---------------------------------------------------------------\necho CMake Build Succeeded\necho ---------------------------------------------------------------\n\n# Resume current directory\ncd $old_cd\nexit 0\n"
  },
  {
    "path": "ci/linux/package_appimage.sh",
    "content": "#!/bin/bash\n\necho \"Package AppImage\"\n\nbuild_mode=\"$1\"\nif [[ $build_mode != \"Release\" && $build_mode != \"Debug\" && $build_mode != \"MinSizeRel\" && $build_mode != \"RelWithDebInfo\" ]]; then\n    echo \"error: unknown build mode, exiting......\"\n    exit 1\nfi\n\n# Qt path detection\ndetected_qt_path=\"\"\nif command -v qmake &> /dev/null; then\n    qmake_path=$(which qmake)\n    if [ -n \"$qmake_path\" ]; then\n        qt_base=$(dirname \"$(dirname \"$(dirname \"$qmake_path\")\")\")\n        if [ -d \"$qt_base/gcc_64\" ]; then\n            detected_qt_path=\"$qt_base\"\n        fi\n    fi\nfi\n\nif [ -n \"$detected_qt_path\" ]; then\n    ENV_QT_PATH=\"$detected_qt_path\"\nelif [ -n \"$ENV_QT_PATH\" ]; then\n    if [ ! -d \"$ENV_QT_PATH/gcc_64\" ]; then\n        detected_qt_path=\"\"\n    fi\nfi\n\nif [ -z \"$ENV_QT_PATH\" ] || [ ! -d \"$ENV_QT_PATH/gcc_64\" ]; then\n    common_qt_paths=(\n        \"$HOME/Qt\"\n        \"/opt/Qt\"\n        \"/usr/local/Qt\"\n        \"/usr/lib/qt5\"\n    )\n    for base_path in \"${common_qt_paths[@]}\"; do\n        if [ -d \"$base_path\" ]; then\n            latest_version=$(ls -1td \"$base_path\"/*/gcc_64 2>/dev/null | head -n 1 | sed 's|/gcc_64$||')\n            if [ -n \"$latest_version\" ] && [ -d \"$latest_version/gcc_64\" ]; then\n                ENV_QT_PATH=\"$latest_version\"\n                break\n            fi\n        fi\n    done\nfi\n\nif [ ! -d \"$ENV_QT_PATH/gcc_64\" ]; then\n    echo \"error: Qt installation not found at $ENV_QT_PATH/gcc_64\"\n    exit 1\nfi\n\necho \"Using Qt: $ENV_QT_PATH\"\n\nscript_dir=$(cd $(dirname \"$0\") && pwd)\nproject_root=$(cd \"$script_dir/../..\" && pwd)\nold_cd=$(pwd)\ncd \"$project_root\"\n\noutput_path=\"./output/x64/$build_mode\"\nappimage_output_path=\"./output/appimage\"\nappdir_path=\"$appimage_output_path/QtScrcpy.AppDir\"\napp_name=\"QtScrcpy\"\napp_version=$(cat QtScrcpy/appversion 2>/dev/null || echo \"0.0.0\")\n\necho \"Build mode: $build_mode\"\necho \"App version: $app_version\"\n\nif [ ! -f \"$output_path/$app_name\" ]; then\n    echo \"error: $app_name executable not found in $output_path\"\n    exit 1\nfi\n\n# Clean previous build\nif [ -d \"$appimage_output_path\" ]; then\n    rm -rf \"$appimage_output_path\"\nfi\n\nmkdir -p \"$appimage_output_path\"\nmkdir -p \"$appdir_path/usr/bin\"\nmkdir -p \"$appdir_path/usr/lib\"\nmkdir -p \"$appdir_path/usr/share/applications\"\nmkdir -p \"$appdir_path/usr/share/icons/hicolor/\"{16x16,24x24,32x32,48x48,64x64,128x128,256x256}\"/apps\"\nmkdir -p \"$appdir_path/usr/share/metainfo\"\n\n# Copy executable and resources\ncp \"$output_path/$app_name\" \"$appdir_path/usr/bin/$app_name\"\nchmod +x \"$appdir_path/usr/bin/$app_name\"\n\nif [ -f \"$output_path/sndcpy.sh\" ]; then\n    cp \"$output_path/sndcpy.sh\" \"$appdir_path/usr/bin/\"\n    chmod +x \"$appdir_path/usr/bin/sndcpy.sh\"\nfi\nif [ -f \"$output_path/sndcpy.apk\" ]; then\n    cp \"$output_path/sndcpy.apk\" \"$appdir_path/usr/bin/\"\nfi\n\nif [ -d \"$project_root/keymap\" ]; then\n    cp -r \"$project_root/keymap\" \"$appdir_path/usr/share/\"\nfi\nif [ -d \"$project_root/config\" ]; then\n    cp -r \"$project_root/config\" \"$appdir_path/usr/share/\"\nfi\n\n# Copy ADB and scrcpy-server\nadb_source=\"$project_root/QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb\"\nserver_source=\"$project_root/QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server\"\nmkdir -p \"$appdir_path/usr/lib/qtscrcpy\"\n\nif [ -f \"$adb_source\" ]; then\n    cp \"$adb_source\" \"$appdir_path/usr/lib/qtscrcpy/adb\"\n    chmod +x \"$appdir_path/usr/lib/qtscrcpy/adb\"\n    # Create symlink for sndcpy.sh compatibility\n    if [ ! -f \"$appdir_path/usr/bin/adb\" ]; then\n        ln -sf \"../lib/qtscrcpy/adb\" \"$appdir_path/usr/bin/adb\"\n    fi\nfi\n\nif [ -f \"$server_source\" ]; then\n    cp \"$server_source\" \"$appdir_path/usr/lib/qtscrcpy/scrcpy-server\"\n    chmod +x \"$appdir_path/usr/lib/qtscrcpy/scrcpy-server\"\nfi\n\n# Process icon\nicon_file=\"\"\nicon_source=\"\"\ntarget_icon_path=\"$appdir_path/usr/share/icons/hicolor/256x256/apps/$app_name.png\"\n\nif [ -f \"$project_root/QtScrcpy/res/QtScrcpy.png\" ]; then\n    icon_source=\"$project_root/QtScrcpy/res/QtScrcpy.png\"\nelif [ -f \"$project_root/QtScrcpy/res/image/tray/logo.png\" ]; then\n    icon_source=\"$project_root/QtScrcpy/res/image/tray/logo.png\"\nelif [ -f \"$project_root/QtScrcpy/res/QtScrcpy.ico\" ]; then\n    icon_source=\"$project_root/QtScrcpy/res/QtScrcpy.ico\"\nfi\n\nif [ -n \"$icon_source\" ] && [ -f \"$icon_source\" ]; then\n    need_resize=false\n    if command -v identify &> /dev/null; then\n        icon_size=$(identify -format \"%wx%h\" \"$icon_source\" 2>/dev/null)\n        [ \"$icon_size\" = \"256x256\" ] || need_resize=true\n    elif command -v magick &> /dev/null; then\n        icon_size=$(magick identify -format \"%wx%h\" \"$icon_source\" 2>/dev/null)\n        [ \"$icon_size\" = \"256x256\" ] || need_resize=true\n    elif [[ \"$icon_source\" == *.png ]]; then\n        cp \"$icon_source\" \"$target_icon_path\"\n        icon_file=\"$target_icon_path\"\n    else\n        need_resize=true\n    fi\n\n    if [ \"$need_resize\" = true ]; then\n        if command -v convert &> /dev/null; then\n            convert \"$icon_source\" -resize 256x256 \"$target_icon_path\" && icon_file=\"$target_icon_path\"\n        elif command -v magick &> /dev/null; then\n            magick \"$icon_source\" -resize 256x256 \"$target_icon_path\" && icon_file=\"$target_icon_path\"\n        elif command -v ffmpeg &> /dev/null; then\n            ffmpeg -i \"$icon_source\" -vf scale=256:256 -y \"$target_icon_path\" 2>/dev/null && icon_file=\"$target_icon_path\"\n        else\n            cp \"$icon_source\" \"$target_icon_path\"\n            icon_file=\"$target_icon_path\"\n        fi\n    fi\nfi\n\nif [ -z \"$icon_file\" ]; then\n    if command -v convert &> /dev/null; then\n        convert -size 256x256 xc:transparent \"$target_icon_path\" 2>/dev/null\n    elif command -v magick &> /dev/null; then\n        magick -size 256x256 xc:transparent \"$target_icon_path\" 2>/dev/null\n    else\n        touch \"$target_icon_path\"\n    fi\n    icon_file=\"$target_icon_path\"\nfi\n\nif [ -n \"$icon_file\" ] && [ -f \"$icon_file\" ]; then\n    icon_sizes=(16 24 32 48 64 128 256)\n    for size in \"${icon_sizes[@]}\"; do\n        icon_size_path=\"$appdir_path/usr/share/icons/hicolor/${size}x${size}/apps/$app_name.png\"\n        if command -v convert &> /dev/null; then\n            convert \"$icon_file\" -resize ${size}x${size} \"$icon_size_path\" 2>/dev/null || cp \"$icon_file\" \"$icon_size_path\"\n        elif command -v magick &> /dev/null; then\n            magick \"$icon_file\" -resize ${size}x${size} \"$icon_size_path\" 2>/dev/null || cp \"$icon_file\" \"$icon_size_path\"\n        else\n            cp \"$icon_file\" \"$icon_size_path\" 2>/dev/null || true\n        fi\n    done\nfi\n\n# Create desktop file\ncat > \"$appdir_path/usr/share/applications/$app_name.desktop\" << EOF\n[Desktop Entry]\nType=Application\nName=QtScrcpy\nComment=Display and control Android devices via USB or over network\nExec=$app_name\nIcon=$app_name\nCategories=Utility;\nTerminal=false\nStartupNotify=true\nEOF\n\n# Create metainfo file\napp_id=\"com.github.barry-ran.QtScrcpy\"\ncat > \"$appdir_path/usr/share/metainfo/$app_name.appdata.xml\" << EOF\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop-application\">\n  <id>$app_id</id>\n  <name>QtScrcpy</name>\n  <summary>Display and control Android devices via USB or over network</summary>\n  <description>\n    <p>QtScrcpy supports displaying and controlling Android devices via USB or over network. It does NOT require root privileges.</p>\n  </description>\n  <provides>\n    <binary>$app_name</binary>\n  </provides>\n  <launchable type=\"desktop-id\">$app_name.desktop</launchable>\n  <url type=\"homepage\">https://github.com/barry-ran/QtScrcpy</url>\n  <metadata_license>CC0-1.0</metadata_license>\n  <project_license>Apache-2.0</project_license>\n</component>\nEOF\n\n# Create AppRun script\nIS_DOCKER_OR_CI=false\nif [ -f \"/.dockerenv\" ] || [ -n \"${GITHUB_ACTIONS:-}\" ] || [ -n \"${CI:-}\" ]; then\n    IS_DOCKER_OR_CI=true\nfi\n\nif [ \"$IS_DOCKER_OR_CI\" = true ]; then\n    cat > \"$appdir_path/AppRun\" << 'APPRUN_EOF'\n#!/bin/bash\nHERE=\"$(dirname \"$(readlink -f \"${0}\")\")\"\nAPPIMAGE_LIB_DIRS=\"$HERE/usr/lib:$HERE/usr/lib/x86_64-linux-gnu\"\nexport LD_LIBRARY_PATH=\"$APPIMAGE_LIB_DIRS:/lib/x86_64-linux-gnu:/usr/lib/x86_64-linux-gnu:/usr/lib\"\nif [ -f \"$HERE/usr/lib/libQt5XcbQpa.so.5\" ]; then\n    export LD_PRELOAD=\"$HERE/usr/lib/libQt5XcbQpa.so.5\"\nfi\nQT_PLUGINS_DIR=\"$HERE/usr/plugins\"\nif [ -d \"$QT_PLUGINS_DIR\" ]; then\n    export QT_PLUGIN_PATH=\"$QT_PLUGINS_DIR\"\n    export QT_QPA_PLATFORM_PLUGIN_PATH=\"$QT_PLUGINS_DIR/platforms\"\nfi\nexport QTSCRCPY_ADB_PATH=\"$HERE/usr/lib/qtscrcpy/adb\"\nexport QTSCRCPY_SERVER_PATH=\"$HERE/usr/lib/qtscrcpy/scrcpy-server\"\nexport QTSCRCPY_KEYMAP_PATH=\"$HERE/usr/share/keymap\"\nexport QTSCRCPY_CONFIG_PATH=\"$HERE/usr/share/config\"\nexec \"$HERE/usr/bin/QtScrcpy\" \"$@\"\nAPPRUN_EOF\nelse\n    cat > \"$appdir_path/AppRun\" << 'APPRUN_EOF'\n#!/bin/bash\nHERE=\"$(dirname \"$(readlink -f \"${0}\")\")\"\nexport QTSCRCPY_ADB_PATH=\"$HERE/usr/lib/qtscrcpy/adb\"\nexport QTSCRCPY_SERVER_PATH=\"$HERE/usr/lib/qtscrcpy/scrcpy-server\"\nexport QTSCRCPY_KEYMAP_PATH=\"$HERE/usr/share/keymap\"\nexport QTSCRCPY_CONFIG_PATH=\"$HERE/usr/share/config\"\nexec \"$HERE/usr/bin/QtScrcpy\" \"$@\"\nAPPRUN_EOF\nfi\nchmod +x \"$appdir_path/AppRun\"\n\n# Download linuxdeploy tools\nlinuxdeploy_url=\"https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage\"\nlinuxdeploy_qt_url=\"https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage\"\nlinuxdeploy_temp_dir=\"$appimage_output_path/.tools\"\nlinuxdeploy_path=\"$linuxdeploy_temp_dir/linuxdeploy.AppImage\"\nlinuxdeploy_qt_path=\"$linuxdeploy_temp_dir/linuxdeploy-plugin-qt.AppImage\"\n\nmkdir -p \"$linuxdeploy_temp_dir\"\n\nif [ ! -f \"$linuxdeploy_path\" ]; then\n    wget \"$linuxdeploy_url\" -O \"$linuxdeploy_path\" || exit 1\n    chmod +x \"$linuxdeploy_path\"\nfi\n\nif [ ! -f \"$linuxdeploy_qt_path\" ]; then\n    wget \"$linuxdeploy_qt_url\" -O \"$linuxdeploy_qt_path\" || exit 1\n    chmod +x \"$linuxdeploy_qt_path\"\nfi\n\nlinuxdeploy_path_abs=$(cd \"$(dirname \"$linuxdeploy_path\")\" && pwd)/$(basename \"$linuxdeploy_path\")\nlinuxdeploy_qt_path_abs=$(cd \"$(dirname \"$linuxdeploy_qt_path\")\" && pwd)/$(basename \"$linuxdeploy_qt_path\")\n\nif [ ! -f \"$linuxdeploy_path_abs\" ] || [ ! -f \"$linuxdeploy_qt_path_abs\" ]; then\n    echo \"error: linuxdeploy tools not found\"\n    exit 1\nfi\n\nlinuxdeploy_path=\"$linuxdeploy_path_abs\"\nlinuxdeploy_qt_path=\"$linuxdeploy_qt_path_abs\"\n\nexport QMAKE=\"$ENV_QT_PATH/gcc_64/bin/qmake\"\nexport QML_SOURCES_PATHS=\"$project_root/QtScrcpy\"\nexport DEPLOY_CMD=\"$linuxdeploy_path\"\nexport PATH=\"$ENV_QT_PATH/gcc_64/bin:$PATH\"\n\n# Pre-copy Qt plugins and libraries for CI environment\nif [ \"$IS_DOCKER_OR_CI\" = true ]; then\n    qt_plugins_source=\"$ENV_QT_PATH/gcc_64/plugins\"\n    qt_plugins_target=\"$appdir_path/usr/plugins\"\n    qt_libs_source=\"$ENV_QT_PATH/gcc_64/lib\"\n    qt_libs_target=\"$appdir_path/usr/lib\"\n\n    if [ -d \"$qt_plugins_source/platforms\" ]; then\n        mkdir -p \"$qt_plugins_target/platforms\"\n        cp -r \"$qt_plugins_source/platforms\"/* \"$qt_plugins_target/platforms/\" 2>/dev/null || true\n    fi\n\n    if [ -d \"$qt_libs_source\" ]; then\n        mkdir -p \"$qt_libs_target\"\n        for lib in \"libQt5XcbQpa.so.5\" \"libQt5XcbQpa.so\"; do\n            if [ -f \"$qt_libs_source/$lib\" ]; then\n                cp \"$qt_libs_source/$lib\" \"$qt_libs_target/\" 2>/dev/null || true\n            fi\n        done\n    fi\nfi\n\n# Run linuxdeploy\ncd \"$project_root\"\nexport LINUXDEPLOY_PLUGIN_QT_PATH=\"$linuxdeploy_qt_path\"\n\nlinuxdeploy_args=(\n    --appdir \"$appdir_path\"\n    --plugin qt\n    --output appimage\n    --executable \"$appdir_path/usr/bin/$app_name\"\n    --desktop-file \"$appdir_path/usr/share/applications/$app_name.desktop\"\n)\n\nif [ -n \"$icon_file\" ] && [ -f \"$icon_file\" ]; then\n    linuxdeploy_args+=(--icon-file \"$icon_file\")\nfi\n\n\"$linuxdeploy_path\" \"${linuxdeploy_args[@]}\" || {\n    if [ -f \"$appdir_path/usr/share/metainfo/$app_name.appdata.xml\" ]; then\n        mv \"$appdir_path/usr/share/metainfo/$app_name.appdata.xml\" \"$appdir_path/usr/share/metainfo/$app_name.appdata.xml.bak\"\n        if \"$linuxdeploy_path\" \"${linuxdeploy_args[@]}\"; then\n            rm -f \"$appdir_path/usr/share/metainfo/$app_name.appdata.xml.bak\"\n        else\n            mv \"$appdir_path/usr/share/metainfo/$app_name.appdata.xml.bak\" \"$appdir_path/usr/share/metainfo/$app_name.appdata.xml\"\n            linuxdeploy_args_no_plugin=(\n                --appdir \"$appdir_path\"\n                --output appimage\n                --executable \"$appdir_path/usr/bin/$app_name\"\n                --desktop-file \"$appdir_path/usr/share/applications/$app_name.desktop\"\n            )\n            [ -n \"$icon_file\" ] && [ -f \"$icon_file\" ] && linuxdeploy_args_no_plugin+=(--icon-file \"$icon_file\")\n            \"$linuxdeploy_path\" \"${linuxdeploy_args_no_plugin[@]}\" || exit 1\n        fi\n    else\n        linuxdeploy_args_no_plugin=(\n            --appdir \"$appdir_path\"\n            --output appimage\n            --executable \"$appdir_path/usr/bin/$app_name\"\n            --desktop-file \"$appdir_path/usr/share/applications/$app_name.desktop\"\n        )\n        [ -n \"$icon_file\" ] && [ -f \"$icon_file\" ] && linuxdeploy_args_no_plugin+=(--icon-file \"$icon_file\")\n        \"$linuxdeploy_path\" \"${linuxdeploy_args_no_plugin[@]}\" || exit 1\n    fi\n}\n\n# Verify Qt plugins for CI environment\nif [ \"$IS_DOCKER_OR_CI\" = true ]; then\n    qt_plugins_dir=\"$appdir_path/usr/plugins\"\n    qt_platforms_dir=\"$qt_plugins_dir/platforms\"\n    if [ ! -d \"$qt_platforms_dir\" ] || [ -z \"$(find \"$qt_platforms_dir\" -name \"*.so\" -type f 2>/dev/null)\" ]; then\n        if [ -n \"$ENV_QT_PATH\" ] && [ -d \"$ENV_QT_PATH/gcc_64/plugins/platforms\" ]; then\n            mkdir -p \"$qt_platforms_dir\"\n            cp -r \"$ENV_QT_PATH/gcc_64/plugins/platforms\"/* \"$qt_platforms_dir/\" 2>/dev/null || true\n        fi\n    fi\nfi\n\n# Find generated AppImage\nappimage_file=\"\"\nappimage_file=$(find \"$project_root\" -maxdepth 1 -name \"*.AppImage\" -type f 2>/dev/null | grep -v \"linuxdeploy\" | grep -v \"plugin\" | head -n 1)\n\nif [ -z \"$appimage_file\" ] || [ ! -f \"$appimage_file\" ]; then\n    appimage_file=$(find \"$(dirname \"$appdir_path\")\" -maxdepth 1 -name \"*.AppImage\" -type f 2>/dev/null | grep -v \"linuxdeploy\" | grep -v \"plugin\" | head -n 1)\nfi\n\nif [ -n \"$appimage_file\" ] && [ -f \"$appimage_file\" ]; then\n    final_appimage=\"$appimage_output_path/${app_name}-x86_64.AppImage\"\n    mv \"$appimage_file\" \"$final_appimage\"\n    echo \"AppImage created: $final_appimage ($(du -h \"$final_appimage\" | cut -f1))\"\nelse\n    echo \"error: AppImage file not found\"\n    exit 1\nfi\n\ncd \"$old_cd\"\necho \"AppImage packaging completed successfully!\"\nexit 0\n"
  },
  {
    "path": "ci/linux/publish_for_ubuntu.sh.todo",
    "content": "echo\necho\necho ---------------------------------------------------------------\necho check ENV\necho ---------------------------------------------------------------\n\n# 从环境变量获取必要参数\n# 例如 /home/barry/Qt5.9.6/5.9.6/gcc_64\necho ENV_QT_GCC $ENV_QT_GCC\n\n# 获取绝对路径，保证其他目录执行此脚本依然正确\n{\ncd $(dirname \"$0\")\nscript_path=$(pwd)\ncd -\n} &> /dev/null # disable output\n# 设置当前目录，cd的目录影响接下来执行程序的工作目录\nold_cd=$(pwd)\ncd $(dirname \"$0\")\n\n# 启动参数声明\npublish_dir=$1\n\n# 提示\necho current publish dir: $publish_dir\n\n# 环境变量设置\nkeymap_path=$script_path/../../keymap\n# config_path=$script_path/../../config\n\npublish_path=$script_path/$publish_dir\nrelease_path=$script_path/../../output/linux/release\n\nexport PATH=$ENV_QT_GCC/bin:$PATH\n\nif [ -d \"$publish_path\" ]; then\n    rm -rf $publish_path\nfi\n\n# 复制要发布的包\ncp -r $release_path $publish_path\ncp -r $keymap_path $publish_path/QtScrcpy.app/Contents/MacOS\n# cp -r $config_path $publish_path/QtScrcpy.app/Contents/MacOS\n\n# 添加qt依赖包\nmacdeployqt $publish_path/QtScrcpy.app\n\n# 删除多余qt依赖包\n\n# PlugIns\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/iconengines\n# 截图功能需要libqjpeg.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqgif.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqicns.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqico.dylib\n# rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqjpeg.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqmacheif.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqmacjp2.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqtga.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqtiff.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqwbmp.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqwebp.dylib\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/virtualkeyboard\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/printsupport\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/platforminputcontexts\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/iconengines\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/bearer\n\n# Frameworks\nrm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtVirtualKeyboard.framework\nrm -rf $publish_path/Contents/Frameworks/QtSvg.framework\n\n# qml\nrm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtQml.framework\nrm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtQuick.framework\n\necho\necho\necho ---------------------------------------------------------------\necho finish!!!\necho ---------------------------------------------------------------\n\n# 恢复当前目录\ncd $old_cd\nexit 0\n"
  },
  {
    "path": "ci/lrelease.sh",
    "content": "# https://doc.qt.io/qt-5/linguist-manager.html#lrelease\n# lrelease -help\nlrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts ./QtScrcpy/res/i18n/ja_JP.ts\n"
  },
  {
    "path": "ci/lupdate.sh",
    "content": "# https://doc.qt.io/qt-5/linguist-manager.html#lupdate\n# lupdate -help\n# export PATH=/D/Qt/5.15.2/msvc2019/bin:$PATH\nlupdate -no-obsolete ./QtScrcpy -ts ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts ./QtScrcpy/res/i18n/ja_JP.ts\n"
  },
  {
    "path": "ci/mac/build_for_mac.sh",
    "content": "\necho\necho\necho ---------------------------------------------------------------\necho check ENV\necho ---------------------------------------------------------------\n\n# 从环境变量获取必要参数\n# 例如 /Users/barry/Qt5.12.5/5.12.5\necho ENV_QT_PATH $ENV_QT_PATH\n\n# 获取绝对路径，保证其他目录执行此脚本依然正确\n{\ncd $(dirname \"$0\")\nscript_path=$(pwd)\ncd -\n} &> /dev/null # disable output\n# 设置当前目录，cd的目录影响接下来执行程序的工作目录\nold_cd=$(pwd)\ncd $(dirname \"$0\")\n\n# 启动参数声明\nbuild_mode=RelWithDebInfo\ncpu_arch=arm64\n\necho\necho\necho ---------------------------------------------------------------\necho check build param[Debug/Release/MinSizeRel/RelWithDebInfo]\necho ---------------------------------------------------------------\n\n# 编译参数检查\nbuild_mode=$(echo $1)\nif [[ $build_mode != \"Release\" && $build_mode != \"Debug\" && $build_mode != \"MinSizeRel\" && $build_mode != \"RelWithDebInfo\" ]]; then\n    echo \"error: unkonow build mode -- $1\"\n    exit 1\nfi\n\necho\necho\necho ---------------------------------------------------------------\necho check cpu arch[x64/arm64]\necho ---------------------------------------------------------------\n\ncpu_arch=$(echo $2)\nif [[ $cpu_arch != \"x64\" && $cpu_arch != \"arm64\" ]]; then\n    echo \"error: unkonow cpu mode -- $2\"\n    exit 1\nfi\n\n# 提示\necho current build mode: $build_mode\necho current cpu mode: $cpu_arch\n\ncmake_arch=x86_64\nif [ $cpu_arch == \"x64\" ]; then\n    qt_cmake_path=$ENV_QT_PATH/clang_64/lib/cmake/Qt5\n    cmake_arch=x86_64\nelse\n    qt_cmake_path=$ENV_QT_PATH/macos/lib/cmake/Qt6\n    cmake_arch=arm64\nfi\n\necho\necho\necho ---------------------------------------------------------------\necho begin cmake build\necho ---------------------------------------------------------------\n\n# 删除输出目录\noutput_path=$script_path../../output\nif [ -d \"$output_path\" ]; then\n    rm -rf $output_path\nfi\n# 删除编译目录\nbuild_path=$script_path/../build_temp\nif [ -d \"$build_path\" ]; then\n    rm -rf $build_path\nfi\nmkdir $build_path\ncd $build_path\n\ncmake_params=\"-DCMAKE_PREFIX_PATH=$qt_cmake_path -DCMAKE_BUILD_TYPE=$build_mode -DCMAKE_OSX_ARCHITECTURES=$cmake_arch\"\ncmake $cmake_params ../..\nif [ $? -ne 0 ] ;then\n    echo \"cmake failed\"\n    exit 1\nfi\n\ncmake --build . --config $build_mode -j8\nif [ $? -ne 0 ] ;then\n    echo \"cmake build failed\"\n    exit 1\nfi\n\necho\necho\necho ---------------------------------------------------------------\necho finish!!!\necho ---------------------------------------------------------------\n\n# 恢复当前目录\ncd $old_cd\nexit 0\n"
  },
  {
    "path": "ci/mac/package/dmg-settings.json",
    "content": "{\"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\"}]}"
  },
  {
    "path": "ci/mac/package/package.py",
    "content": "import dmgbuild\nimport os\nimport json\nimport sys\n\ncurrent_file_path = os.path.dirname(os.path.realpath(__file__))\ndmg_settings_path = '%s/dmg-settings.json' % current_file_path\ndmg_background_img = '%s/dmg-background.jpg' % current_file_path\napp_path = '%s/../../build/QtScrcpy.app' % current_file_path\ndmg_path = '%s/../../build/QtScrcpy.dmg' % current_file_path\napp_name = 'QtScrcpy'\n\ndef console_print(msg):\n    print(msg)\n    sys.stdout.flush()\n\ndef generate_dmg_info():\n    with open(dmg_settings_path, 'w') as file:\n        info = {\n            'title': app_name,\n            'icon-size': 120,\n            'background': dmg_background_img,\n            'format': 'UDZO',\n            'compression-level': 9,\n            'window': {\n                'position': {'x': 400, 'y': 200},\n                'size': {'width': 780, 'height': 480}\n                    },\n            'contents': [\n                 {\n                 'x': 223,\n                 'y': 227,\n                 'type': 'file',\n                 'path': app_path\n                 },\n                 {\n                 'x': 550,\n                 'y': 227,\n                 'type': 'link',\n                 'path': '/Applications'\n                 }\n                 ]\n                }\n        json.dump(info, file)\n\nif __name__ == '__main__':\n    console_print('generate dmg info')\n    generate_dmg_info()\n    console_print('build dmg: %s' % dmg_path)\n    dmgbuild.build_dmg(dmg_path, app_name, dmg_settings_path)\n    if not os.path.exists(dmg_path):\n        console_print('fail to create %s' % dmg_path)\n        sys.exit(1)\n    \n    sys.exit(0)"
  },
  {
    "path": "ci/mac/package/requirements.txt",
    "content": "dmgbuild==1.4.2"
  },
  {
    "path": "ci/mac/package_for_mac.sh",
    "content": "# 获取绝对路径，保证其他目录执行此脚本依然正确\n{\ncd $(dirname \"$0\")\nscript_path=$(pwd)\ncd -\n} &> /dev/null # disable output\n# 设置当前目录，cd的目录影响接下来执行程序的工作目录\nold_cd=$(pwd)\ncd $(dirname \"$0\")\n\necho\necho\necho ---------------------------------------------------------------\necho pip install requirements\necho ---------------------------------------------------------------\n\npip install -r $script_path/package/requirements.txt\nif [ $? -ne 0 ] ;then\n    echo \"pip install requirements failed\"\n    exit 1\nfi\n\necho\necho\necho ---------------------------------------------------------------\necho create package\necho ---------------------------------------------------------------\n\npython $script_path/package/package.py\nif [ $? -ne 0 ] ;then\n    echo \"create package failed\"\n    exit 1\nfi\n\n# 恢复当前目录\ncd $old_cd\nexit 0\n"
  },
  {
    "path": "ci/mac/publish_for_mac.sh",
    "content": "echo\necho\necho ---------------------------------------------------------------\necho check ENV\necho ---------------------------------------------------------------\n\n# 从环境变量获取必要参数\n# 例如 /Users/barry/Qt5.12.5/5.12.5\necho ENV_QT_PATH $ENV_QT_PATH\n\n# 获取绝对路径，保证其他目录执行此脚本依然正确\n{\ncd $(dirname \"$0\")\nscript_path=$(pwd)\ncd -\n} &> /dev/null # disable output\n# 设置当前目录，cd的目录影响接下来执行程序的工作目录\nold_cd=$(pwd)\ncd $(dirname \"$0\")\n\n# 启动参数声明\npublish_dir=$1\ncpu_arch=$2\n\necho\necho\necho ---------------------------------------------------------------\necho check cpu arch[x64/arm64]\necho ---------------------------------------------------------------\n\nif [[ $cpu_arch != \"x64\" && $cpu_arch != \"arm64\" ]]; then\n    echo \"error: unkonow cpu mode -- $2\"\n    exit 1\nfi\n\n# 提示\necho current cpu mode: $cpu_arch\n\nif [ $cpu_arch == \"x64\" ]; then\n    qt_clang_path=$ENV_QT_PATH/clang_64\nelse\n    qt_clang_path=$ENV_QT_PATH/macos\nfi\n\n# 提示\necho current publish dir: $publish_dir\n\n# 环境变量设置\nkeymap_path=$script_path/../../keymap\n# config_path=$script_path/../../config\n\npublish_path=$script_path/$publish_dir\nrelease_path=$script_path/../../output/$cpu_arch/RelWithDebInfo\n\nexport PATH=$qt_clang_path/bin:$PATH\n\nif [ -d \"$publish_path\" ]; then\n    rm -rf $publish_path\nfi\n\n# 复制要发布的包\ncp -r $release_path $publish_path\ncp -r $keymap_path $publish_path/QtScrcpy.app/Contents/MacOS\n# cp -r $config_path $publish_path/QtScrcpy.app/Contents/MacOS\n\n# 添加qt依赖包\nmacdeployqt $publish_path/QtScrcpy.app\n\n# 删除多余qt依赖包\n\n# PlugIns\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/iconengines\n# 截图功能需要libqjpeg.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqgif.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqicns.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqico.dylib\n# rm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqjpeg.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqmacheif.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqmacjp2.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqtga.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqtiff.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqwbmp.dylib\nrm -f $publish_path/QtScrcpy.app/Contents/PlugIns/imageformats/libqwebp.dylib\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/virtualkeyboard\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/printsupport\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/platforminputcontexts\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/iconengines\nrm -rf $publish_path/QtScrcpy.app/Contents/PlugIns/bearer\n\n# Frameworks\nrm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtVirtualKeyboard.framework\nrm -rf $publish_path/Contents/Frameworks/QtSvg.framework\n\n# qml\nrm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtQml.framework\nrm -rf $publish_path/QtScrcpy.app/Contents/Frameworks/QtQuick.framework\n\necho\necho\necho ---------------------------------------------------------------\necho finish!!!\necho ---------------------------------------------------------------\n\n# 恢复当前目录\ncd $old_cd\nexit 0\n"
  },
  {
    "path": "ci/win/build_for_win.bat",
    "content": "@echo off\r\n\r\necho=\r\necho=\r\necho ---------------------------------------------------------------\r\necho check ENV\r\necho ---------------------------------------------------------------\r\n\r\n:: 从环境变量获取必要参数\r\n:: example: D:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional\\VC\\Auxiliary\\Build\\vcvarsall.bat\r\n:: set vcvarsall=\"%ENV_VCVARSALL%\"\r\n\r\n:: example: D:\\Qt\\Qt5.12.5\\5.12.5\r\n:: echo ENV_VCVARSALL %ENV_VCVARSALL%\r\necho ENV_QT_PATH %ENV_QT_PATH%\r\n\r\n:: 获取脚本绝对路径\r\nset script_path=%~dp0\r\n:: 进入脚本所在目录,因为这会影响脚本中执行的程序的工作目录\r\nset old_cd=%cd%\r\ncd /d %~dp0\r\n\r\n:: 启动参数声明\r\nset cpu_mode=x86\r\nset build_mode=RelWithDebInfo\r\nset errno=1\r\n\r\necho=\r\necho=\r\necho ---------------------------------------------------------------\r\necho check build param[Debug/Release/MinSizeRel/RelWithDebInfo]\r\necho ---------------------------------------------------------------\r\n\r\n:: 编译参数检查\r\nif \"%1\"==\"Debug\" (    \r\n    goto build_mode_ok\r\n)\r\nif \"%1\"==\"Release\" (    \r\n    goto build_mode_ok\r\n)\r\nif \"%1\"==\"MinSizeRel\" (    \r\n    goto build_mode_ok\r\n)\r\nif \"%1\"==\"RelWithDebInfo\" (\r\n    goto build_mode_ok\r\n)\r\necho error: unknown build mode -- %1\r\ngoto return\r\n:build_mode_ok\r\n\r\nset build_mode=%1\r\nset cmake_vs_build_mode=Win32\r\nset qt_cmake_path=%ENV_QT_PATH%\\msvc2019\\lib\\cmake\\Qt5\r\n\r\nif /i \"%2\"==\"x86\" (\r\n    set cpu_mode=x86\r\n    set cmake_vs_build_mode=Win32\r\n    set qt_cmake_path=%ENV_QT_PATH%\\msvc2019\\lib\\cmake\\Qt5\r\n)\r\nif /i \"%2\"==\"x64\" (\r\n    set cpu_mode=x64\r\n    set cmake_vs_build_mode=x64\r\n    set qt_cmake_path=%ENV_QT_PATH%\\msvc2019_64\\lib\\cmake\\Qt5\r\n)\r\n\r\n:: 提示\r\necho current build mode: %build_mode% %cpu_mode%\r\necho qt cmake path: %qt_cmake_path%\r\n\r\necho=\r\necho=\r\necho ---------------------------------------------------------------\r\necho begin cmake build\r\necho ---------------------------------------------------------------\r\n\r\n:: 删除输出目录\r\nset output_path=%script_path%..\\..\\output\r\nif exist %output_path% (          \r\n    rmdir /q /s %output_path%\r\n)\r\n:: 删除临时目录\r\nset temp_path=%script_path%..\\build_temp\r\nif exist %temp_path% (          \r\n    rmdir /q /s %temp_path%\r\n)\r\nmd %temp_path%\r\ncd %temp_path%\r\n\r\nset cmake_params=-DCMAKE_PREFIX_PATH=%qt_cmake_path% -DCMAKE_BUILD_TYPE=%build_mode% -G \"Visual Studio 17 2022\" -A %cmake_vs_build_mode%\r\necho cmake params: %cmake_params%\r\n\r\ncmake %cmake_params% ../..\r\nif not %errorlevel%==0 (\r\n    echo \"cmake failed\"\r\n    goto return\r\n)\r\n\r\ncmake --build . --config %build_mode% -j8\r\nif not %errorlevel%==0 (\r\n    echo \"cmake build failed\"\r\n    goto return\r\n)\r\n\r\necho=\r\necho=\r\necho ---------------------------------------------------------------\r\necho finish!!!\r\necho ---------------------------------------------------------------\r\n\r\nset errno=0\r\n\r\n:return\r\ncd %old_cd%\r\nexit /B %errno%\r\n"
  },
  {
    "path": "ci/win/publish_for_win.bat",
    "content": "@echo off\n\necho=\necho=\necho ---------------------------------------------------------------\necho check ENV\necho ---------------------------------------------------------------\n\n:: 从环境变量获取必要参数\n:: example: D:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional\\VC\\Auxiliary\\Build\\vcvarsall.bat\nset vcvarsall=\"%ENV_VCVARSALL%\"\n:: 例如 d:\\a\\QtScrcpy\\Qt\\5.12.7\nset qt_msvc_path=\"%ENV_QT_PATH%\"\n:: 设置了VCINSTALLDIR，windeployqt会自动copy vc_redist.x**.exe(vcruntime dll安装包)\n:: set VCINSTALLDIR=\"%ENV_VCINSTALL%\"\n\necho ENV_VCVARSALL %ENV_VCVARSALL%\necho ENV_QT_PATH %ENV_QT_PATH%\n\n:: 获取脚本绝对路径\nset script_path=%~dp0\n:: 进入脚本所在目录,因为这会影响脚本中执行的程序的工作目录\nset old_cd=%cd%\ncd /d %~dp0\n\n:: 启动参数声明\nset cpu_mode=x86\nset publish_dir=%2\nset errno=1\n\nif /i \"%1\"==\"x86\" (\n    set cpu_mode=x86\n)\nif /i \"%1\"==\"x64\" (\n    set cpu_mode=x64\n)\n\n:: 提示\necho current build mode: %cpu_mode%\necho current publish dir: %publish_dir%\n\n:: 环境变量设置\nset adb_path=%script_path%..\\..\\QtScrcpy\\QtScrcpyCore\\src\\third_party\\adb\\win\\*.*\nset jar_path=%script_path%..\\..\\QtScrcpy\\QtScrcpyCore\\src\\third_party\\scrcpy-server\nset keymap_path=%script_path%..\\..\\keymap\nset config_path=%script_path%..\\..\\config\n\nif /i %cpu_mode% == x86 (\n    set publish_path=%script_path%%publish_dir%\\\n    set release_path=%script_path%..\\..\\output\\x86\\RelWithDebInfo\n    set qt_msvc_path=%qt_msvc_path%\\msvc2019\\bin\n) else (\n    set publish_path=%script_path%%publish_dir%\\\n    set release_path=%script_path%..\\..\\output\\x64\\RelWithDebInfo\n    set qt_msvc_path=%qt_msvc_path%\\msvc2019_64\\bin\n)\nset PATH=%qt_msvc_path%;%PATH%\n\n:: 注册vc环境(注册以后，windeployqt会把vc_redist复制过来（vcruntime安装包）)\nif /i %cpu_mode% == x86 (\n    call %vcvarsall% %cpu_mode%\n) else (\n    call %vcvarsall% %cpu_mode%\n)\n\nif exist %publish_path% (\n    rmdir /s/q %publish_path%\n)\n\n:: 复制要发布的包\nxcopy %release_path% %publish_path% /E /Y\nxcopy %adb_path% %publish_path% /Y\nxcopy %jar_path% %publish_path% /Y\nxcopy %keymap_path% %publish_path%keymap\\ /E /Y\nxcopy %config_path% %publish_path%config\\ /E /Y\n\n:: 添加qt依赖包\nwindeployqt %publish_path%\\QtScrcpy.exe\n\n:: 删除多余qt依赖包\nrmdir /s/q %publish_path%\\iconengines\nrmdir /s/q %publish_path%\\translations\n\n:: 截图功能需要qjpeg.dll\ndel %publish_path%\\imageformats\\qgif.dll\ndel %publish_path%\\imageformats\\qicns.dll\ndel %publish_path%\\imageformats\\qico.dll\n::del %publish_path%\\imageformats\\qjpeg.dll\ndel %publish_path%\\imageformats\\qsvg.dll\ndel %publish_path%\\imageformats\\qtga.dll\ndel %publish_path%\\imageformats\\qtiff.dll\ndel %publish_path%\\imageformats\\qwbmp.dll\ndel %publish_path%\\imageformats\\qwebp.dll\n\n:: 删除vc_redist，自己copy vcruntime dll\nif /i %cpu_mode% == x86 (\n    del %publish_path%\\vc_redist.x86.exe\n) else (\n    del %publish_path%\\vc_redist.x64.exe\n)\n\n:: copy vcruntime dll\nif /i %cpu_mode% == x64 (\n    cp \"C:\\Windows\\System32\\msvcp140_1.dll\" %publish_path%\\msvcp140_1.dll\n    cp \"C:\\Windows\\System32\\msvcp140.dll\" %publish_path%\\msvcp140.dll\n    cp \"C:\\Windows\\System32\\vcruntime140.dll\" %publish_path%\\vcruntime140.dll\n    :: 只有x64需要\n    cp \"C:\\Windows\\System32\\vcruntime140_1.dll\" %publish_path%\\vcruntime140_1.dll\n) else (\n    cp \"C:\\Windows\\SysWOW64\\msvcp140_1.dll\" %publish_path%\\msvcp140_1.dll\n    cp \"C:\\Windows\\SysWOW64\\msvcp140.dll\" %publish_path%\\msvcp140.dll\n    cp \"C:\\Windows\\SysWOW64\\vcruntime140.dll\" %publish_path%\\vcruntime140.dll\n    \n)\n\n::cp \"C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\VCRUNTIME140.dll\" %publish_path%\\VCRUNTIME140.dll\n::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\n::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\n::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\n::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\n::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\n\necho=\necho=\necho ---------------------------------------------------------------\necho finish!!!\necho ---------------------------------------------------------------\n\nset errno=0\n\n:return\ncd %old_cd%\nexit /B %errno%"
  },
  {
    "path": "config/config.ini",
    "content": "﻿[common]\n# 语言 Auto=自动，zh_CN=简体中文，en_US=English\nLanguage=Auto\n# 窗口标题\nWindowTitle=QtScrcpy\n# 推送到安卓设备的文件保存路径（必须以/结尾）\nPushFilePath=/sdcard/\n# 最大fps（仅支持Android 10以上）\nMaxFps=0\n# 是否渲染过期视频帧（跳过过期视频帧意味着更低的延迟）\nRenderExpiredFrames=0\n# 视频解码方式：-1 自动，0 软解，1 dx硬解，2 opengl硬解\nUseDesktopOpenGL=-1\n# scrcpy-server推送到安卓设备的路径\nServerPath=/data/local/tmp/scrcpy-server.jar\n# 自定义adb路径，例如D:/android/tools/adb.exe\nAdbPath=\n# 编码选项 \"\"表示默认\n# 例如 CodecOptions=\"profile=1,level=2\"\n# 更多编码选项参考 https://d.android.com/reference/android/media/MediaFormat\nCodecOptions=\"\"\n# 指定编码器名称(必须是H.264编码器)，\"\"表示默认\n# 例如 CodecName=\"OMX.qcom.video.encoder.avc\"\nCodecName=\"\"\n\n# Set the log level (verbose, debug, info, warn, error)\nLogLevel=verbose\n"
  },
  {
    "path": "docs/DEVELOP.md",
    "content": "# scrcpy for developers\n\n## Overview\n\nThis application is composed of two parts:\n - the server (`scrcpy-server`), to be executed on the device,\n - the client (the `scrcpy` binary), executed on the host computer.\n\nThe client is responsible to push the server to the device and start its\nexecution.\n\nOnce the client and the server are connected to each other, the server initially\nsends device information (name and initial screen dimensions), then starts to\nsend a raw H.264 video stream of the device screen. The client decodes the video\nframes, and display them as soon as possible, without buffering, to minimize\nlatency. The client is not aware of the device rotation (which is handled by the\nserver), it just knows the dimensions of the video frames.\n\nThe client captures relevant keyboard and mouse events, that it transmits to the\nserver, which injects them to the device.\n\n\n\n## Server\n\n\n### Privileges\n\nCapturing the screen requires some privileges, which are granted to `shell`.\n\nThe server is a Java application (with a [`public static void main(String...\nargs)`][main] method), compiled against the Android framework, and executed as\n`shell` on the Android device.\n\n[main]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/Server.java#L123\n\nTo run such a Java application, the classes must be [_dexed_][dex] (typically,\nto `classes.dex`). If `my.package.MainClass` is the main class, compiled to\n`classes.dex`, pushed to the device in `/data/local/tmp`, then it can be run\nwith:\n\n    adb shell CLASSPATH=/data/local/tmp/classes.dex \\\n        app_process / my.package.MainClass\n\n_The path `/data/local/tmp` is a good candidate to push the server, since it's\nreadable and writable by `shell`, but not world-writable, so a malicious\napplication may not replace the server just before the client executes it._\n\nInstead of a raw _dex_ file, `app_process` accepts a _jar_ containing\n`classes.dex` (e.g. an [APK]). For simplicity, and to benefit from the gradle\nbuild system, the server is built to an (unsigned) APK (renamed to\n`scrcpy-server`).\n\n[dex]: https://en.wikipedia.org/wiki/Dalvik_(software)\n[apk]: https://en.wikipedia.org/wiki/Android_application_package\n\n\n### Hidden methods\n\nAlthough compiled against the Android framework, [hidden] methods and classes are\nnot directly accessible (and they may differ from one Android version to\nanother).\n\nThey can be called using reflection though. The communication with hidden\ncomponents is provided by [_wrappers_ classes][wrappers] and [aidl].\n\n[hidden]: https://stackoverflow.com/a/31908373/1987178\n[wrappers]: https://github.com/Genymobile/scrcpy/tree/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/wrappers\n[aidl]: https://github.com/Genymobile/scrcpy/tree/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/aidl/android/view\n\n\n### Threading\n\nThe server uses 3 threads:\n\n - the **main** thread, encoding and streaming the video to the client;\n - the **controller** thread, listening for _control messages_ (typically,\n   keyboard and mouse events) from the client;\n - the **receiver** thread (managed by the controller), sending _device messges_\n   to the clients (currently, it is only used to send the device clipboard\n   content).\n\nSince the video encoding is typically hardware, there would be no benefit in\nencoding and streaming in two different threads.\n\n\n### Screen video encoding\n\nThe encoding is managed by [`ScreenEncoder`].\n\nThe video is encoded using the [`MediaCodec`] API. The codec takes its input\nfrom a [surface] associated to the display, and writes the resulting H.264\nstream to the provided output stream (the socket connected to the client).\n\n[`ScreenEncoder`]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java\n[`MediaCodec`]: https://developer.android.com/reference/android/media/MediaCodec.html\n[surface]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L68-L69\n\nOn device [rotation], the codec, surface and display are reinitialized, and a\nnew video stream is produced.\n\nNew frames are produced only when changes occur on the surface. This is good\nbecause it avoids to send unnecessary frames, but there are drawbacks:\n\n - it does not send any frame on start if the device screen does not change,\n - after fast motion changes, the last frame may have poor quality.\n\nBoth problems are [solved][repeat] by the flag\n[`KEY_REPEAT_PREVIOUS_FRAME_AFTER`][repeat-flag].\n\n[rotation]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L90\n[repeat]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/ScreenEncoder.java#L147-L148\n[repeat-flag]: https://developer.android.com/reference/android/media/MediaFormat.html#KEY_REPEAT_PREVIOUS_FRAME_AFTER\n\n\n### Input events injection\n\n_Control messages_ are received from the client by the [`Controller`] (run in a\nseparate thread). There are several types of input events:\n - keycode (cf [`KeyEvent`]),\n - text (special characters may not be handled by keycodes directly),\n - mouse motion/click,\n - mouse scroll,\n - other commands (e.g. to switch the screen on or to copy the clipboard).\n\nSome of them need to inject input events to the system. To do so, they use the\n_hidden_ method [`InputManager.injectInputEvent`] (exposed by our\n[`InputManager` wrapper][inject-wrapper]).\n\n[`Controller`]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/Controller.java#L81\n[`KeyEvent`]: https://developer.android.com/reference/android/view/KeyEvent.html\n[`MotionEvent`]: https://developer.android.com/reference/android/view/MotionEvent.html\n[`InputManager.injectInputEvent`]: https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/hardware/input/InputManager.java#857\n[inject-wrapper]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/server/src/main/java/com/genymobile/scrcpy/wrappers/InputManager.java#L27\n\n\n\n## Client\n\nThe client relies on [SDL], which provides cross-platform API for UI, input\nevents, threading, etc.\n\nThe video stream is decoded by [libav] (FFmpeg).\n\n[SDL]: https://www.libsdl.org\n[libav]: https://www.libav.org/\n\n### Initialization\n\nOn startup, in addition to _libav_ and _SDL_ initialization, the client must\npush and start the server on the device, and open two sockets (one for the video\nstream, one for control) so that they may communicate.\n\nNote that the client-server roles are expressed at the application level:\n\n - the server _serves_ video stream and handle requests from the client,\n - the client _controls_ the device through the server.\n\nHowever, the roles are reversed at the network level:\n\n - the client opens a server socket and listen on a port before starting the\n   server,\n - the server connects to the client.\n\nThis role inversion guarantees that the connection will not fail due to race\nconditions, and avoids polling.\n\n_(Note that over TCP/IP, the roles are not reversed, due to a bug in `adb\nreverse`. See commit [1038bad] and [issue #5].)_\n\nOnce the server is connected, it sends the device information (name and initial\nscreen dimensions). Thus, the client may init the window and renderer, before\nthe first frame is available.\n\nTo minimize startup time, SDL initialization is performed while listening for\nthe connection from the server (see commit [90a46b4]).\n\n[1038bad]: https://github.com/Genymobile/scrcpy/commit/1038bad3850f18717a048a4d5c0f8110e54ee172\n[issue #5]: https://github.com/Genymobile/scrcpy/issues/5\n[90a46b4]: https://github.com/Genymobile/scrcpy/commit/90a46b4c45637d083e877020d85ade52a9a5fa8e\n\n\n### Threading\n\nThe client uses 4 threads:\n\n - the **main** thread, executing the SDL event loop,\n - the **stream** thread, receiving the video and used for decoding and\n   recording,\n - the **controller** thread, sending _control messages_ to the server,\n - the **receiver** thread (managed by the controller), receiving _device\n   messages_ from the server.\n\nIn addition, another thread can be started if necessary to handle APK\ninstallation or file push requests (via drag&drop on the main window) or to\nprint the framerate regularly in the console.\n\n\n\n### Stream\n\nThe video [stream] is received from the socket (connected to the server on the\ndevice) in a separate thread.\n\nIf a [decoder] is present (i.e. `--no-display` is not set), then it uses _libav_\nto decode the H.264 stream from the socket, and notifies the main thread when a\nnew frame is available.\n\nThere are two [frames][video_buffer] simultaneously in memory:\n - the **decoding** frame, written by the decoder from the decoder thread,\n - the **rendering** frame, rendered in a texture from the main thread.\n\nWhen a new decoded frame is available, the decoder _swaps_ the decoding and\nrendering frame (with proper synchronization). Thus, it immediatly starts\nto decode a new frame while the main thread renders the last one.\n\nIf a [recorder] is present (i.e. `--record` is enabled), then it muxes the raw\nH.264 packet to the output video file.\n\n[stream]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/stream.h\n[decoder]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/decoder.h\n[video_buffer]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/video_buffer.h\n[recorder]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/recorder.h\n\n```\n                                   +----------+      +----------+\n                              ---> | decoder  | ---> |  screen  |\n             +---------+     /     +----------+      +----------+\n socket ---> | stream  | ----\n             +---------+     \\     +----------+\n                              ---> | recorder |\n                                   +----------+\n```\n\n### Controller\n\nThe [controller] is responsible to send _control messages_ to the device. It\nruns in a separate thread, to avoid I/O on the main thread.\n\nOn SDL event, received on the main thread, the [input manager][inputmanager]\ncreates appropriate [_control messages_][controlmsg]. It is responsible to\nconvert SDL events to Android events (using [convert]). It pushes the _control\nmessages_ to a queue hold by the controller. On its own thread, the controller\ntakes messages from the queue, that it serializes and sends to the client.\n\n[controller]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/controller.h\n[controlmsg]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/control_msg.h\n[inputmanager]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/input_manager.h\n[convert]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/convert.h\n\n\n### UI and event loop\n\nInitialization, input events and rendering are all [managed][scrcpy] in the main\nthread.\n\nEvents are handled in the [event loop], which either updates the [screen] or\ndelegates to the [input manager][inputmanager].\n\n[scrcpy]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/scrcpy.c\n[event loop]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/scrcpy.c#L201\n[screen]: https://github.com/Genymobile/scrcpy/blob/ffe0417228fb78ab45b7ee4e202fc06fc8875bf3/app/src/screen.h\n\n\n## Hack\n\nFor more details, go read the code!\n\nIf you find a bug, or have an awesome idea to implement, please discuss and\ncontribute ;-)\n\n\n### Debug the server\n\nThe server is pushed to the device by the client on startup.\n\nTo debug it, enable the server debugger during configuration:\n\n```bash\nmeson x -Dserver_debugger=true\n# or, if x is already configured\nmeson configure x -Dserver_debugger=true\n```\n\nIf your device runs Android 8 or below, set the `server_debugger_method` to\n`old` in addition:\n\n```bash\nmeson x -Dserver_debugger=true -Dserver_debugger_method=old\n# or, if x is already configured\nmeson configure x -Dserver_debugger=true -Dserver_debugger_method=old\n```\n\nThen recompile.\n\nWhen you start scrcpy, it will start a debugger on port 5005 on the device.\nRedirect that port to the computer:\n\n```bash\nadb forward tcp:5005 tcp:5005\n```\n\nIn Android Studio, _Run_ > _Debug_ > _Edit configurations..._ On the left, click on\n`+`, _Remote_, and fill the form:\n\n - Host: `localhost`\n - Port: `5005`\n\nThen click on _Debug_.\n"
  },
  {
    "path": "docs/FAQ.md",
    "content": "# Frequently Asked Questions\n一些经常问的问题\n\n如果在此文档没有解决你的问题，描述你的问题，截图软件控制台中打印的日志，一起发到QQ群里提问。\n\n# adb问题\n## ADB版本之间的冲突\n```\nadb server version (41) doesn't match this client (39); killing...\n```\n当你的电脑中运行不同版本的adb时，会发生此错误。你必须保证所有程序使用相同版本的adb。\n现在你有两个办法解决这个问题：\n1. 任务管理器找到adb进程并杀死\n2. 配置QtScrcpy的config.ini中的AdbPath路径指向当前使用的adb\n\n## 手机通过数据线连接电脑，刷新设备列表以后，没有任何设备出现\n随便下载一个手机助手，尝试连接成功以后，再用QtScrcpy刷新设备列表连接\n\n# 控制问题\n## 可以看到画面，但无法控制\n有些手机(小米等手机)需要额外打开控制权限，检查是否USB调试里打开了允许模拟点击\n\n![image](image/USB调试(安全设置).jpg)\n\n# 其它\n## 支持声音（软件不做支持）\n[关于转发安卓声音到PC的讨论](https://github.com/Genymobile/scrcpy/issues/14#issuecomment-543204526)\n\n## 画面不清晰\n在Windows上，您可能需要配置缩放行为。\n\nQtScrcpy.exe>属性>兼容性>更改高DPI设置>覆盖高DPI缩放行为>由以下人员执行缩放：应用程序。\n\n如果视频窗口大小远远小于设备屏幕的大小，则画面会不清晰。这在文字上尤其明显\n\n## 玩和平精英上下车操作会失效\n这是由于游戏中上车会创建新的界面，导致鼠标触摸点失效，目前技术上没有好的解决办法\n\n可以通过`连续按两次~键（数字键1左边）`来恢复，这是目前最好的办法。\n\n## 无法输入中文\n手机端安装搜狗输入法/QQ输入法就可以支持输入中文了\n\n## 可以控制，但无法看到画面\n控制台错误信息可能会包含 QOpenGLShaderProgram::attributeLocation(vertexIn): shader program is not linked\n\n一般是由于显卡不支持当前的视频渲染方式，config.ini里修改下解码方式，改成1或者2试试\n\n## 错误信息：AdbProcess::error:adb server version (40) doesnt match this client (41)\n任务管理找到adb进程并杀死，重新操作即可\n\n## 错误信息：Could not open video stream\n导致这个错误的原因有很多，最简单的解决方法是在分辨率设置中，选择一个较低的分辨率\n\n"
  },
  {
    "path": "docs/KeyMapDes.md",
    "content": "# Custom key mapping instructions\n\nThe 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.\n\nThe 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.\n\n## Key mapping script format description\n\n### General Instructions\n\n-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\nTaking the upper left corner of the screen as the origin, the position of the pixel coordinates (1920,1080)*(0.5,0.5)=(960,540).\n    \n    Or when the left mouse button clicks, the console will output the pos at this time, just use this pos directly\n    ![](image/debug-keymap-pos.png)\n\n-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).\n-Open the following two settings in the developer options, you can easily observe the coordinates of the touch point:\n![](image/display pointer position.jpg)\n\n### Mapping type description\n\n-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.\n\n-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).\nGenerally used to adjust the character field of vision in FPS mobile games.\n    -startPos finger drag starting point\n    -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.\n    -speedRatioX sensitivity of the mouse X-axis. This value must be at least 0.001.\n    -speedRatioY sensitivity of the mouse Y-axis. This value must be at least 0.001.\n    -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\n\n-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.\n\nThere are several types of key mapping as follows:\n\n-type The type of key mapping, each element in keyMapNodes needs to be specified, and can be of the following types:\n    -KMT_CLICK Ordinary click, key press simulates finger press, key lift simulates finger lift\n    -KMT_CLICK_TWICE Double click, key press simulates finger press and then lift, key lift simulates finger press and then lift\n    - KMT_CLICK_MULTI Click multiple times. According to the delay and pos in the clickNodes array, press one key to simulate touching multiple positions\n    -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\n    -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.\n\nDescription of the unique attributes of different key mapping types:\n\n-KMT_CLICK\n    -key The key code to be mapped\n    -pos simulates the location of the touch\n    -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)\n\n-KMT_CLICK_TWICE\n    -key The key code to be mapped\n    -pos Simulates the location of the touch\n\n-KMT_CLICK_MULTI\n    -delay Delay `delay` ms before simulating touch\n    -pos Simulates the location of the touch\n\n-KMT_DRAG\n    -key The key code to be mapped\n    -startPos Simulate the start position of touch drag\n    -endPos Simulate the end position of touch drag\n    -dragSpeed Speed of the drag movement (range 0-1, default 1.0). Higher values result in faster movements\n    -startDelay Optional delay in milliseconds to wait after the initial touch before starting the drag movement\n\n-KMT_STEER_WHEEL\n    -centerPos steering wheel center point\n    -leftKey key control in the left direction\n    -rightKey Right key control\n    -UpKey key control\n    -downKey key control in down direction\n    -leftOffset After dragging the left arrow key, drag to the leftOffset horizontally to the centerPos\n    -rightOffset After pressing the right direction key, drag it to the right offset of the center to the right of the centerPos position\n    -upOffset After pressing the up arrow key, drag it to the upper offset position horizontally relative to the centerPos position\n    -downOffset Press the down arrow key and drag it to the downOffset position horizontally relative to the centerPos position\n\n## Visual Key Mapping Tool\n\n1. Just use [QuickAssistant](https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh)\n\n![game](../screenshot/game.png)\n\n2. A web-based GUI tool is available to help you create and manage key mappings visually: [ScrcpyKeyMapper](https://github.com/w4po/ScrcpyKeyMapper)\n\n![ScrcpyKeyMapper Screenshot](https://raw.githubusercontent.com/w4po/ScrcpyKeyMapper/main/assets/screenshot.png)\n\nYou can use this tool to:\n- Create key mappings visually\n- Test your mappings in real-time\n- Export mappings as JSON files\n- Import existing mappings for editing\n\nTry it online: [ScrcpyKeyMapper Web App](https://w4po.github.io/ScrcpyKeyMapper)\n\n"
  },
  {
    "path": "docs/KeyMapDes_zh.md",
    "content": "# 自定义按键映射说明\n\n按键映射文件为json格式，新增自己的按键映射文件需要放在keymap目录中才可以被QtScrcpy识别。\n\n按键映射文件的具体编写格式下面会介绍，也可以参考自带的按键映射文件。\n\n## 按键映射脚本格式说明\n\n### 通用说明\n\n- 按键映射中的坐标位置都是用相对位置表示的，屏幕的宽高都用1表示，例如屏幕的像素为1920x1080，那么坐标(0.5,0.5)则表示的是\n以屏幕左上角为原点，像素坐标(1920,1080)*(0.5,0.5)=(960,540)的位置。\n    \n    或者鼠标左键单击时控制台会输出此时的pos，直接使用这个pos即可\n    ![](image/debug-keymap-pos.png)\n\n- 按键映射中的按键码是用Qt的枚举表示的，详细说明可以[参考Qt文档]( https://doc.qt.io/qt-5/qt.html )(搜索 The key names used by Qt. 可以快速定位)。\n- 开发人员选项中打开如下两个设置，可以方便的观察触摸点的坐标：\n![](image/显示指针位置.jpg)\n\n### 映射类型说明\n\n- switchKey：切换自定义按键映射的开关键，默认为普通映射，需要使用这个按键在普通映射和自定义映射之间切换。\n\n- mouseMoveMap：鼠标移动映射，鼠标的移动将被映射为以startPos为起点，以鼠标移动方向为移动方向的手指拖动操作（开启鼠标移动映射以后会隐藏鼠标，限制鼠标移动范围）。\n一般在FPS手游中用来调整人物视野。\n    - startPos 手指拖动起始点\n    - speedRatio 鼠标移动映射为手指拖动的比例，可以控制鼠标灵敏度，数值要大于0.00225，数值越大，灵敏度越低，Y轴以2.25的比率平移。如果这不适合您的手机屏幕，请使用以下两种设置来设置单个灵敏度值。\n    - speedRatioX 鼠标X轴的速度比灵敏度。此值必须至少为0.001。\n    - speedRatioY 鼠标Y轴的速度比灵敏度。此值必须至少为0.001。\n    - smallEyes 触发小眼睛的按键，按下此按键以后，鼠标的移动将被映射为以smallEyes.pos为起点，以鼠标移动方向为移动方向的手指拖动操作\n\n- keyMapNodes 一般按键的映射，json数组，所有一般按键映射都放在这个数组中，将键盘的按键映射为普通的手指点击。\n\n一般按键映射有如下几种类型：\n\n- type 按键映射的类型，每个keyMapNodes中的元素都需要指明，可以是如下类型：\n    - KMT_CLICK 普通点击，按键按下模拟为手指按下，按键抬起模拟为手指抬起\n    - KMT_CLICK_TWICE 两次点击，按键按下模拟为手指按下再抬起，按键抬起模拟为手指按下再抬起\n    - KMT_CLICK_MULTI 多次点击，根据clickNodes数组中的delay和pos实现一个按键多次点击\n    - KMT_DRAG 拖拽，按键按下模拟为手指按下并拖动一段距离，按键抬起模拟为手指抬起\n    - KMT_STEER_WHEEL 方向盘映射，专用于FPS游戏中移动人物脚步的方向盘的映射，需要4个按键来配合。\n\n不同按键映射类型的专有属性说明：\n\n- KMT_CLICK\n    - key 要映射的按键码\n    - pos 模拟触摸的位置\n    - switchMap 是否释放出鼠标，点击此按键后，除了默认的模拟触摸映射，是否释放出鼠标操作。（可以参考和平精英映射中M地图映射的效果）\n\n- KMT_CLICK_TWICE\n    - key 要映射的按键码\n    - pos 模拟触摸的位置\n\n- KMT_CLICK_MULTI\n    - delay 延迟delay毫秒以后再模拟触摸\n    - pos 模拟触摸的位置\n\n- KMT_DRAG\n    - key 要映射的按键码\n    - startPos 模拟触摸拖动的开始位置\n    - endPos 模拟触摸拖动的结束位置\n    - dragSpeed 拖动移动的速度（范围0-1，默认1.0）。数值越大，移动越快\n    - startDelay 可选的延迟时间（毫秒），在开始拖动移动之前等待指定的时间\n\n- KMT_STEER_WHEEL\n    - centerPos 方向盘中心点\n    - leftKey 左方向的按键控制\n    - rightKey 右方向的按键控制\n    - upKey 上方向的按键控制\n    - downKey 下方向的按键控制\n    - leftOffset 按下左方向键后模拟拖动到相对centerPos位置水平偏左leftOffset处\n    - rightOffset 按下右方向键后模拟拖动到相对centerPos位置水平偏右rightOffset处\n    - upOffset 按下上方向键后模拟拖动到相对centerPos位置水平偏上upOffset处\n    - downOffset 按下下方向键后模拟拖动到相对centerPos位置水平偏下downOffset处\n    \n## 可视化按键映射工具\n1. 直接使用[QuickAssistant](https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh)\n\n![game](../screenshot/game.png)\n\n2. 还有一个基于Web的GUI工具可以帮助你直观地创建和管理按键映射：[ScrcpyKeyMapper](https://github.com/w4po/ScrcpyKeyMapper)\n\n![ScrcpyKeyMapper截图](https://raw.githubusercontent.com/w4po/ScrcpyKeyMapper/main/assets/screenshot.png)\n\n你可以使用这个工具来：\n- 直观地创建按键映射\n- 实时测试你的映射\n- 导出映射为JSON文件\n- 导入现有映射进行编辑\n\n在线试用：[ScrcpyKeyMapper网页应用](https://w4po.github.io/ScrcpyKeyMapper)\n"
  },
  {
    "path": "docs/TODO.md",
    "content": "# TODO\n## 低优先级\n- text转换 https://github.com/Genymobile/scrcpy/commit/c916af0984f72a60301d13fa8ef9a85112f54202?tdsourcetag=s_pctim_aiomsg\n- 关闭number lock时的数字小键盘处理 https://github.com/Genymobile/scrcpy/commit/cd69eb4a4fecf8167208399def4ef536b59c9d22\n- mipmapping https://github.com/Genymobile/scrcpy/commit/bea7658807d276aeab7d18d856a366c83ee05827\n\n## 中优先级\n- 脚本\n- 某些机器软解不行\n- opengles 3.0 兼容性参考[这里](https://github.com/libretro/glsl-shaders/blob/master/nnedi3/shaders/yuv-to-rgb-2x.glsl)\n- 通过host:track-devices实现自动连接 https://www.jianshu.com/p/2cb86c6de76c\n- 旋转 https://github.com/Genymobile/scrcpy/commit/d48b375a1dbc8bed92e3424b5967e59c2d8f6ca1\n- 禁用屏幕保护 https://github.com/Genymobile/scrcpy/commit/dc7b60e6199b90a45ea26751988f6f30f8b2efdf\n- 自定义快捷键 https://github.com/Genymobile/scrcpy/commit/1b76d9fd78c3a88a8503a72d4cd2f65bdb836aa4\n\n## 高优先级\n- linux打包以及版本号\n- 关于\n- 音频转发 https://github.com/rom1v/sndcpy\n\n# mark\n## ffmpeg\n[ffmpeg编译参数详解](https://www.cnblogs.com/wainiwann/p/4204230.html)\n\n## fontawesome\n[fontawesome 在线搜索](http://www.fontawesome.com.cn/cheatsheet/)\n\n## adb\n以下是 ADB 和 Fastboot 的谷歌官方下载链接：\n\nADB和Fastboot for Windows\n\nhttps://dl.google.com/android/repository/platform-tools-latest-windows.zip\n\nADB和Fastboot for Mac\n\nhttps://dl.google.com/android/repository/platform-tools-latest-darwin.zip\n\nADB和Fastboot for Linux\n\nhttps://dl.google.com/android/repository/platform-tools-latest-linux.zip\n\n由于这些是直接的 Google 链接，用户可以确保下载不仅是官方的，而且将始终能够获得最新版本的 ADB 和 Fastboot\n"
  },
  {
    "path": "keymap/FRAG.json",
    "content": "{\n\t\"old-switchKey\": \"Key_QuoteLeft\",\n\t\"switchKey\": \"RightButton\",\n\t\"mouseMoveMap\": {\n\t\t\"startPos\": {\n\t\t\t\"x\": 0.5,\n\t\t\t\"y\": 0.5\n\t\t},\n\t\t\"speedRatioX\": 3.25,\n\t\t\"speedRatioY\": 1.25\n\t},\n\t\"keyMapNodes\": [{\n\t\t\t\"comment\": \"Steering Wheel\",\n\t\t\t\"type\": \"KMT_STEER_WHEEL\",\n\t\t\t\"centerPos\": {\n\t\t\t\t\"x\": 0.194792,\n\t\t\t\t\"y\": 0.716484\n\t\t\t},\n\t\t\t\"leftOffset\": 0.15,\n\t\t\t\"rightOffset\": 0.15,\n\t\t\t\"upOffset\": 0.15,\n\t\t\t\"downOffset\": 0.15,\n\t\t\t\"leftKey\": \"Key_A\",\n\t\t\t\"rightKey\": \"Key_D\",\n\t\t\t\"upKey\": \"Key_W\",\n\t\t\t\"downKey\": \"Key_S\"\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"Activate item under crosshair\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"LeftButton\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.51875,\n\t\t\t\t\"y\": 0.496703\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"Activate first special skill\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_E\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.909375,\n\t\t\t\t\"y\": 0.542857\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"Activate Chat\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_C\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.905208,\n\t\t\t\t\"y\": 0.254945\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"Chat option 1\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_1\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.875,\n\t\t\t\t\"y\": 0.523077\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"Chat option 2\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_2\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.875,\n\t\t\t\t\"y\": 0.606593\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"Chat option 3\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_3\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.875,\n\t\t\t\t\"y\": 0.685714\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"Chat option 4\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_4\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.875,\n\t\t\t\t\"y\": 0.756044\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"Chat option 5\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_5\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.875,\n\t\t\t\t\"y\": 0.832967\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"Chat option 6\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_6\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.875,\n\t\t\t\t\"y\": 0.911273\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "keymap/gameforpeace.json",
    "content": "{\n\t\"switchKey\": \"Key_QuoteLeft\",\n\t\"mouseMoveMap\": {\n\t\t\"startPos\": {\n\t\t\t\"x\": 0.57,\n\t\t\t\"y\": 0.26\n\t\t},\n\t\t\"speedRatioX\": 3.25,\n\t\t\"speedRatioY\": 1.25,\n\t\t\"smallEyes\": {\n\t\t\t\"comment\": \"小眼睛\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Alt\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.8,\n\t\t\t\t\"y\": 0.31\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t\"speedRatio\": 10\n\t},\n\t\"keyMapNodes\": [\n\t\t{\n\t\t\t\"comment\": \"方向盘\",\n\t\t\t\"type\": \"KMT_STEER_WHEEL\",\n\t\t\t\"centerPos\": {\n\t\t\t\t\"x\": 0.16,\n\t\t\t\t\"y\": 0.75\n\t\t\t},\n\t\t\t\"leftOffset\": 0.1,\n\t\t\t\"rightOffset\": 0.1,\n\t\t\t\"upOffset\": 0.27,\n\t\t\t\"downOffset\": 0.2,\n\t\t\t\"leftKey\": \"Key_A\",\n\t\t\t\"rightKey\": \"Key_D\",\n\t\t\t\"upKey\": \"Key_W\",\n\t\t\t\"downKey\": \"Key_S\"\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"左探头\",\n\t\t\t\"type\": \"KMT_CLICK_TWICE\",\n\t\t\t\"key\": \"Key_Q\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.12,\n\t\t\t\t\"y\": 0.35\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"右探头\",\n\t\t\t\"type\": \"KMT_CLICK_TWICE\",\n\t\t\t\"key\": \"Key_E\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.2,\n\t\t\t\t\"y\": 0.35\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"自动跑\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Equal\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.84,\n\t\t\t\t\"y\": 0.26\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"跳\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Space\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.96,\n\t\t\t\t\"y\": 0.7\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"地图\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_M\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.98,\n\t\t\t\t\"y\": 0.03\n\t\t\t},\n\t\t\t\"switchMap\": true\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"背包\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Tab\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.06,\n\t\t\t\t\"y\": 0.9\n\t\t\t},\n\t\t\t\"switchMap\": true\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"视角\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_V\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.23,\n\t\t\t\t\"y\": 0.95\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"趴\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Z\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.95,\n\t\t\t\t\"y\": 0.9\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"蹲\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_C\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.86,\n\t\t\t\t\"y\": 0.92\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"换弹\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_R\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.795,\n\t\t\t\t\"y\": 0.93\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"捡东西1\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_F\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.7,\n\t\t\t\t\"y\": 0.34\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"捡东西2\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_G\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.7,\n\t\t\t\t\"y\": 0.44\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"捡东西3\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_H\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.7,\n\t\t\t\t\"y\": 0.54\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"换枪1\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_1\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.45,\n\t\t\t\t\"y\": 0.9\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"换枪2\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_2\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.55,\n\t\t\t\t\"y\": 0.9\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"手雷\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_3\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.67,\n\t\t\t\t\"y\": 0.92\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"快速打药\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_4\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.33,\n\t\t\t\t\"y\": 0.95\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"下车\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_5\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.92,\n\t\t\t\t\"y\": 0.4\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"救人\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_6\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.49,\n\t\t\t\t\"y\": 0.63\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"手枪\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_7\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.63,\n\t\t\t\t\"y\": 0.82\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"车加速\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Shift\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.8,\n\t\t\t\t\"y\": 0.8\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"投掷物菜单\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_F1\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.69,\n\t\t\t\t\"y\": 0.88\n\t\t\t},\n\t\t\t\"switchMap\": true\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"药物菜单\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_F2\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.31,\n\t\t\t\t\"y\": 0.88\n\t\t\t},\n\t\t\t\"switchMap\": true\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"消息菜单\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_F3\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.98,\n\t\t\t\t\"y\": 0.34\n\t\t\t},\n\t\t\t\"switchMap\": true\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"表情菜单\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_F4\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.81,\n\t\t\t\t\"y\": 0.03\n\t\t\t},\n\t\t\t\"switchMap\": true\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"开关门\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_X\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.7,\n\t\t\t\t\"y\": 0.7\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"舔包\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_T\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.72,\n\t\t\t\t\"y\": 0.26\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"开枪\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"LeftButton\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.86,\n\t\t\t\t\"y\": 0.72\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"开镜\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"RightButton\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.96,\n\t\t\t\t\"y\": 0.52\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "keymap/identityv.json",
    "content": "{\n\t\"comment\":\"https://doc.qt.io/qt-5/qt.html#Key-enum\",\n\t\"old-switchKey\": \"Key_QuoteLeft\",\n\t\"switchKey\": \"RightButton\",\n\t\"mouseMoveMap\": {\n\t\t\"startPos\": {\n\t\t\t\"x\": 0.700,\n\t\t\t\"y\": 0.410\n\t\t},\n\t\t\"speedRatioX\": 3.25,\n\t\t\"speedRatioY\": 1.25\n\t},\n\t\"keyMapNodes\": [{\n\t\t\t\"comment\": \"退出\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Escape\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.015,\n\t\t\t\t\"y\": 0.042\n\t\t\t},\n\t\t\t\"switchMap\": true\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"方向盘\",\n\t\t\t\"type\": \"KMT_STEER_WHEEL\",\n\t\t\t\"centerPos\": {\n\t\t\t\t\"x\": 0.16,\n\t\t\t\t\"y\": 0.70\n\t\t\t},\n\t\t\t\"leftOffset\": 0.1,\n\t\t\t\"rightOffset\": 0.1,\n\t\t\t\"upOffset\": 0.1,\n\t\t\t\"downOffset\": 0.1,\n\t\t\t\"leftKey\": \"Key_A\",\n\t\t\t\"rightKey\": \"Key_D\",\n\t\t\t\"upKey\": \"Key_W\",\n\t\t\t\"downKey\": \"Key_S\"\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"动作\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"LeftButton\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.907,\n\t\t\t\t\"y\": 0.842\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"动作\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Space\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.907,\n\t\t\t\t\"y\": 0.842\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"蹲\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Control\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.8125,\n\t\t\t\t\"y\": 0.912\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"走/翻越\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_C\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.815,\n\t\t\t\t\"y\": 0.761\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"跑/技能1\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Z\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.868,\n\t\t\t\t\"y\": 0.636\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"技能2\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_E\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.945,\n\t\t\t\t\"y\": 0.619\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"特质/道具1\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Q\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.949,\n\t\t\t\t\"y\": 0.458\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"底牌/切换1\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Tab\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.885,\n\t\t\t\t\"y\": 0.488\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"发言/道具2\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_R\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.950,\n\t\t\t\t\"y\": 0.308\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"涂鸦\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Y\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.732,\n\t\t\t\t\"y\": 0.904\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"盯红蝶/挂人\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_F\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.815,\n\t\t\t\t\"y\": 0.514\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"判定\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_T\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.681,\n\t\t\t\t\"y\": 0.750\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"中间\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Shift\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.5,\n\t\t\t\t\"y\": 0.6\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"小丑零件1\",\n\t\t\t\"type\": \"KMT_DRAG\",\n\t\t\t\"key\": \"Key_1\",\n\t\t\t\"startPos\": {\n\t\t\t\t\"x\": 0.951,\n\t\t\t\t\"y\": 0.615\n\t\t\t},\n\t\t\t\"endPos\": {\n\t\t\t\t\"x\": 0.911,\n\t\t\t\t\"y\": 0.472\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"小丑零件2\",\n\t\t\t\"type\": \"KMT_DRAG\",\n\t\t\t\"key\": \"Key_2\",\n\t\t\t\"startPos\": {\n\t\t\t\t\"x\": 0.951,\n\t\t\t\t\"y\": 0.615\n\t\t\t},\n\t\t\t\"endPos\": {\n\t\t\t\t\"x\": 0.861,\n\t\t\t\t\"y\": 0.615\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"小丑零件3\",\n\t\t\t\"type\": \"KMT_DRAG\",\n\t\t\t\"key\": \"Key_3\",\n\t\t\t\"startPos\": {\n\t\t\t\t\"x\": 0.951,\n\t\t\t\t\"y\": 0.615\n\t\t\t},\n\t\t\t\"endPos\": {\n\t\t\t\t\"x\": 0.907,\n\t\t\t\t\"y\": 0.774\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"挣扎左\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Left\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.267,\n\t\t\t\t\"y\": 0.550\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"挣扎右\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Right\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.736,\n\t\t\t\t\"y\": 0.550\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"小镜头\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Alt\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.801,\n\t\t\t\t\"y\": 0.244\n\t\t\t},\n\t\t\t\"speedRatio\": 2\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "keymap/test.json",
    "content": "{\n\t\"switchKey\": \"Key_QuoteLeft\",\n\t\"keyMapNodes\": [\n\t\t{\n\t\t\t\"comment\": \"测试一键多点\",\n\t\t\t\"type\": \"KMT_CLICK_MULTI\",\n\t\t\t\"key\": \"Key_Space\",\n\t\t\t\"clickNodes\": [\n\t\t\t\t{\n\t\t\t\t\t\"delay\": 500,\n\t\t\t\t\t\"pos\": {\n\t\t\t\t\t\t\"x\": 0.5,\n\t\t\t\t\t\t\"y\": 0.5\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"delay\": 500,\n\t\t\t\t\t\"pos\": {\n\t\t\t\t\t\t\"x\": 0.8,\n\t\t\t\t\t\t\"y\": 0.8\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"测试拖拽\",\n\t\t\t\"type\": \"KMT_DRAG\",\n\t\t\t\"key\": \"Key_Up\",\n\t\t\t\"startPos\": {\n\t\t\t\t\"x\": 0.5,\n\t\t\t\t\"y\": 0.7\n\t\t\t},\n\t\t\t\"endPos\": {\n\t\t\t\t\"x\": 0.5,\n\t\t\t\t\"y\": 0.3\n\t\t\t}\n\t\t}\n\t]\n}"
  },
  {
    "path": "keymap/tiktok.json",
    "content": "{\n\t\"switchKey\": \"Key_QuoteLeft\",\n\t\"keyMapNodes\": [\n\t\t{\n\t\t\t\"comment\": \"暂停/继续\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Space\",\n\t\t\t\"pos\": {\n\t\t\t\t\"x\": 0.5,\n\t\t\t\t\"y\": 0.5\n\t\t\t},\n\t\t\t\"switchMap\": false\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"上滑\",\n\t\t\t\"type\": \"KMT_DRAG\",\n\t\t\t\"key\": \"Key_Up\",\n\t\t\t\"startPos\": {\n\t\t\t\t\"x\": 0.5,\n\t\t\t\t\"y\": 0.7\n\t\t\t},\n\t\t\t\"endPos\": {\n\t\t\t\t\"x\": 0.5,\n\t\t\t\t\"y\": 0.3\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"下滑\",\n\t\t\t\"type\": \"KMT_DRAG\",\n\t\t\t\"key\": \"Key_Down\",\n\t\t\t\"startPos\": {\n\t\t\t\t\"x\": 0.5,\n\t\t\t\t\"y\": 0.3\n\t\t\t},\n\t\t\t\"endPos\": {\n\t\t\t\t\"x\": 0.5,\n\t\t\t\t\"y\": 0.7\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"左滑\",\n\t\t\t\"type\": \"KMT_DRAG\",\n\t\t\t\"key\": \"Key_Left\",\n\t\t\t\"startPos\": {\n\t\t\t\t\"x\": 0.7,\n\t\t\t\t\"y\": 0.5\n\t\t\t},\n\t\t\t\"endPos\": {\n\t\t\t\t\"x\": 0.3,\n\t\t\t\t\"y\": 0.5\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"comment\": \"右滑\",\n\t\t\t\"type\": \"KMT_DRAG\",\n\t\t\t\"key\": \"Key_Right\",\n\t\t\t\"startPos\": {\n\t\t\t\t\"x\": 0.3,\n\t\t\t\t\"y\": 0.5\n\t\t\t},\n\t\t\t\"endPos\": {\n\t\t\t\t\"x\": 0.7,\n\t\t\t\t\"y\": 0.5\n\t\t\t}\n\t\t}\n\t]\n}"
  }
]